diff --git a/osu.Desktop/osu.Desktop.csproj b/osu.Desktop/osu.Desktop.csproj
index 0d4efc7eed..3cf95e9b3e 100644
--- a/osu.Desktop/osu.Desktop.csproj
+++ b/osu.Desktop/osu.Desktop.csproj
@@ -26,7 +26,7 @@
-
+
@@ -35,4 +35,4 @@
-
+
\ No newline at end of file
diff --git a/osu.Game.Rulesets.Catch/CatchRuleset.cs b/osu.Game.Rulesets.Catch/CatchRuleset.cs
index 2325a8cad9..d0180f1791 100644
--- a/osu.Game.Rulesets.Catch/CatchRuleset.cs
+++ b/osu.Game.Rulesets.Catch/CatchRuleset.cs
@@ -82,56 +82,25 @@ namespace osu.Game.Rulesets.Catch
{
new CatchModEasy(),
new CatchModNoFail(),
- new MultiMod
- {
- Mods = new Mod[]
- {
- new CatchModHalfTime(),
- new CatchModDaycore(),
- },
- },
+ new MultiMod(new CatchModHalfTime(), new CatchModDaycore())
};
-
case ModType.DifficultyIncrease:
return new Mod[]
{
new CatchModHardRock(),
- new MultiMod
- {
- Mods = new Mod[]
- {
- new CatchModSuddenDeath(),
- new CatchModPerfect(),
- },
- },
- new MultiMod
- {
- Mods = new Mod[]
- {
- new CatchModDoubleTime(),
- new CatchModNightcore(),
- },
- },
+ new MultiMod(new CatchModSuddenDeath(), new CatchModPerfect()),
+ new MultiMod(new CatchModDoubleTime(), new CatchModNightcore()),
new CatchModHidden(),
new CatchModFlashlight(),
};
-
case ModType.Special:
return new Mod[]
{
new CatchModRelax(),
null,
null,
- new MultiMod
- {
- Mods = new Mod[]
- {
- new CatchModAutoplay(),
- new ModCinema(),
- },
- },
+ new MultiMod(new CatchModAutoplay(), new ModCinema()),
};
-
default:
return new Mod[] { };
}
diff --git a/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs b/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs
index 2517839355..ca2002b7c9 100644
--- a/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs
+++ b/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs
@@ -7,6 +7,7 @@ using System.Linq;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Difficulty;
using osu.Game.Rulesets.Mania.Beatmaps;
+using osu.Game.Rulesets.Mania.Mods;
using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Mods;
@@ -141,5 +142,22 @@ namespace osu.Game.Rulesets.Mania.Difficulty
return difficulty;
}
+
+ protected override Mod[] DifficultyAdjustmentMods => new Mod[]
+ {
+ new ManiaModDoubleTime(),
+ new ManiaModHalfTime(),
+ new ManiaModEasy(),
+ new ManiaModHardRock(),
+ new ManiaModKey1(),
+ new ManiaModKey2(),
+ new ManiaModKey3(),
+ new ManiaModKey4(),
+ new ManiaModKey5(),
+ new ManiaModKey6(),
+ new ManiaModKey7(),
+ new ManiaModKey8(),
+ new ManiaModKey9(),
+ };
}
}
diff --git a/osu.Game.Rulesets.Mania/ManiaRuleset.cs b/osu.Game.Rulesets.Mania/ManiaRuleset.cs
index 02ecb3afda..5f803e3406 100644
--- a/osu.Game.Rulesets.Mania/ManiaRuleset.cs
+++ b/osu.Game.Rulesets.Mania/ManiaRuleset.cs
@@ -105,78 +105,34 @@ namespace osu.Game.Rulesets.Mania
{
new ManiaModEasy(),
new ManiaModNoFail(),
- new MultiMod
- {
- Mods = new Mod[]
- {
- new ManiaModHalfTime(),
- new ManiaModDaycore(),
- },
- },
+ new MultiMod(new ManiaModHalfTime(), new ManiaModDaycore()),
};
-
case ModType.DifficultyIncrease:
return new Mod[]
{
new ManiaModHardRock(),
- new MultiMod
- {
- Mods = new Mod[]
- {
- new ManiaModSuddenDeath(),
- new ManiaModPerfect(),
- },
- },
- new MultiMod
- {
- Mods = new Mod[]
- {
- new ManiaModDoubleTime(),
- new ManiaModNightcore(),
- },
- },
- new MultiMod
- {
- Mods = new Mod[]
- {
- new ManiaModFadeIn(),
- new ManiaModHidden(),
- }
- },
+ new MultiMod(new ManiaModSuddenDeath(), new ManiaModPerfect()),
+ new MultiMod(new ManiaModDoubleTime(), new ManiaModNightcore()),
+ new MultiMod(new ManiaModFadeIn(), new ManiaModHidden()),
new ManiaModFlashlight(),
};
-
case ModType.Special:
return new Mod[]
{
- new MultiMod
- {
- Mods = new Mod[]
- {
- new ManiaModKey4(),
- new ManiaModKey5(),
- new ManiaModKey6(),
- new ManiaModKey7(),
- new ManiaModKey8(),
- new ManiaModKey9(),
- new ManiaModKey1(),
- new ManiaModKey2(),
- new ManiaModKey3(),
- },
- },
+ new MultiMod(new ManiaModKey4(),
+ new ManiaModKey5(),
+ new ManiaModKey6(),
+ new ManiaModKey7(),
+ new ManiaModKey8(),
+ new ManiaModKey9(),
+ new ManiaModKey1(),
+ new ManiaModKey2(),
+ new ManiaModKey3()),
new ManiaModRandom(),
new ManiaModDualStages(),
new ManiaModMirror(),
- new MultiMod
- {
- Mods = new Mod[]
- {
- new ManiaModAutoplay(),
- new ModCinema(),
- },
- },
+ new MultiMod(new ManiaModAutoplay(), new ModCinema()),
};
-
default:
return new Mod[] { };
}
diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaKeyMod.cs b/osu.Game.Rulesets.Mania/Mods/ManiaKeyMod.cs
index e02db68a28..6bfe295c3d 100644
--- a/osu.Game.Rulesets.Mania/Mods/ManiaKeyMod.cs
+++ b/osu.Game.Rulesets.Mania/Mods/ManiaKeyMod.cs
@@ -1,6 +1,8 @@
// Copyright (c) 2007-2018 ppy Pty Ltd .
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+using System;
+using System.Linq;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Mania.Beatmaps;
using osu.Game.Rulesets.Mods;
@@ -24,5 +26,18 @@ namespace osu.Game.Rulesets.Mania.Mods
mbc.TargetColumns = KeyCount;
}
+
+ public override Type[] IncompatibleMods => new[]
+ {
+ typeof(ManiaModKey1),
+ typeof(ManiaModKey2),
+ typeof(ManiaModKey3),
+ typeof(ManiaModKey4),
+ typeof(ManiaModKey5),
+ typeof(ManiaModKey6),
+ typeof(ManiaModKey7),
+ typeof(ManiaModKey8),
+ typeof(ManiaModKey9),
+ }.Except(new[] { GetType() }).ToArray();
}
}
diff --git a/osu.Game.Rulesets.Mania/UI/Column.cs b/osu.Game.Rulesets.Mania/UI/Column.cs
index dee113c82f..9c1830e642 100644
--- a/osu.Game.Rulesets.Mania/UI/Column.cs
+++ b/osu.Game.Rulesets.Mania/UI/Column.cs
@@ -277,8 +277,13 @@ namespace osu.Game.Rulesets.Mania.UI
if (action != Action)
return false;
- var hitObject = HitObjects.Objects.LastOrDefault(h => h.HitObject.StartTime > Time.Current) ?? HitObjects.Objects.FirstOrDefault();
- hitObject?.PlaySamples();
+ var nextObject =
+ HitObjects.AliveObjects.FirstOrDefault(h => h.HitObject.StartTime > Time.Current) ??
+ // fallback to non-alive objects to find next off-screen object
+ HitObjects.Objects.FirstOrDefault(h => h.HitObject.StartTime > Time.Current) ??
+ HitObjects.Objects.LastOrDefault();
+
+ nextObject?.PlaySamples();
return true;
}
diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs
index 3ed072a275..94d2afbf45 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs
@@ -8,6 +8,7 @@ using osu.Game.Rulesets.Difficulty;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu.Difficulty.Preprocessing;
using osu.Game.Rulesets.Osu.Difficulty.Skills;
+using osu.Game.Rulesets.Osu.Mods;
using osu.Game.Rulesets.Osu.Objects;
namespace osu.Game.Rulesets.Osu.Difficulty
@@ -71,5 +72,13 @@ namespace osu.Game.Rulesets.Osu.Difficulty
return starRating;
}
+
+ protected override Mod[] DifficultyAdjustmentMods => new Mod[]
+ {
+ new OsuModDoubleTime(),
+ new OsuModHalfTime(),
+ new OsuModEasy(),
+ new OsuModHardRock(),
+ };
}
}
diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs
index c455bb2af6..b920e889ce 100644
--- a/osu.Game.Rulesets.Osu/OsuRuleset.cs
+++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs
@@ -93,57 +93,26 @@ namespace osu.Game.Rulesets.Osu
{
new OsuModEasy(),
new OsuModNoFail(),
- new MultiMod
- {
- Mods = new Mod[]
- {
- new OsuModHalfTime(),
- new OsuModDaycore(),
- },
- },
+ new MultiMod(new OsuModHalfTime(), new OsuModDaycore()),
};
-
case ModType.DifficultyIncrease:
return new Mod[]
{
new OsuModHardRock(),
- new MultiMod
- {
- Mods = new Mod[]
- {
- new OsuModSuddenDeath(),
- new OsuModPerfect(),
- },
- },
- new MultiMod
- {
- Mods = new Mod[]
- {
- new OsuModDoubleTime(),
- new OsuModNightcore(),
- },
- },
+ new MultiMod(new OsuModSuddenDeath(), new OsuModPerfect()),
+ new MultiMod(new OsuModDoubleTime(), new OsuModNightcore()),
new OsuModHidden(),
new OsuModFlashlight(),
};
-
case ModType.Special:
return new Mod[]
{
new OsuModRelax(),
new OsuModAutopilot(),
new OsuModSpunOut(),
- new MultiMod
- {
- Mods = new Mod[]
- {
- new OsuModAutoplay(),
- new ModCinema(),
- },
- },
+ new MultiMod(new OsuModAutoplay(), new ModCinema()),
new OsuModTarget(),
};
-
default:
return new Mod[] { };
}
diff --git a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs
index b63f85623e..f2d5631e93 100644
--- a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs
+++ b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs
@@ -64,9 +64,7 @@ namespace osu.Game.Rulesets.Osu.UI
public override void PostProcess()
{
- connectionLayer.HitObjects = HitObjects.Objects
- .Select(d => d.HitObject)
- .OrderBy(h => h.StartTime).OfType();
+ connectionLayer.HitObjects = HitObjects.Objects.Select(d => d.HitObject).OfType();
}
private void onJudgement(DrawableHitObject judgedObject, Judgement judgement)
diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs
index 57e1e65064..bb666eb528 100644
--- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs
+++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs
@@ -6,6 +6,7 @@ using System.Collections.Generic;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Difficulty;
using osu.Game.Rulesets.Mods;
+using osu.Game.Rulesets.Taiko.Mods;
using osu.Game.Rulesets.Taiko.Objects;
namespace osu.Game.Rulesets.Taiko.Difficulty
@@ -62,6 +63,14 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
return starRating;
}
+ protected override Mod[] DifficultyAdjustmentMods => new Mod[]
+ {
+ new TaikoModDoubleTime(),
+ new TaikoModHalfTime(),
+ new TaikoModEasy(),
+ new TaikoModHardRock(),
+ };
+
private bool calculateStrainValues()
{
// Traverse hitObjects in pairs to calculate the strain value of NextHitObject from the strain value of CurrentHitObject and environment.
diff --git a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs
index abaa8db597..ccf28a2f12 100644
--- a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs
+++ b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs
@@ -84,56 +84,25 @@ namespace osu.Game.Rulesets.Taiko
{
new TaikoModEasy(),
new TaikoModNoFail(),
- new MultiMod
- {
- Mods = new Mod[]
- {
- new TaikoModHalfTime(),
- new TaikoModDaycore(),
- },
- },
+ new MultiMod(new TaikoModHalfTime(), new TaikoModDaycore()),
};
-
case ModType.DifficultyIncrease:
return new Mod[]
{
new TaikoModHardRock(),
- new MultiMod
- {
- Mods = new Mod[]
- {
- new TaikoModSuddenDeath(),
- new TaikoModPerfect(),
- },
- },
- new MultiMod
- {
- Mods = new Mod[]
- {
- new TaikoModDoubleTime(),
- new TaikoModNightcore(),
- },
- },
+ new MultiMod(new TaikoModSuddenDeath(), new TaikoModPerfect()),
+ new MultiMod(new TaikoModDoubleTime(), new TaikoModDaycore()),
new TaikoModHidden(),
new TaikoModFlashlight(),
};
-
case ModType.Special:
return new Mod[]
{
new TaikoModRelax(),
null,
null,
- new MultiMod
- {
- Mods = new Mod[]
- {
- new TaikoModAutoplay(),
- new ModCinema(),
- },
- },
+ new MultiMod(new TaikoModAutoplay(), new ModCinema()),
};
-
default:
return new Mod[] { };
}
diff --git a/osu.Game.Tests/NonVisual/DifficultyAdjustmentModCombinationsTest.cs b/osu.Game.Tests/NonVisual/DifficultyAdjustmentModCombinationsTest.cs
new file mode 100644
index 0000000000..fd697ba3d3
--- /dev/null
+++ b/osu.Game.Tests/NonVisual/DifficultyAdjustmentModCombinationsTest.cs
@@ -0,0 +1,152 @@
+// Copyright (c) 2007-2018 ppy Pty Ltd .
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using System;
+using System.Collections.Generic;
+using NUnit.Framework;
+using osu.Game.Rulesets.Difficulty;
+using osu.Game.Rulesets.Mods;
+
+namespace osu.Game.Tests.NonVisual
+{
+ [TestFixture]
+ public class DifficultyAdjustmentModCombinationsTest
+ {
+ [Test]
+ public void TestNoMods()
+ {
+ var combinations = new TestDifficultyCalculator().CreateDifficultyAdjustmentModCombinations();
+
+ Assert.AreEqual(1, combinations.Length);
+ Assert.IsTrue(combinations[0] is NoModMod);
+ }
+
+ [Test]
+ public void TestSingleMod()
+ {
+ var combinations = new TestDifficultyCalculator(new ModA()).CreateDifficultyAdjustmentModCombinations();
+
+ Assert.AreEqual(2, combinations.Length);
+ Assert.IsTrue(combinations[0] is NoModMod);
+ Assert.IsTrue(combinations[1] is ModA);
+ }
+
+ [Test]
+ public void TestDoubleMod()
+ {
+ var combinations = new TestDifficultyCalculator(new ModA(), new ModB()).CreateDifficultyAdjustmentModCombinations();
+
+ Assert.AreEqual(4, combinations.Length);
+ Assert.IsTrue(combinations[0] is NoModMod);
+ Assert.IsTrue(combinations[1] is ModA);
+ Assert.IsTrue(combinations[2] is MultiMod);
+ Assert.IsTrue(combinations[3] is ModB);
+
+ Assert.IsTrue(((MultiMod)combinations[2]).Mods[0] is ModA);
+ Assert.IsTrue(((MultiMod)combinations[2]).Mods[1] is ModB);
+ }
+
+ [Test]
+ public void TestIncompatibleMods()
+ {
+ var combinations = new TestDifficultyCalculator(new ModA(), new ModIncompatibleWithA()).CreateDifficultyAdjustmentModCombinations();
+
+ Assert.AreEqual(3, combinations.Length);
+ Assert.IsTrue(combinations[0] is NoModMod);
+ Assert.IsTrue(combinations[1] is ModA);
+ Assert.IsTrue(combinations[2] is ModIncompatibleWithA);
+ }
+
+ [Test]
+ public void TestDoubleIncompatibleMods()
+ {
+ var combinations = new TestDifficultyCalculator(new ModA(), new ModB(), new ModIncompatibleWithA(), new ModIncompatibleWithAAndB()).CreateDifficultyAdjustmentModCombinations();
+
+ Assert.AreEqual(8, combinations.Length);
+ Assert.IsTrue(combinations[0] is NoModMod);
+ Assert.IsTrue(combinations[1] is ModA);
+ Assert.IsTrue(combinations[2] is MultiMod);
+ Assert.IsTrue(combinations[3] is ModB);
+ Assert.IsTrue(combinations[4] is MultiMod);
+ Assert.IsTrue(combinations[5] is ModIncompatibleWithA);
+ Assert.IsTrue(combinations[6] is MultiMod);
+ Assert.IsTrue(combinations[7] is ModIncompatibleWithAAndB);
+
+ Assert.IsTrue(((MultiMod)combinations[2]).Mods[0] is ModA);
+ Assert.IsTrue(((MultiMod)combinations[2]).Mods[1] is ModB);
+ Assert.IsTrue(((MultiMod)combinations[4]).Mods[0] is ModB);
+ Assert.IsTrue(((MultiMod)combinations[4]).Mods[1] is ModIncompatibleWithA);
+ Assert.IsTrue(((MultiMod)combinations[6]).Mods[0] is ModIncompatibleWithA);
+ Assert.IsTrue(((MultiMod)combinations[6]).Mods[1] is ModIncompatibleWithAAndB);
+ }
+
+ [Test]
+ public void TestIncompatibleThroughBaseType()
+ {
+ var combinations = new TestDifficultyCalculator(new ModAofA(), new ModIncompatibleWithAofA()).CreateDifficultyAdjustmentModCombinations();
+
+ Assert.AreEqual(3, combinations.Length);
+ Assert.IsTrue(combinations[0] is NoModMod);
+ Assert.IsTrue(combinations[1] is ModAofA);
+ Assert.IsTrue(combinations[2] is ModIncompatibleWithAofA);
+ }
+
+ private class ModA : Mod
+ {
+ public override string Name => nameof(ModA);
+ public override string ShortenedName => nameof(ModA);
+ public override double ScoreMultiplier => 1;
+
+ public override Type[] IncompatibleMods => new[] { typeof(ModIncompatibleWithA), typeof(ModIncompatibleWithAAndB) };
+ }
+
+ private class ModB : Mod
+ {
+ public override string Name => nameof(ModB);
+ public override string ShortenedName => nameof(ModB);
+ public override double ScoreMultiplier => 1;
+
+ public override Type[] IncompatibleMods => new[] { typeof(ModIncompatibleWithAAndB) };
+ }
+
+ private class ModIncompatibleWithA : Mod
+ {
+ public override string Name => $"Incompatible With {nameof(ModA)}";
+ public override string ShortenedName => $"Incompatible With {nameof(ModA)}";
+ public override double ScoreMultiplier => 1;
+
+ public override Type[] IncompatibleMods => new[] { typeof(ModA) };
+ }
+
+ private class ModAofA : ModA
+ {
+ }
+
+ private class ModIncompatibleWithAofA : ModIncompatibleWithA
+ {
+ // Incompatible through base type
+ }
+
+ private class ModIncompatibleWithAAndB : Mod
+ {
+ public override string Name => $"Incompatible With {nameof(ModA)} and {nameof(ModB)}";
+ public override string ShortenedName => $"Incompatible With {nameof(ModA)} and {nameof(ModB)}";
+ public override double ScoreMultiplier => 1;
+
+ public override Type[] IncompatibleMods => new[] { typeof(ModA), typeof(ModB) };
+ }
+
+ private class TestDifficultyCalculator : DifficultyCalculator
+ {
+ public TestDifficultyCalculator(params Mod[] mods)
+ : base(null)
+ {
+ DifficultyAdjustmentMods = mods;
+ }
+
+ public override double Calculate(Dictionary categoryDifficulty = null) => throw new NotImplementedException();
+
+ protected override Mod[] DifficultyAdjustmentMods { get; }
+ }
+ }
+}
diff --git a/osu.Game.Tests/Visual/TestCaseEditorComposeTimeline.cs b/osu.Game.Tests/Visual/TestCaseEditorComposeTimeline.cs
index 81b491799b..6c74876e81 100644
--- a/osu.Game.Tests/Visual/TestCaseEditorComposeTimeline.cs
+++ b/osu.Game.Tests/Visual/TestCaseEditorComposeTimeline.cs
@@ -15,7 +15,7 @@ namespace osu.Game.Tests.Visual
[TestFixture]
public class TestCaseEditorComposeTimeline : OsuTestCase
{
- public override IReadOnlyList RequiredTypes => new[] { typeof(ScrollableTimeline), typeof(ScrollingTimelineContainer), typeof(BeatmapWaveformGraph), typeof(TimelineButton) };
+ public override IReadOnlyList RequiredTypes => new[] { typeof(TimelineArea), typeof(Timeline), typeof(TimelineButton) };
public TestCaseEditorComposeTimeline()
{
@@ -27,11 +27,12 @@ namespace osu.Game.Tests.Visual
Origin = Anchor.TopCentre,
State = Visibility.Visible
},
- new ScrollableTimeline
+ new TimelineArea
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
- Size = new Vector2(1000, 100)
+ RelativeSizeAxes = Axes.X,
+ Size = new Vector2(0.8f, 100)
}
};
}
diff --git a/osu.Game.Tests/Visual/TestCaseWaveform.cs b/osu.Game.Tests/Visual/TestCaseWaveform.cs
index b0966cf5c4..983b98016e 100644
--- a/osu.Game.Tests/Visual/TestCaseWaveform.cs
+++ b/osu.Game.Tests/Visual/TestCaseWaveform.cs
@@ -6,11 +6,11 @@ using OpenTK;
using OpenTK.Graphics;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
+using osu.Framework.Graphics.Audio;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Game.Graphics.Sprites;
using osu.Game.Overlays;
-using osu.Game.Screens.Edit.Screens.Compose.Timeline;
namespace osu.Game.Tests.Visual
{
@@ -40,14 +40,13 @@ namespace osu.Game.Tests.Visual
for (int i = 1; i <= 16; i *= 2)
{
- var newDisplay = new BeatmapWaveformGraph
+ var newDisplay = new WaveformGraph
{
RelativeSizeAxes = Axes.Both,
Resolution = 1f / i,
- Beatmap = Beatmap
};
- Beatmap.ValueChanged += b => newDisplay.Beatmap = b;
+ Beatmap.ValueChanged += b => newDisplay.Waveform = b.Waveform;
flow.Add(new Container
{
diff --git a/osu.Game.Tests/Visual/TestCaseZoomableScrollContainer.cs b/osu.Game.Tests/Visual/TestCaseZoomableScrollContainer.cs
new file mode 100644
index 0000000000..70dd67cdbd
--- /dev/null
+++ b/osu.Game.Tests/Visual/TestCaseZoomableScrollContainer.cs
@@ -0,0 +1,142 @@
+// Copyright (c) 2007-2018 ppy Pty Ltd .
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using NUnit.Framework;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Colour;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Primitives;
+using osu.Framework.Graphics.Shapes;
+using osu.Framework.MathUtils;
+using osu.Game.Graphics;
+using osu.Game.Graphics.Cursor;
+using osu.Game.Screens.Edit.Screens.Compose.Timeline;
+using OpenTK;
+using OpenTK.Graphics;
+using OpenTK.Input;
+
+namespace osu.Game.Tests.Visual
+{
+ public class TestCaseZoomableScrollContainer : ManualInputManagerTestCase
+ {
+ private readonly ZoomableScrollContainer scrollContainer;
+ private readonly Drawable innerBox;
+
+ public TestCaseZoomableScrollContainer()
+ {
+ Children = new Drawable[]
+ {
+ new Container
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ RelativeSizeAxes = Axes.X,
+ Height = 250,
+ Width = 0.75f,
+ Children = new Drawable[]
+ {
+ new Box
+ {
+ RelativeSizeAxes = Axes.Both,
+ Colour = OsuColour.Gray(30)
+ },
+ scrollContainer = new ZoomableScrollContainer { RelativeSizeAxes = Axes.Both }
+ }
+ },
+ new MenuCursor()
+ };
+
+ scrollContainer.Add(innerBox = new Box
+ {
+ RelativeSizeAxes = Axes.Both,
+ Colour = ColourInfo.GradientHorizontal(new Color4(0.8f, 0.6f, 0.4f, 1f), new Color4(0.4f, 0.6f, 0.8f, 1f))
+ });
+ }
+
+ [Test]
+ public void TestZoom0()
+ {
+ reset();
+ AddAssert("Box at 0", () => Precision.AlmostEquals(boxQuad.TopLeft, scrollQuad.TopLeft));
+ AddAssert("Box width = 1x", () => Precision.AlmostEquals(boxQuad.Size, scrollQuad.Size));
+ }
+
+ [Test]
+ public void TestZoom10()
+ {
+ reset();
+ AddStep("Set zoom = 10", () => scrollContainer.Zoom = 10);
+ AddAssert("Box at 1/2", () => Precision.AlmostEquals(boxQuad.Centre, scrollQuad.Centre));
+ AddAssert("Box width = 10x", () => Precision.AlmostEquals(boxQuad.Size.X, 10 * scrollQuad.Size.X));
+ }
+
+ [Test]
+ public void TestMouseZoomInOnceOutOnce()
+ {
+ reset();
+
+ // Scroll in at 0.25
+ AddStep("Move mouse to 0.25x", () => InputManager.MoveMouseTo(new Vector2(scrollQuad.TopLeft.X + 0.25f * scrollQuad.Size.X, scrollQuad.Centre.Y)));
+ AddStep("Press ctrl", () => InputManager.PressKey(Key.LControl));
+ AddStep("Scroll by 3", () => InputManager.ScrollBy(new Vector2(3, 0)));
+ AddStep("Release ctrl", () => InputManager.ReleaseKey(Key.LControl));
+ AddAssert("Box not at 0", () => !Precision.AlmostEquals(boxQuad.TopLeft, scrollQuad.TopLeft));
+ AddAssert("Box 1/4 at 1/4", () => Precision.AlmostEquals(boxQuad.TopLeft.X + 0.25f * boxQuad.Size.X, scrollQuad.TopLeft.X + 0.25f * scrollQuad.Size.X));
+
+ // Scroll out at 0.25
+ AddStep("Press ctrl", () => InputManager.PressKey(Key.LControl));
+ AddStep("Scroll by -3", () => InputManager.ScrollBy(new Vector2(-3, 0)));
+ AddStep("Release ctrl", () => InputManager.ReleaseKey(Key.LControl));
+ AddAssert("Box at 0", () => Precision.AlmostEquals(boxQuad.TopLeft, scrollQuad.TopLeft));
+ AddAssert("Box 1/4 at 1/4", () => Precision.AlmostEquals(boxQuad.TopLeft.X + 0.25f * boxQuad.Size.X, scrollQuad.TopLeft.X + 0.25f * scrollQuad.Size.X));
+ }
+
+ [Test]
+ public void TestMouseZoomInTwiceOutTwice()
+ {
+ reset();
+
+ // Scroll in at 0.25
+ AddStep("Move mouse to 0.25x", () => InputManager.MoveMouseTo(new Vector2(scrollQuad.TopLeft.X + 0.25f * scrollQuad.Size.X, scrollQuad.Centre.Y)));
+ AddStep("Press ctrl", () => InputManager.PressKey(Key.LControl));
+ AddStep("Scroll by 1", () => InputManager.ScrollBy(new Vector2(1, 0)));
+ AddStep("Release ctrl", () => InputManager.ReleaseKey(Key.LControl));
+
+ // Scroll in at 0.6
+ AddStep("Move mouse to 0.75x", () => InputManager.MoveMouseTo(new Vector2(scrollQuad.TopLeft.X + 0.75f * scrollQuad.Size.X, scrollQuad.Centre.Y)));
+ AddStep("Press ctrl", () => InputManager.PressKey(Key.LControl));
+ AddStep("Scroll by 1", () => InputManager.ScrollBy(new Vector2(1, 0)));
+ AddStep("Release ctrl", () => InputManager.ReleaseKey(Key.LControl));
+ AddAssert("Box not at 0", () => !Precision.AlmostEquals(boxQuad.TopLeft, scrollQuad.TopLeft));
+
+ // Very hard to determine actual position, so approximate
+ AddAssert("Box at correct position (1)", () => Precision.DefinitelyBigger(scrollQuad.TopLeft.X + 0.25f * scrollQuad.Size.X, boxQuad.TopLeft.X + 0.25f * boxQuad.Size.X));
+ AddAssert("Box at correct position (2)", () => Precision.DefinitelyBigger(scrollQuad.TopLeft.X + 0.6f * scrollQuad.Size.X, boxQuad.TopLeft.X + 0.3f * boxQuad.Size.X));
+ AddAssert("Box at correct position (3)", () => Precision.DefinitelyBigger(boxQuad.TopLeft.X + 0.6f * boxQuad.Size.X, scrollQuad.TopLeft.X + 0.6f * scrollQuad.Size.X));
+
+ // Scroll out at 0.6
+ AddStep("Press ctrl", () => InputManager.PressKey(Key.LControl));
+ AddStep("Scroll by -1", () => InputManager.ScrollBy(new Vector2(-1, 0)));
+ AddStep("Release ctrl", () => InputManager.ReleaseKey(Key.LControl));
+
+ // Scroll out at 0.25
+ AddStep("Move mouse to 0.25x", () => InputManager.MoveMouseTo(new Vector2(scrollQuad.TopLeft.X + 0.25f * scrollQuad.Size.X, scrollQuad.Centre.Y)));
+ AddStep("Press ctrl", () => InputManager.PressKey(Key.LControl));
+ AddStep("Scroll by -1", () => InputManager.ScrollBy(new Vector2(-1, 0)));
+ AddStep("Release ctrl", () => InputManager.ReleaseKey(Key.LControl));
+ AddAssert("Box at 0", () => Precision.AlmostEquals(boxQuad.TopLeft, scrollQuad.TopLeft));
+ }
+
+ private void reset()
+ {
+ AddStep("Reset", () =>
+ {
+ scrollContainer.Zoom = 0;
+ scrollContainer.ScrollTo(0, false);
+ });
+ }
+
+ private Quad scrollQuad => scrollContainer.ScreenSpaceDrawQuad;
+ private Quad boxQuad => innerBox.ScreenSpaceDrawQuad;
+ }
+}
diff --git a/osu.Game/Beatmaps/Drawables/BeatmapSetDownloader.cs b/osu.Game/Beatmaps/Drawables/BeatmapSetDownloader.cs
new file mode 100644
index 0000000000..6acb58e165
--- /dev/null
+++ b/osu.Game/Beatmaps/Drawables/BeatmapSetDownloader.cs
@@ -0,0 +1,84 @@
+// Copyright (c) 2007-2018 ppy Pty Ltd .
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using System.Linq;
+using osu.Framework.Allocation;
+using osu.Framework.Configuration;
+using osu.Framework.Graphics;
+
+namespace osu.Game.Beatmaps.Drawables
+{
+ ///
+ /// A component to allow downloading of a beatmap set. Automatically handles state syncing between other instances.
+ ///
+ public class BeatmapSetDownloader : Component
+ {
+ private readonly BeatmapSetInfo set;
+ private readonly bool noVideo;
+
+ private BeatmapManager beatmaps;
+
+ ///
+ /// Whether the associated beatmap set has been downloading (by this instance or any other instance).
+ ///
+ public readonly BindableBool Downloaded = new BindableBool();
+
+ public BeatmapSetDownloader(BeatmapSetInfo set, bool noVideo = false)
+ {
+ this.set = set;
+ this.noVideo = noVideo;
+ }
+
+ [BackgroundDependencyLoader]
+ private void load(BeatmapManager beatmaps)
+ {
+ this.beatmaps = beatmaps;
+
+ beatmaps.ItemAdded += setAdded;
+ beatmaps.ItemRemoved += setRemoved;
+
+ // initial value
+ if (set.OnlineBeatmapSetID != null)
+ Downloaded.Value = beatmaps.QueryBeatmapSets(s => s.OnlineBeatmapSetID == set.OnlineBeatmapSetID && !s.DeletePending).Any();
+ }
+
+ protected override void Dispose(bool isDisposing)
+ {
+ base.Dispose(isDisposing);
+
+ if (beatmaps != null)
+ {
+ beatmaps.ItemAdded -= setAdded;
+ beatmaps.ItemRemoved -= setRemoved;
+ }
+ }
+
+ ///
+ /// Begin downloading the associated beatmap set.
+ ///
+ /// True if downloading began. False if an existing download is active or completed.
+ public bool Download()
+ {
+ if (Downloaded.Value)
+ return false;
+
+ if (beatmaps.GetExistingDownload(set) != null)
+ return false;
+
+ beatmaps.Download(set, noVideo);
+ return true;
+ }
+
+ private void setAdded(BeatmapSetInfo s)
+ {
+ if (s.OnlineBeatmapSetID == set.OnlineBeatmapSetID)
+ Downloaded.Value = true;
+ }
+
+ private void setRemoved(BeatmapSetInfo s)
+ {
+ if (s.OnlineBeatmapSetID == set.OnlineBeatmapSetID)
+ Downloaded.Value = false;
+ }
+ }
+}
diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs
index 597960c352..f7fe424aa9 100644
--- a/osu.Game/Configuration/OsuConfigManager.cs
+++ b/osu.Game/Configuration/OsuConfigManager.cs
@@ -5,6 +5,7 @@ using osu.Framework.Configuration;
using osu.Framework.Configuration.Tracking;
using osu.Framework.Platform;
using osu.Game.Overlays;
+using osu.Game.Rulesets.Scoring;
using osu.Game.Screens.Select;
namespace osu.Game.Configuration
@@ -80,6 +81,8 @@ namespace osu.Game.Configuration
Set(OsuSetting.FloatingComments, false);
+ Set(OsuSetting.ScoreDisplayMode, ScoringMode.Standardised);
+
Set(OsuSetting.SpeedChangeVisualisation, SpeedChangeVisualisationMethod.Sequential);
Set(OsuSetting.IncreaseFirstObjectVisibility, true);
@@ -147,6 +150,7 @@ namespace osu.Game.Configuration
SongSelectRightMouseScroll,
BeatmapSkins,
BeatmapHitsounds,
- IncreaseFirstObjectVisibility
+ IncreaseFirstObjectVisibility,
+ ScoreDisplayMode
}
}
diff --git a/osu.Game/Overlays/BeatmapSet/Buttons/DownloadButton.cs b/osu.Game/Overlays/BeatmapSet/Buttons/DownloadButton.cs
index c699ae2328..4fce6a49fb 100644
--- a/osu.Game/Overlays/BeatmapSet/Buttons/DownloadButton.cs
+++ b/osu.Game/Overlays/BeatmapSet/Buttons/DownloadButton.cs
@@ -3,6 +3,8 @@
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
+using osu.Game.Beatmaps;
+using osu.Game.Beatmaps.Drawables;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using OpenTK;
@@ -11,10 +13,11 @@ namespace osu.Game.Overlays.BeatmapSet.Buttons
{
public class DownloadButton : HeaderButton
{
- public DownloadButton(string title, string subtitle)
+ public DownloadButton(BeatmapSetInfo set, bool noVideo = false)
{
Width = 120;
+ BeatmapSetDownloader downloader;
Add(new Container
{
Depth = -1,
@@ -22,6 +25,7 @@ namespace osu.Game.Overlays.BeatmapSet.Buttons
Padding = new MarginPadding { Horizontal = 10 },
Children = new Drawable[]
{
+ downloader = new BeatmapSetDownloader(set, noVideo),
new FillFlowContainer
{
Anchor = Anchor.CentreLeft,
@@ -32,13 +36,13 @@ namespace osu.Game.Overlays.BeatmapSet.Buttons
{
new OsuSpriteText
{
- Text = title,
+ Text = "Download",
TextSize = 13,
Font = @"Exo2.0-Bold",
},
new OsuSpriteText
{
- Text = subtitle,
+ Text = set.OnlineInfo.HasVideo && noVideo ? "without Video" : string.Empty,
TextSize = 11,
Font = @"Exo2.0-Bold",
},
@@ -54,6 +58,25 @@ namespace osu.Game.Overlays.BeatmapSet.Buttons
},
},
});
+
+ Action = () =>
+ {
+ if (!downloader.Download())
+ {
+ Content.MoveToX(-5, 50, Easing.OutSine).Then()
+ .MoveToX(5, 100, Easing.InOutSine).Then()
+ .MoveToX(-5, 100, Easing.InOutSine).Then()
+ .MoveToX(0, 50, Easing.InSine);
+ }
+ };
+
+ downloader.Downloaded.ValueChanged += d =>
+ {
+ if (d)
+ this.FadeOut(200);
+ else
+ this.FadeIn(200);
+ };
}
}
}
diff --git a/osu.Game/Overlays/BeatmapSet/Header.cs b/osu.Game/Overlays/BeatmapSet/Header.cs
index 89c141ef17..afba99f928 100644
--- a/osu.Game/Overlays/BeatmapSet/Header.cs
+++ b/osu.Game/Overlays/BeatmapSet/Header.cs
@@ -35,8 +35,6 @@ namespace osu.Game.Overlays.BeatmapSet
private readonly BeatmapSetOnlineStatusPill onlineStatusPill;
public Details Details;
- private BeatmapManager beatmaps;
-
public readonly BeatmapPicker Picker;
private BeatmapSetInfo beatmapSet;
@@ -68,8 +66,24 @@ namespace osu.Game.Overlays.BeatmapSet
downloadButtonsContainer.FadeIn(transition_duration);
favouriteButton.FadeIn(transition_duration);
- noVideoButtons.FadeTo(BeatmapSet.OnlineInfo.HasVideo ? 0 : 1, transition_duration);
- videoButtons.FadeTo(BeatmapSet.OnlineInfo.HasVideo ? 1 : 0, transition_duration);
+ if (BeatmapSet.OnlineInfo.HasVideo)
+ {
+ videoButtons.Children = new[]
+ {
+ new DownloadButton(BeatmapSet),
+ new DownloadButton(BeatmapSet, true),
+ };
+
+ videoButtons.FadeIn(transition_duration);
+ noVideoButtons.FadeOut(transition_duration);
+ }
+ else
+ {
+ noVideoButtons.Child = new DownloadButton(BeatmapSet);
+
+ noVideoButtons.FadeIn(transition_duration);
+ videoButtons.FadeOut(transition_duration);
+ }
}
else
{
@@ -192,27 +206,12 @@ namespace osu.Game.Overlays.BeatmapSet
{
RelativeSizeAxes = Axes.Both,
Alpha = 0f,
- Child = new DownloadButton("Download", @"")
- {
- Action = () => download(false),
- },
},
videoButtons = new FillFlowContainer
{
RelativeSizeAxes = Axes.Both,
Spacing = new Vector2(buttons_spacing),
Alpha = 0f,
- Children = new[]
- {
- new DownloadButton("Download", "with Video")
- {
- Action = () => download(false),
- },
- new DownloadButton("Download", "without Video")
- {
- Action = () => download(true),
- },
- },
},
},
},
@@ -248,41 +247,10 @@ namespace osu.Game.Overlays.BeatmapSet
}
[BackgroundDependencyLoader]
- private void load(OsuColour colours, BeatmapManager beatmaps)
+ private void load(OsuColour colours)
{
tabsBg.Colour = colours.Gray3;
- this.beatmaps = beatmaps;
-
- beatmaps.ItemAdded += handleBeatmapAdd;
-
updateDisplay();
}
-
- protected override void Dispose(bool isDisposing)
- {
- base.Dispose(isDisposing);
- if (beatmaps != null) beatmaps.ItemAdded -= handleBeatmapAdd;
- }
-
- private void handleBeatmapAdd(BeatmapSetInfo beatmap) => Schedule(() =>
- {
- if (beatmap.OnlineBeatmapSetID == BeatmapSet?.OnlineBeatmapSetID)
- downloadButtonsContainer.FadeOut(transition_duration);
- });
-
- private void download(bool noVideo)
- {
- if (beatmaps.GetExistingDownload(BeatmapSet) != null)
- {
- downloadButtonsContainer.MoveToX(-5, 50, Easing.OutSine).Then()
- .MoveToX(5, 100, Easing.InOutSine).Then()
- .MoveToX(-5, 100, Easing.InOutSine).Then()
- .MoveToX(0, 50, Easing.InSine).Then();
-
- return;
- }
-
- beatmaps.Download(BeatmapSet, noVideo);
- }
}
}
diff --git a/osu.Game/Overlays/Direct/DirectGridPanel.cs b/osu.Game/Overlays/Direct/DirectGridPanel.cs
index ed4630a8e7..723e9e8b35 100644
--- a/osu.Game/Overlays/Direct/DirectGridPanel.cs
+++ b/osu.Game/Overlays/Direct/DirectGridPanel.cs
@@ -166,14 +166,13 @@ namespace osu.Game.Overlays.Direct
},
},
},
- new DownloadButton
+ new DownloadButton(SetInfo)
{
Size = new Vector2(30),
Margin = new MarginPadding(horizontal_padding),
Anchor = Anchor.CentreRight,
Origin = Anchor.CentreRight,
Colour = colours.Gray5,
- Action = StartDownload
},
},
},
diff --git a/osu.Game/Overlays/Direct/DirectListPanel.cs b/osu.Game/Overlays/Direct/DirectListPanel.cs
index 13398a4a32..6e3483604b 100644
--- a/osu.Game/Overlays/Direct/DirectListPanel.cs
+++ b/osu.Game/Overlays/Direct/DirectListPanel.cs
@@ -12,28 +12,31 @@ using osu.Game.Graphics.Sprites;
using osu.Framework.Allocation;
using osu.Framework.Localisation;
using osu.Framework.Graphics.Shapes;
+using osu.Framework.Input;
using osu.Game.Beatmaps;
namespace osu.Game.Overlays.Direct
{
public class DirectListPanel : DirectPanel
{
+ private const float transition_duration = 120;
private const float horizontal_padding = 10;
private const float vertical_padding = 5;
private const float height = 70;
+ private PlayButton playButton;
+ private Box progressBar;
+ private Container downloadContainer;
+
+ protected override PlayButton PlayButton => playButton;
+ protected override Box PreviewBar => progressBar;
+
public DirectListPanel(BeatmapSetInfo beatmap) : base(beatmap)
{
RelativeSizeAxes = Axes.X;
Height = height;
}
- private PlayButton playButton;
- private Box progressBar;
-
- protected override PlayButton PlayButton => playButton;
- protected override Box PreviewBar => progressBar;
-
[BackgroundDependencyLoader]
private void load(LocalisationEngine localisation, OsuColour colours)
{
@@ -59,7 +62,7 @@ namespace osu.Game.Overlays.Direct
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Horizontal,
LayoutEasing = Easing.OutQuint,
- LayoutDuration = 120,
+ LayoutDuration = transition_duration,
Spacing = new Vector2(10, 0),
Children = new Drawable[]
{
@@ -104,53 +107,69 @@ namespace osu.Game.Overlays.Direct
Anchor = Anchor.TopRight,
Origin = Anchor.TopRight,
AutoSizeAxes = Axes.Both,
- Direction = FillDirection.Vertical,
- Margin = new MarginPadding { Right = height - vertical_padding * 2 + vertical_padding },
+ Direction = FillDirection.Horizontal,
+ LayoutEasing = Easing.OutQuint,
+ LayoutDuration = transition_duration,
Children = new Drawable[]
{
- new Statistic(FontAwesome.fa_play_circle, SetInfo.OnlineInfo?.PlayCount ?? 0)
+ downloadContainer = new Container
{
- Margin = new MarginPadding { Right = 1 },
+ Anchor = Anchor.TopRight,
+ Origin = Anchor.TopRight,
+ AutoSizeAxes = Axes.Both,
+ Alpha = 0,
+ Child = new DownloadButton(SetInfo)
+ {
+ Size = new Vector2(height - vertical_padding * 2),
+ Margin = new MarginPadding { Left = vertical_padding },
+ },
},
- new Statistic(FontAwesome.fa_heart, SetInfo.OnlineInfo?.FavouriteCount ?? 0),
new FillFlowContainer
{
Anchor = Anchor.TopRight,
Origin = Anchor.TopRight,
AutoSizeAxes = Axes.Both,
- Direction = FillDirection.Horizontal,
- Children = new[]
+ Direction = FillDirection.Vertical,
+ Children = new Drawable[]
{
- new OsuSpriteText
+ new Statistic(FontAwesome.fa_play_circle, SetInfo.OnlineInfo?.PlayCount ?? 0)
{
- Text = "mapped by ",
- TextSize = 14,
+ Margin = new MarginPadding { Right = 1 },
+ },
+ new Statistic(FontAwesome.fa_heart, SetInfo.OnlineInfo?.FavouriteCount ?? 0),
+ new FillFlowContainer
+ {
+ Anchor = Anchor.TopRight,
+ Origin = Anchor.TopRight,
+ AutoSizeAxes = Axes.Both,
+ Direction = FillDirection.Horizontal,
+ Children = new[]
+ {
+ new OsuSpriteText
+ {
+ Text = "mapped by ",
+ TextSize = 14,
+ },
+ new OsuSpriteText
+ {
+ Text = SetInfo.Metadata.Author.Username,
+ TextSize = 14,
+ Font = @"Exo2.0-SemiBoldItalic",
+ },
+ },
},
new OsuSpriteText
{
- Text = SetInfo.Metadata.Author.Username,
+ Text = $"from {SetInfo.Metadata.Source}",
+ Anchor = Anchor.TopRight,
+ Origin = Anchor.TopRight,
TextSize = 14,
- Font = @"Exo2.0-SemiBoldItalic",
+ Alpha = string.IsNullOrEmpty(SetInfo.Metadata.Source) ? 0f : 1f,
},
},
},
- new OsuSpriteText
- {
- Text = $"from {SetInfo.Metadata.Source}",
- Anchor = Anchor.TopRight,
- Origin = Anchor.TopRight,
- TextSize = 14,
- Alpha = string.IsNullOrEmpty(SetInfo.Metadata.Source) ? 0f : 1f,
- },
},
},
- new DownloadButton
- {
- Anchor = Anchor.TopRight,
- Origin = Anchor.TopRight,
- Size = new Vector2(height - vertical_padding * 2),
- Action = StartDownload
- },
},
},
progressBar = new Box
@@ -165,5 +184,17 @@ namespace osu.Game.Overlays.Direct
},
});
}
+
+ protected override bool OnHover(InputState state)
+ {
+ downloadContainer.FadeIn(transition_duration, Easing.InOutQuint);
+ return base.OnHover(state);
+ }
+
+ protected override void OnHoverLost(InputState state)
+ {
+ downloadContainer.FadeOut(transition_duration, Easing.InOutQuint);
+ base.OnHoverLost(state);
+ }
}
}
diff --git a/osu.Game/Overlays/Direct/DirectPanel.cs b/osu.Game/Overlays/Direct/DirectPanel.cs
index df784252ce..e767f6ec83 100644
--- a/osu.Game/Overlays/Direct/DirectPanel.cs
+++ b/osu.Game/Overlays/Direct/DirectPanel.cs
@@ -147,22 +147,6 @@ namespace osu.Game.Overlays.Direct
protected void ShowInformation() => beatmapSetOverlay?.ShowBeatmapSet(SetInfo);
- protected void StartDownload()
- {
- if (beatmaps.GetExistingDownload(SetInfo) != null)
- {
- // we already have an active download running.
- content.MoveToX(-5, 50, Easing.OutSine).Then()
- .MoveToX(5, 100, Easing.InOutSine).Then()
- .MoveToX(-5, 100, Easing.InOutSine).Then()
- .MoveToX(0, 50, Easing.InSine).Then();
-
- return;
- }
-
- beatmaps.Download(SetInfo);
- }
-
private void attachDownload(DownloadBeatmapSetRequest request)
{
if (request.BeatmapSet.OnlineBeatmapSetID != SetInfo.OnlineBeatmapSetID)
diff --git a/osu.Game/Overlays/Direct/DownloadButton.cs b/osu.Game/Overlays/Direct/DownloadButton.cs
index f01c9dac59..1ffa8dbd35 100644
--- a/osu.Game/Overlays/Direct/DownloadButton.cs
+++ b/osu.Game/Overlays/Direct/DownloadButton.cs
@@ -3,6 +3,8 @@
using osu.Framework.Graphics;
using osu.Framework.Input;
+using osu.Game.Beatmaps;
+using osu.Game.Beatmaps.Drawables;
using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
using OpenTK;
@@ -13,10 +15,12 @@ namespace osu.Game.Overlays.Direct
{
private readonly SpriteIcon icon;
- public DownloadButton()
+ public DownloadButton(BeatmapSetInfo set, bool noVideo = false)
{
+ BeatmapSetDownloader downloader;
Children = new Drawable[]
{
+ downloader = new BeatmapSetDownloader(set, noVideo),
icon = new SpriteIcon
{
Anchor = Anchor.Centre,
@@ -25,6 +29,25 @@ namespace osu.Game.Overlays.Direct
Icon = FontAwesome.fa_osu_chevron_down_o,
},
};
+
+ Action = () =>
+ {
+ if (!downloader.Download())
+ {
+ Content.MoveToX(-5, 50, Easing.OutSine).Then()
+ .MoveToX(5, 100, Easing.InOutSine).Then()
+ .MoveToX(-5, 100, Easing.InOutSine).Then()
+ .MoveToX(0, 50, Easing.InSine);
+ }
+ };
+
+ downloader.Downloaded.ValueChanged += d =>
+ {
+ if (d)
+ this.FadeOut(200);
+ else
+ this.FadeIn(200);
+ };
}
protected override bool OnMouseDown(InputState state, MouseDownEventArgs args)
diff --git a/osu.Game/Overlays/DirectOverlay.cs b/osu.Game/Overlays/DirectOverlay.cs
index f437546888..b33f271986 100644
--- a/osu.Game/Overlays/DirectOverlay.cs
+++ b/osu.Game/Overlays/DirectOverlay.cs
@@ -28,7 +28,6 @@ namespace osu.Game.Overlays
private APIAccess api;
private RulesetStore rulesets;
- private BeatmapManager beatmaps;
private readonly FillFlowContainer resultCountsContainer;
private readonly OsuSpriteText resultCountsText;
@@ -177,24 +176,14 @@ namespace osu.Game.Overlays
}
[BackgroundDependencyLoader]
- private void load(OsuColour colours, APIAccess api, RulesetStore rulesets, BeatmapManager beatmaps)
+ private void load(OsuColour colours, APIAccess api, RulesetStore rulesets)
{
this.api = api;
this.rulesets = rulesets;
- this.beatmaps = beatmaps;
resultCountsContainer.Colour = colours.Yellow;
-
- beatmaps.ItemAdded += setAdded;
}
- private void setAdded(BeatmapSetInfo set) => Schedule(() =>
- {
- // if a new map was imported, we should remove it from search results (download completed etc.)
- panels?.FirstOrDefault(p => p.SetInfo.OnlineBeatmapSetID == set.OnlineBeatmapSetID)?.FadeOut(400).Expire();
- BeatmapSets = BeatmapSets?.Where(b => b.OnlineBeatmapSetID != set.OnlineBeatmapSetID);
- });
-
private void updateResultCounts()
{
resultCountsContainer.FadeTo(ResultAmounts == null ? 0f : 1f, 200, Easing.OutQuint);
@@ -297,9 +286,7 @@ namespace osu.Game.Overlays
{
Task.Run(() =>
{
- var onlineIds = response.Select(r => r.OnlineBeatmapSetID).ToList();
- var presentOnlineIds = beatmaps.QueryBeatmapSets(s => onlineIds.Contains(s.OnlineBeatmapSetID) && !s.DeletePending).Select(r => r.OnlineBeatmapSetID).ToList();
- var sets = response.Select(r => r.ToBeatmapSet(rulesets)).Where(b => !presentOnlineIds.Contains(b.OnlineBeatmapSetID)).ToList();
+ var sets = response.Select(r => r.ToBeatmapSet(rulesets)).ToList();
// may not need scheduling; loads async internally.
Schedule(() =>
@@ -323,14 +310,6 @@ namespace osu.Game.Overlays
private int distinctCount(List list) => list.Distinct().ToArray().Length;
- protected override void Dispose(bool isDisposing)
- {
- base.Dispose(isDisposing);
-
- if (beatmaps != null)
- beatmaps.ItemAdded -= setAdded;
- }
-
public class ResultCounts
{
public readonly int Artists;
diff --git a/osu.Game/Overlays/Profile/Header/BadgeContainer.cs b/osu.Game/Overlays/Profile/Header/BadgeContainer.cs
index 36a9a9b01a..f968f94187 100644
--- a/osu.Game/Overlays/Profile/Header/BadgeContainer.cs
+++ b/osu.Game/Overlays/Profile/Header/BadgeContainer.cs
@@ -107,13 +107,20 @@ namespace osu.Game.Overlays.Profile.Header
visibleBadge = 0;
badgeFlowContainer.Clear();
- foreach (var badge in badges)
+ for (var index = 0; index < badges.Length; index++)
{
- LoadComponentAsync(new DrawableBadge(badge)
+ int displayIndex = index;
+ LoadComponentAsync(new DrawableBadge(badges[index])
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
- }, badgeFlowContainer.Add);
+ }, asyncBadge =>
+ {
+ badgeFlowContainer.Add(asyncBadge);
+
+ // load in stable order regardless of async load order.
+ badgeFlowContainer.SetLayoutPosition(asyncBadge, displayIndex);
+ });
}
}
diff --git a/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs b/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs
index 647395cf69..9c8f5e2643 100644
--- a/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs
+++ b/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs
@@ -4,6 +4,7 @@
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Game.Configuration;
+using osu.Game.Rulesets.Scoring;
namespace osu.Game.Overlays.Settings.Sections.Gameplay
{
@@ -38,6 +39,11 @@ namespace osu.Game.Overlays.Settings.Sections.Gameplay
LabelText = "Always show key overlay",
Bindable = config.GetBindable(OsuSetting.KeyOverlay)
},
+ new SettingsEnumDropdown
+ {
+ LabelText = "Score display mode",
+ Bindable = config.GetBindable(OsuSetting.ScoreDisplayMode)
+ }
};
}
}
diff --git a/osu.Game/Overlays/Volume/VolumeMeter.cs b/osu.Game/Overlays/Volume/VolumeMeter.cs
index 64106967f4..b2cf43704b 100644
--- a/osu.Game/Overlays/Volume/VolumeMeter.cs
+++ b/osu.Game/Overlays/Volume/VolumeMeter.cs
@@ -167,9 +167,25 @@ namespace osu.Game.Overlays.Volume
private set => Bindable.Value = value;
}
- public void Increase() => Volume += 0.05f;
+ private const float adjust_step = 0.05f;
- public void Decrease() => Volume -= 0.05f;
+ public void Increase() => adjust(1);
+ public void Decrease() => adjust(-1);
+
+ private void adjust(int direction)
+ {
+ float amount = adjust_step * direction;
+
+ var mouse = GetContainingInputManager().CurrentState.Mouse;
+ if (mouse.HasPreciseScroll)
+ {
+ float scrollDelta = mouse.ScrollDelta.Y;
+ if (scrollDelta != 0)
+ amount *= Math.Abs(scrollDelta / 10);
+ }
+
+ Volume += amount;
+ }
public bool OnPressed(GlobalAction action)
{
diff --git a/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs b/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs
index 070bc7ddb0..31cd9dc6f5 100644
--- a/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs
+++ b/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs
@@ -1,6 +1,7 @@
// Copyright (c) 2007-2018 ppy Pty Ltd .
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+using System;
using System.Collections.Generic;
using System.Linq;
using osu.Framework.Extensions.IEnumerableExtensions;
@@ -36,6 +37,44 @@ namespace osu.Game.Rulesets.Difficulty
{
}
+ ///
+ /// Creates all combinations which adjust the difficulty.
+ ///
+ public Mod[] CreateDifficultyAdjustmentModCombinations()
+ {
+ return createDifficultyAdjustmentModCombinations(Enumerable.Empty(), DifficultyAdjustmentMods).ToArray();
+
+ IEnumerable createDifficultyAdjustmentModCombinations(IEnumerable currentSet, Mod[] adjustmentSet, int currentSetCount = 0, int adjustmentSetStart = 0)
+ {
+ // Initial-case: Empty current set
+ if (currentSetCount == 0)
+ yield return new NoModMod();
+
+ if (currentSetCount == 1)
+ yield return currentSet.Single();
+
+ if (currentSetCount > 1)
+ yield return new MultiMod(currentSet.ToArray());
+
+ // Apply mods in the adjustment set recursively. Using the entire adjustment set would result in duplicate multi-mod mod
+ // combinations in further recursions, so a moving subset is used to eliminate this effect
+ for (int i = adjustmentSetStart; i < adjustmentSet.Length; i++)
+ {
+ var adjustmentMod = adjustmentSet[i];
+ if (currentSet.Any(c => c.IncompatibleMods.Any(m => m.IsInstanceOfType(adjustmentMod))))
+ continue;
+
+ foreach (var combo in createDifficultyAdjustmentModCombinations(currentSet.Append(adjustmentMod), adjustmentSet, currentSetCount + 1, i + 1))
+ yield return combo;
+ }
+ }
+ }
+
+ ///
+ /// Retrieves all s which adjust the difficulty.
+ ///
+ protected virtual Mod[] DifficultyAdjustmentMods => Array.Empty();
+
public abstract double Calculate(Dictionary categoryDifficulty = null);
}
}
diff --git a/osu.Game/Rulesets/Mods/ModHidden.cs b/osu.Game/Rulesets/Mods/ModHidden.cs
index 45da628ce8..9b09f0bd6d 100644
--- a/osu.Game/Rulesets/Mods/ModHidden.cs
+++ b/osu.Game/Rulesets/Mods/ModHidden.cs
@@ -27,8 +27,7 @@ namespace osu.Game.Rulesets.Mods
public virtual void ApplyToDrawableHitObjects(IEnumerable drawables)
{
- // todo: fix ordering of objects so we don't have to do this (#2740).
- foreach (var d in drawables.Reverse().Skip(IncreaseFirstObjectVisibility ? 1 : 0))
+ foreach (var d in drawables.Skip(IncreaseFirstObjectVisibility ? 1 : 0))
d.ApplyCustomUpdateState += ApplyHiddenState;
}
diff --git a/osu.Game/Rulesets/Mods/ModType.cs b/osu.Game/Rulesets/Mods/ModType.cs
index 1941724879..913ba23701 100644
--- a/osu.Game/Rulesets/Mods/ModType.cs
+++ b/osu.Game/Rulesets/Mods/ModType.cs
@@ -7,6 +7,6 @@ namespace osu.Game.Rulesets.Mods
{
DifficultyReduction,
DifficultyIncrease,
- Special,
+ Special
}
}
diff --git a/osu.Game/Rulesets/Mods/MultiMod.cs b/osu.Game/Rulesets/Mods/MultiMod.cs
index 3c90a4eedb..b65773e93f 100644
--- a/osu.Game/Rulesets/Mods/MultiMod.cs
+++ b/osu.Game/Rulesets/Mods/MultiMod.cs
@@ -1,6 +1,9 @@
// Copyright (c) 2007-2018 ppy Pty Ltd .
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+using System;
+using System.Linq;
+
namespace osu.Game.Rulesets.Mods
{
public class MultiMod : Mod
@@ -10,6 +13,13 @@ namespace osu.Game.Rulesets.Mods
public override string Description => string.Empty;
public override double ScoreMultiplier => 0;
- public Mod[] Mods;
+ public Mod[] Mods { get; }
+
+ public MultiMod(params Mod[] mods)
+ {
+ Mods = mods;
+ }
+
+ public override Type[] IncompatibleMods => Mods.SelectMany(m => m.IncompatibleMods).ToArray();
}
}
diff --git a/osu.Game/Rulesets/Mods/NoModMod.cs b/osu.Game/Rulesets/Mods/NoModMod.cs
new file mode 100644
index 0000000000..dcab3538da
--- /dev/null
+++ b/osu.Game/Rulesets/Mods/NoModMod.cs
@@ -0,0 +1,15 @@
+// Copyright (c) 2007-2018 ppy Pty Ltd .
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+namespace osu.Game.Rulesets.Mods
+{
+ ///
+ /// Indicates a type of mod that doesn't do anything.
+ ///
+ public sealed class NoModMod : Mod
+ {
+ public override string Name => "No Mod";
+ public override string ShortenedName => "NM";
+ public override double ScoreMultiplier => 1;
+ }
+}
diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs
index 345930ed04..dd4120f2fb 100644
--- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs
+++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs
@@ -65,6 +65,11 @@ namespace osu.Game.Rulesets.Scoring
///
public readonly BindableInt HighestCombo = new BindableInt();
+ ///
+ /// The used to calculate scores.
+ ///
+ public readonly Bindable Mode = new Bindable();
+
///
/// Whether all s have been processed.
///
@@ -169,8 +174,6 @@ namespace osu.Game.Rulesets.Scoring
private const double combo_portion = 0.7;
private const double max_score = 1000000;
- public readonly Bindable Mode = new Bindable();
-
protected sealed override bool HasCompleted => JudgedHits == MaxHits;
protected int MaxHits { get; private set; }
@@ -199,16 +202,18 @@ namespace osu.Game.Rulesets.Scoring
if (maxBaseScore == 0 || maxHighestCombo == 0)
{
- Mode.Value = ScoringMode.Exponential;
+ Mode.Value = ScoringMode.Classic;
Mode.Disabled = true;
}
+
+ Mode.ValueChanged += _ => updateScore();
}
///
/// Simulates an autoplay of s that will be judged by this
/// by adding s for each in the .
///
- /// This is required for to work, otherwise will be used.
+ /// This is required for to work, otherwise will be used.
///
///
/// The containing the s that will be judged by this .
@@ -295,8 +300,9 @@ namespace osu.Game.Rulesets.Scoring
case ScoringMode.Standardised:
TotalScore.Value = max_score * (base_portion * baseScore / maxBaseScore + combo_portion * HighestCombo / maxHighestCombo) + bonusScore;
break;
- case ScoringMode.Exponential:
- TotalScore.Value = (baseScore + bonusScore) * Math.Log(HighestCombo + 1, 2);
+ case ScoringMode.Classic:
+ // should emulate osu-stable's scoring as closely as we can (https://osu.ppy.sh/help/wiki/Score/ScoreV1)
+ TotalScore.Value = bonusScore + baseScore * (1 + Math.Max(0, HighestCombo - 1) / 25);
break;
}
}
@@ -322,6 +328,6 @@ namespace osu.Game.Rulesets.Scoring
public enum ScoringMode
{
Standardised,
- Exponential
+ Classic
}
}
diff --git a/osu.Game/Rulesets/UI/HitObjectContainer.cs b/osu.Game/Rulesets/UI/HitObjectContainer.cs
index 1b6841c9bd..af18d98561 100644
--- a/osu.Game/Rulesets/UI/HitObjectContainer.cs
+++ b/osu.Game/Rulesets/UI/HitObjectContainer.cs
@@ -11,8 +11,8 @@ namespace osu.Game.Rulesets.UI
{
public class HitObjectContainer : CompositeDrawable
{
- public virtual IEnumerable Objects => InternalChildren.Cast();
- public virtual IEnumerable AliveObjects => AliveInternalChildren.Cast();
+ public IEnumerable Objects => InternalChildren.Cast().OrderBy(h => h.HitObject.StartTime);
+ public IEnumerable AliveObjects => AliveInternalChildren.Cast().OrderBy(h => h.HitObject.StartTime);
public virtual void Add(DrawableHitObject hitObject) => AddInternal(hitObject);
public virtual bool Remove(DrawableHitObject hitObject) => RemoveInternal(hitObject);
diff --git a/osu.Game/Rulesets/UI/RulesetInputManager.cs b/osu.Game/Rulesets/UI/RulesetInputManager.cs
index b35616985a..58a66a5224 100644
--- a/osu.Game/Rulesets/UI/RulesetInputManager.cs
+++ b/osu.Game/Rulesets/UI/RulesetInputManager.cs
@@ -121,6 +121,8 @@ namespace osu.Game.Rulesets.UI
///
private bool validState;
+ protected override bool RequiresChildrenUpdate => base.RequiresChildrenUpdate && validState;
+
private bool isAttached => replayInputHandler != null && !UseParentState;
private const int max_catch_up_updates_per_frame = 50;
diff --git a/osu.Game/Screens/Edit/Screens/Compose/Compose.cs b/osu.Game/Screens/Edit/Screens/Compose/Compose.cs
index b991f7c2c0..a862485fd6 100644
--- a/osu.Game/Screens/Edit/Screens/Compose/Compose.cs
+++ b/osu.Game/Screens/Edit/Screens/Compose/Compose.cs
@@ -64,7 +64,7 @@ namespace osu.Game.Screens.Edit.Screens.Compose
{
RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding { Right = 5 },
- Child = new ScrollableTimeline { RelativeSizeAxes = Axes.Both }
+ Child = new TimelineArea { RelativeSizeAxes = Axes.Both }
},
new BeatDivisorControl(beatDivisor) { RelativeSizeAxes = Axes.Both }
},
diff --git a/osu.Game/Screens/Edit/Screens/Compose/Timeline/BeatmapWaveformGraph.cs b/osu.Game/Screens/Edit/Screens/Compose/Timeline/BeatmapWaveformGraph.cs
deleted file mode 100644
index 136ceb47c4..0000000000
--- a/osu.Game/Screens/Edit/Screens/Compose/Timeline/BeatmapWaveformGraph.cs
+++ /dev/null
@@ -1,31 +0,0 @@
-// Copyright (c) 2007-2018 ppy Pty Ltd .
-// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
-
-using osu.Framework.Graphics;
-using osu.Framework.Graphics.Audio;
-using osu.Framework.Graphics.Containers;
-using osu.Game.Beatmaps;
-
-namespace osu.Game.Screens.Edit.Screens.Compose.Timeline
-{
- public class BeatmapWaveformGraph : CompositeDrawable
- {
- public WorkingBeatmap Beatmap { set => graph.Waveform = value.Waveform; }
-
- private readonly WaveformGraph graph;
-
- public BeatmapWaveformGraph()
- {
- InternalChild = graph = new WaveformGraph { RelativeSizeAxes = Axes.Both };
- }
-
- ///
- /// Gets or sets the .
- ///
- public float Resolution
- {
- get { return graph.Resolution; }
- set { graph.Resolution = value; }
- }
- }
-}
diff --git a/osu.Game/Screens/Edit/Screens/Compose/Timeline/ScrollableTimeline.cs b/osu.Game/Screens/Edit/Screens/Compose/Timeline/ScrollableTimeline.cs
deleted file mode 100644
index 3bebf78b17..0000000000
--- a/osu.Game/Screens/Edit/Screens/Compose/Timeline/ScrollableTimeline.cs
+++ /dev/null
@@ -1,126 +0,0 @@
-// Copyright (c) 2007-2018 ppy Pty Ltd .
-// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
-
-using OpenTK;
-using osu.Framework.Graphics;
-using osu.Framework.Graphics.Containers;
-using osu.Framework.Graphics.Shapes;
-using osu.Game.Graphics;
-using osu.Game.Graphics.UserInterface;
-
-namespace osu.Game.Screens.Edit.Screens.Compose.Timeline
-{
- public class ScrollableTimeline : CompositeDrawable
- {
- private readonly ScrollingTimelineContainer timelineContainer;
-
- public ScrollableTimeline()
- {
- Masking = true;
- CornerRadius = 5;
-
- OsuCheckbox hitObjectsCheckbox;
- OsuCheckbox hitSoundsCheckbox;
- OsuCheckbox waveformCheckbox;
- InternalChildren = new Drawable[]
- {
- new Box
- {
- RelativeSizeAxes = Axes.Both,
- Colour = OsuColour.FromHex("111")
- },
- new FillFlowContainer
- {
- RelativeSizeAxes = Axes.Both,
- Direction = FillDirection.Horizontal,
- Children = new Drawable[]
- {
- new Container
- {
- AutoSizeAxes = Axes.X,
- RelativeSizeAxes = Axes.Y,
- Children = new Drawable[]
- {
- new Box
- {
- RelativeSizeAxes = Axes.Both,
- Colour = OsuColour.FromHex("222")
- },
- new FillFlowContainer
- {
- Anchor = Anchor.CentreLeft,
- Origin = Anchor.CentreLeft,
- AutoSizeAxes = Axes.Y,
- Width = 160,
- Padding = new MarginPadding { Horizontal = 15 },
- Direction = FillDirection.Vertical,
- Spacing = new Vector2(0, 4),
- Children = new[]
- {
- hitObjectsCheckbox = new OsuCheckbox { LabelText = "Hitobjects" },
- hitSoundsCheckbox = new OsuCheckbox { LabelText = "Hitsounds" },
- waveformCheckbox = new OsuCheckbox { LabelText = "Waveform" }
- }
- }
- }
- },
- new Container
- {
- AutoSizeAxes = Axes.X,
- RelativeSizeAxes = Axes.Y,
- Children = new Drawable[]
- {
- new Box
- {
- RelativeSizeAxes = Axes.Both,
- Colour = OsuColour.FromHex("333")
- },
- new Container
- {
- Anchor = Anchor.CentreLeft,
- Origin = Anchor.CentreLeft,
- RelativeSizeAxes = Axes.Y,
- AutoSizeAxes = Axes.X,
- Masking = true,
- Children = new[]
- {
- new TimelineButton
- {
- RelativeSizeAxes = Axes.Y,
- Height = 0.5f,
- Icon = FontAwesome.fa_search_plus,
- Action = () => timelineContainer.Zoom++
- },
- new TimelineButton
- {
- Anchor = Anchor.BottomLeft,
- Origin = Anchor.BottomLeft,
- RelativeSizeAxes = Axes.Y,
- Height = 0.5f,
- Icon = FontAwesome.fa_search_minus,
- Action = () => timelineContainer.Zoom--
- },
- }
- }
- }
- },
- timelineContainer = new ScrollingTimelineContainer { RelativeSizeAxes = Axes.Y }
- }
- }
- };
-
- hitObjectsCheckbox.Current.Value = true;
- hitSoundsCheckbox.Current.Value = true;
- waveformCheckbox.Current.Value = true;
-
- timelineContainer.WaveformVisible.BindTo(waveformCheckbox.Current);
- }
-
- protected override void Update()
- {
- base.Update();
-
- timelineContainer.Size = new Vector2(DrawSize.X - timelineContainer.DrawPosition.X, 1);
- }
- }
-}
diff --git a/osu.Game/Screens/Edit/Screens/Compose/Timeline/ScrollingTimelineContainer.cs b/osu.Game/Screens/Edit/Screens/Compose/Timeline/ScrollingTimelineContainer.cs
deleted file mode 100644
index cc51ae1096..0000000000
--- a/osu.Game/Screens/Edit/Screens/Compose/Timeline/ScrollingTimelineContainer.cs
+++ /dev/null
@@ -1,151 +0,0 @@
-// Copyright (c) 2007-2018 ppy Pty Ltd .
-// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
-
-using System;
-using osu.Framework.Allocation;
-using OpenTK;
-using osu.Framework.Configuration;
-using osu.Framework.Graphics;
-using osu.Framework.Graphics.Containers;
-using osu.Framework.Input;
-using osu.Game.Beatmaps;
-using osu.Game.Graphics;
-
-namespace osu.Game.Screens.Edit.Screens.Compose.Timeline
-{
- public class ScrollingTimelineContainer : ScrollContainer
- {
- public readonly Bindable HitObjectsVisible = new Bindable();
- public readonly Bindable HitSoundsVisible = new Bindable();
- public readonly Bindable WaveformVisible = new Bindable();
-
- private readonly IBindable beatmap = new Bindable();
-
- private readonly BeatmapWaveformGraph waveform;
-
- public ScrollingTimelineContainer()
- : base(Direction.Horizontal)
- {
- Masking = true;
-
- Add(waveform = new BeatmapWaveformGraph
- {
- RelativeSizeAxes = Axes.Both,
- Colour = OsuColour.FromHex("222"),
- Depth = float.MaxValue
- });
-
- Content.AutoSizeAxes = Axes.None;
- Content.RelativeSizeAxes = Axes.Both;
-
- WaveformVisible.ValueChanged += waveformVisibilityChanged;
-
- Zoom = 10;
- }
-
- [BackgroundDependencyLoader]
- private void load(IBindableBeatmap beatmap)
- {
- this.beatmap.BindTo(beatmap);
- this.beatmap.BindValueChanged(beatmapChanged, true);
- }
-
- private void beatmapChanged(WorkingBeatmap beatmap) => waveform.Beatmap = beatmap;
-
- private float minZoom = 1;
- ///
- /// The minimum zoom level allowed.
- ///
- public float MinZoom
- {
- get { return minZoom; }
- set
- {
- if (value <= 0)
- throw new ArgumentOutOfRangeException(nameof(value));
- if (minZoom == value)
- return;
- minZoom = value;
-
- // Update the zoom level
- Zoom = Zoom;
- }
- }
-
- private float maxZoom = 30;
- ///
- /// The maximum zoom level allowed.
- ///
- public float MaxZoom
- {
- get { return maxZoom; }
- set
- {
- if (value <= 0)
- throw new ArgumentOutOfRangeException(nameof(value));
- if (maxZoom == value)
- return;
- maxZoom = value;
-
- // Update the zoom level
- Zoom = Zoom;
- }
- }
-
- private float zoom = 1;
- ///
- /// The current zoom level.
- ///
- public float Zoom
- {
- get { return zoom; }
- set
- {
- value = MathHelper.Clamp(value, MinZoom, MaxZoom);
- if (zoom == value)
- return;
- zoom = value;
-
- // Make the zoom target default to the center of the graph if it hasn't been set
- if (relativeContentZoomTarget == null)
- relativeContentZoomTarget = ToSpaceOfOtherDrawable(DrawSize / 2, Content).X / Content.DrawSize.X;
- if (localZoomTarget == null)
- localZoomTarget = DrawSize.X / 2;
-
- Content.ResizeWidthTo(Zoom);
-
- // Update the scroll position to focus on the zoom target
- float scrollPos = Content.DrawSize.X * relativeContentZoomTarget.Value - localZoomTarget.Value;
- ScrollTo(scrollPos, false);
-
- relativeContentZoomTarget = null;
- localZoomTarget = null;
- }
- }
-
- ///
- /// Zoom target as a relative position in the space.
- ///
- private float? relativeContentZoomTarget;
-
- ///
- /// Zoom target as a position in our local space.
- ///
- private float? localZoomTarget;
-
- protected override bool OnScroll(InputState state)
- {
- if (!state.Keyboard.ControlPressed)
- return base.OnScroll(state);
-
- relativeContentZoomTarget = Content.ToLocalSpace(state.Mouse.NativeState.Position).X / Content.DrawSize.X;
- localZoomTarget = ToLocalSpace(state.Mouse.NativeState.Position).X;
-
- Zoom += state.Mouse.ScrollDelta.Y;
-
- return true;
- }
-
- private void waveformVisibilityChanged(bool visible) => waveform.FadeTo(visible ? 1 : 0, 200, Easing.OutQuint);
- }
-}
diff --git a/osu.Game/Screens/Edit/Screens/Compose/Timeline/Timeline.cs b/osu.Game/Screens/Edit/Screens/Compose/Timeline/Timeline.cs
new file mode 100644
index 0000000000..3649b24cd0
--- /dev/null
+++ b/osu.Game/Screens/Edit/Screens/Compose/Timeline/Timeline.cs
@@ -0,0 +1,57 @@
+// Copyright (c) 2007-2018 ppy Pty Ltd .
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using osu.Framework.Allocation;
+using osu.Framework.Configuration;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Audio;
+using osu.Game.Beatmaps;
+using osu.Game.Graphics;
+
+namespace osu.Game.Screens.Edit.Screens.Compose.Timeline
+{
+ public class Timeline : ZoomableScrollContainer
+ {
+ public readonly Bindable WaveformVisible = new Bindable();
+ public readonly IBindable Beatmap = new Bindable();
+
+ public Timeline()
+ {
+ ZoomDuration = 200;
+ ZoomEasing = Easing.OutQuint;
+ Zoom = 10;
+ }
+
+ private WaveformGraph waveform;
+
+ [BackgroundDependencyLoader]
+ private void load(IBindableBeatmap beatmap)
+ {
+ Child = waveform = new WaveformGraph
+ {
+ RelativeSizeAxes = Axes.Both,
+ Colour = OsuColour.FromHex("222"),
+ Depth = float.MaxValue
+ };
+
+ WaveformVisible.ValueChanged += visible => waveform.FadeTo(visible ? 1 : 0, 200, Easing.OutQuint);
+
+ Beatmap.BindTo(beatmap);
+ }
+
+ protected override void LoadComplete()
+ {
+ base.LoadComplete();
+ Beatmap.BindValueChanged(b => waveform.Waveform = b.Waveform);
+ waveform.Waveform = Beatmap.Value.Waveform;
+ }
+
+ protected override void Update()
+ {
+ base.Update();
+
+ // We want time = 0 to be at the centre of the container when scrolled to the start
+ Content.Margin = new MarginPadding { Horizontal = DrawWidth / 2 };
+ }
+ }
+}
diff --git a/osu.Game/Screens/Edit/Screens/Compose/Timeline/TimelineArea.cs b/osu.Game/Screens/Edit/Screens/Compose/Timeline/TimelineArea.cs
new file mode 100644
index 0000000000..006317e57e
--- /dev/null
+++ b/osu.Game/Screens/Edit/Screens/Compose/Timeline/TimelineArea.cs
@@ -0,0 +1,128 @@
+// Copyright (c) 2007-2018 ppy Pty Ltd .
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using OpenTK;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Shapes;
+using osu.Game.Graphics;
+using osu.Game.Graphics.UserInterface;
+
+namespace osu.Game.Screens.Edit.Screens.Compose.Timeline
+{
+ public class TimelineArea : CompositeDrawable
+ {
+ private readonly Timeline timeline;
+
+ public TimelineArea()
+ {
+ Masking = true;
+ CornerRadius = 5;
+
+ OsuCheckbox hitObjectsCheckbox;
+ OsuCheckbox hitSoundsCheckbox;
+ OsuCheckbox waveformCheckbox;
+
+ InternalChildren = new Drawable[]
+ {
+ new Box
+ {
+ RelativeSizeAxes = Axes.Both,
+ Colour = OsuColour.FromHex("111")
+ },
+ new GridContainer
+ {
+ RelativeSizeAxes = Axes.Both,
+ Content = new[]
+ {
+ new Drawable[]
+ {
+ new Container
+ {
+ RelativeSizeAxes = Axes.Y,
+ AutoSizeAxes = Axes.X,
+ Children = new Drawable[]
+ {
+ new Box
+ {
+ RelativeSizeAxes = Axes.Both,
+ Colour = OsuColour.FromHex("222")
+ },
+ new FillFlowContainer
+ {
+ Anchor = Anchor.CentreLeft,
+ Origin = Anchor.CentreLeft,
+ AutoSizeAxes = Axes.Y,
+ Width = 160,
+ Padding = new MarginPadding { Horizontal = 15 },
+ Direction = FillDirection.Vertical,
+ Spacing = new Vector2(0, 4),
+ Children = new[]
+ {
+ hitObjectsCheckbox = new OsuCheckbox { LabelText = "Hitobjects" },
+ hitSoundsCheckbox = new OsuCheckbox { LabelText = "Hitsounds" },
+ waveformCheckbox = new OsuCheckbox { LabelText = "Waveform" }
+ }
+ }
+ }
+ },
+ new Container
+ {
+ RelativeSizeAxes = Axes.Y,
+ AutoSizeAxes = Axes.X,
+ Children = new Drawable[]
+ {
+ new Box
+ {
+ RelativeSizeAxes = Axes.Both,
+ Colour = OsuColour.FromHex("333")
+ },
+ new Container
+ {
+ Anchor = Anchor.CentreLeft,
+ Origin = Anchor.CentreLeft,
+ RelativeSizeAxes = Axes.Y,
+ AutoSizeAxes = Axes.X,
+ Masking = true,
+ Children = new[]
+ {
+ new TimelineButton
+ {
+ RelativeSizeAxes = Axes.Y,
+ Height = 0.5f,
+ Icon = FontAwesome.fa_search_plus,
+ Action = () => timeline.Zoom++
+ },
+ new TimelineButton
+ {
+ Anchor = Anchor.BottomLeft,
+ Origin = Anchor.BottomLeft,
+ RelativeSizeAxes = Axes.Y,
+ Height = 0.5f,
+ Icon = FontAwesome.fa_search_minus,
+ Action = () => timeline.Zoom--
+ },
+ }
+ }
+ }
+ },
+ timeline = new Timeline { RelativeSizeAxes = Axes.Both }
+ },
+ },
+ ColumnDimensions = new[]
+ {
+ new Dimension(GridSizeMode.AutoSize),
+ new Dimension(GridSizeMode.AutoSize),
+ new Dimension(GridSizeMode.Distributed),
+ }
+ }
+ };
+
+ hitObjectsCheckbox.Current.Value = true;
+ hitSoundsCheckbox.Current.Value = true;
+ waveformCheckbox.Current.Value = true;
+
+ timeline.WaveformVisible.BindTo(waveformCheckbox.Current);
+ }
+ }
+}
diff --git a/osu.Game/Screens/Edit/Screens/Compose/Timeline/ZoomableScrollContainer.cs b/osu.Game/Screens/Edit/Screens/Compose/Timeline/ZoomableScrollContainer.cs
new file mode 100644
index 0000000000..035e6a0804
--- /dev/null
+++ b/osu.Game/Screens/Edit/Screens/Compose/Timeline/ZoomableScrollContainer.cs
@@ -0,0 +1,174 @@
+// Copyright (c) 2007-2018 ppy Pty Ltd .
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using System;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Transforms;
+using osu.Framework.Input;
+using osu.Framework.MathUtils;
+using OpenTK;
+
+namespace osu.Game.Screens.Edit.Screens.Compose.Timeline
+{
+ public class ZoomableScrollContainer : ScrollContainer
+ {
+ ///
+ /// The time to zoom into/out of a point.
+ /// All user scroll input will be overwritten during the zoom transform.
+ ///
+ public double ZoomDuration;
+
+ ///
+ /// The easing with which to transform the zoom.
+ ///
+ public Easing ZoomEasing;
+
+ private readonly Container zoomedContent;
+ protected override Container Content => zoomedContent;
+
+ private float currentZoom = 1;
+
+ public ZoomableScrollContainer()
+ : base(Direction.Horizontal)
+ {
+ base.Content.Add(zoomedContent = new Container { RelativeSizeAxes = Axes.Y });
+ }
+
+ private int minZoom = 1;
+
+ ///
+ /// The minimum zoom level allowed.
+ ///
+ public int MinZoom
+ {
+ get => minZoom;
+ set
+ {
+ if (value < 1)
+ throw new ArgumentException($"{nameof(MinZoom)} must be >= 1.", nameof(value));
+ minZoom = value;
+
+ if (Zoom < value)
+ Zoom = value;
+ }
+ }
+
+ private int maxZoom = 60;
+
+ ///
+ /// The maximum zoom level allowed.
+ ///
+ public int MaxZoom
+ {
+ get => maxZoom;
+ set
+ {
+ if (value < 1)
+ throw new ArgumentException($"{nameof(MaxZoom)} must be >= 1.", nameof(value));
+ maxZoom = value;
+
+ if (Zoom > value)
+ Zoom = value;
+ }
+ }
+
+ ///
+ /// Gets or sets the content zoom level of this .
+ ///
+ public float Zoom
+ {
+ get => zoomTarget;
+ set
+ {
+ value = MathHelper.Clamp(value, MinZoom, MaxZoom);
+
+ if (IsLoaded)
+ setZoomTarget(value, ToSpaceOfOtherDrawable(new Vector2(DrawWidth / 2, 0), zoomedContent).X);
+ else
+ currentZoom = zoomTarget = value;
+ }
+ }
+
+ protected override void Update()
+ {
+ base.Update();
+
+ zoomedContent.Width = DrawWidth * currentZoom;
+ }
+
+ protected override bool OnScroll(InputState state)
+ {
+ if (!state.Keyboard.ControlPressed)
+ return base.OnScroll(state);
+
+ setZoomTarget(zoomTarget + state.Mouse.ScrollDelta.X, zoomedContent.ToLocalSpace(state.Mouse.NativeState.Position).X);
+ return true;
+ }
+
+ private float zoomTarget = 1;
+ private void setZoomTarget(float newZoom, float focusPoint)
+ {
+ zoomTarget = MathHelper.Clamp(newZoom, MinZoom, MaxZoom);
+ transformZoomTo(zoomTarget, focusPoint, ZoomDuration, ZoomEasing);
+ }
+
+ private void transformZoomTo(float newZoom, float focusPoint, double duration = 0, Easing easing = Easing.None)
+ => this.TransformTo(this.PopulateTransform(new TransformZoom(focusPoint, zoomedContent.DrawWidth, Current), newZoom, duration, easing));
+
+ private class TransformZoom : Transform
+ {
+ ///
+ /// The focus point in absolute coordinates local to the content.
+ ///
+ private readonly float focusPoint;
+
+ ///
+ /// The size of the content.
+ ///
+ private readonly float contentSize;
+
+ ///
+ /// The scroll offset at the start of the transform.
+ ///
+ private readonly float scrollOffset;
+
+ ///
+ /// Transforms to a new value.
+ ///
+ /// The focus point in absolute coordinates local to the content.
+ /// The size of the content.
+ /// The scroll offset at the start of the transform.
+ public TransformZoom(float focusPoint, float contentSize, float scrollOffset)
+ {
+ this.focusPoint = focusPoint;
+ this.contentSize = contentSize;
+ this.scrollOffset = scrollOffset;
+ }
+
+ public override string TargetMember => nameof(currentZoom);
+
+ private float valueAt(double time)
+ {
+ if (time < StartTime) return StartValue;
+ if (time >= EndTime) return EndValue;
+
+ return Interpolation.ValueAt(time, StartValue, EndValue, StartTime, EndTime, Easing);
+ }
+
+ protected override void Apply(ZoomableScrollContainer d, double time)
+ {
+ float newZoom = valueAt(time);
+
+ float focusOffset = focusPoint - scrollOffset;
+ float expectedWidth = d.DrawWidth * newZoom;
+ float targetOffset = expectedWidth * (focusPoint / contentSize) - focusOffset;
+
+ d.currentZoom = newZoom;
+ d.ScrollTo(targetOffset, false);
+ }
+
+ protected override void ReadIntoStartValue(ZoomableScrollContainer d) => StartValue = d.currentZoom;
+ }
+ }
+}
diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs
index fe77f85a50..04148cd558 100644
--- a/osu.Game/Screens/Play/Player.cs
+++ b/osu.Game/Screens/Play/Player.cs
@@ -158,6 +158,7 @@ namespace osu.Game.Screens.Play
userAudioOffset.TriggerChange();
ScoreProcessor = RulesetContainer.CreateScoreProcessor();
+ config.BindWith(OsuSetting.ScoreDisplayMode, ScoreProcessor.Mode);
Children = new Drawable[]
{
diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj
index f5ff1fbd53..d87e190352 100644
--- a/osu.Game/osu.Game.csproj
+++ b/osu.Game/osu.Game.csproj
@@ -17,8 +17,8 @@
-
-
+
+
diff --git a/osu.TestProject.props b/osu.TestProject.props
index cf8276eee1..8f7128f8b7 100644
--- a/osu.TestProject.props
+++ b/osu.TestProject.props
@@ -13,7 +13,7 @@
-
+