diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs
index 3c1f48b7a4..5fd5fe342d 100644
--- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs
+++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs
@@ -170,27 +170,98 @@ namespace osu.Game.Tests.Beatmaps.Formats
var controlPoints = beatmap.ControlPointInfo;
Assert.AreEqual(4, controlPoints.TimingPoints.Count);
- var timingPoint = controlPoints.TimingPoints[0];
+ Assert.AreEqual(42, controlPoints.DifficultyPoints.Count);
+ Assert.AreEqual(42, controlPoints.SamplePoints.Count);
+ Assert.AreEqual(42, controlPoints.EffectPoints.Count);
+
+ var timingPoint = controlPoints.TimingPointAt(0);
+ Assert.AreEqual(956, timingPoint.Time);
+ Assert.AreEqual(329.67032967033, timingPoint.BeatLength);
+ Assert.AreEqual(TimeSignatures.SimpleQuadruple, timingPoint.TimeSignature);
+
+ timingPoint = controlPoints.TimingPointAt(48428);
Assert.AreEqual(956, timingPoint.Time);
Assert.AreEqual(329.67032967033d, timingPoint.BeatLength);
Assert.AreEqual(TimeSignatures.SimpleQuadruple, timingPoint.TimeSignature);
- Assert.AreEqual(5, controlPoints.DifficultyPoints.Count);
- var difficultyPoint = controlPoints.DifficultyPoints[0];
- Assert.AreEqual(116999, difficultyPoint.Time);
- Assert.AreEqual(0.75000000000000189d, difficultyPoint.SpeedMultiplier);
+ timingPoint = controlPoints.TimingPointAt(119637);
+ Assert.AreEqual(119637, timingPoint.Time);
+ Assert.AreEqual(659.340659340659, timingPoint.BeatLength);
+ Assert.AreEqual(TimeSignatures.SimpleQuadruple, timingPoint.TimeSignature);
- Assert.AreEqual(34, controlPoints.SamplePoints.Count);
- var soundPoint = controlPoints.SamplePoints[0];
+ var difficultyPoint = controlPoints.DifficultyPointAt(0);
+ Assert.AreEqual(0, difficultyPoint.Time);
+ Assert.AreEqual(1.0, difficultyPoint.SpeedMultiplier);
+
+ difficultyPoint = controlPoints.DifficultyPointAt(48428);
+ Assert.AreEqual(48428, difficultyPoint.Time);
+ Assert.AreEqual(1.0, difficultyPoint.SpeedMultiplier);
+
+ difficultyPoint = controlPoints.DifficultyPointAt(116999);
+ Assert.AreEqual(116999, difficultyPoint.Time);
+ Assert.AreEqual(0.75, difficultyPoint.SpeedMultiplier, 0.1);
+
+ var soundPoint = controlPoints.SamplePointAt(0);
Assert.AreEqual(956, soundPoint.Time);
Assert.AreEqual("soft", soundPoint.SampleBank);
Assert.AreEqual(60, soundPoint.SampleVolume);
- Assert.AreEqual(8, controlPoints.EffectPoints.Count);
- var effectPoint = controlPoints.EffectPoints[0];
+ soundPoint = controlPoints.SamplePointAt(53373);
+ Assert.AreEqual(53373, soundPoint.Time);
+ Assert.AreEqual("soft", soundPoint.SampleBank);
+ Assert.AreEqual(60, soundPoint.SampleVolume);
+
+ soundPoint = controlPoints.SamplePointAt(119637);
+ Assert.AreEqual(119637, soundPoint.Time);
+ Assert.AreEqual("soft", soundPoint.SampleBank);
+ Assert.AreEqual(80, soundPoint.SampleVolume);
+
+ var effectPoint = controlPoints.EffectPointAt(0);
+ Assert.AreEqual(0, effectPoint.Time);
+ Assert.IsFalse(effectPoint.KiaiMode);
+ Assert.IsFalse(effectPoint.OmitFirstBarLine);
+
+ effectPoint = controlPoints.EffectPointAt(53703);
Assert.AreEqual(53703, effectPoint.Time);
Assert.IsTrue(effectPoint.KiaiMode);
Assert.IsFalse(effectPoint.OmitFirstBarLine);
+
+ effectPoint = controlPoints.EffectPointAt(119637);
+ Assert.AreEqual(119637, effectPoint.Time);
+ Assert.IsFalse(effectPoint.KiaiMode);
+ Assert.IsFalse(effectPoint.OmitFirstBarLine);
+ }
+ }
+
+ [Test]
+ public void TestDecodeOverlappingTimingPoints()
+ {
+ var decoder = new LegacyBeatmapDecoder { ApplyOffsets = false };
+
+ using (var resStream = TestResources.OpenResource("overlapping-control-points.osu"))
+ using (var stream = new StreamReader(resStream))
+ {
+ var controlPoints = decoder.Decode(stream).ControlPointInfo;
+
+ Assert.That(controlPoints.DifficultyPointAt(500).SpeedMultiplier, Is.EqualTo(1.5).Within(0.1));
+ Assert.That(controlPoints.DifficultyPointAt(1500).SpeedMultiplier, Is.EqualTo(1.5).Within(0.1));
+ Assert.That(controlPoints.DifficultyPointAt(2500).SpeedMultiplier, Is.EqualTo(0.75).Within(0.1));
+ Assert.That(controlPoints.DifficultyPointAt(3500).SpeedMultiplier, Is.EqualTo(1.5).Within(0.1));
+
+ Assert.That(controlPoints.EffectPointAt(500).KiaiMode, Is.True);
+ Assert.That(controlPoints.EffectPointAt(1500).KiaiMode, Is.True);
+ Assert.That(controlPoints.EffectPointAt(2500).KiaiMode, Is.False);
+ Assert.That(controlPoints.EffectPointAt(3500).KiaiMode, Is.True);
+
+ Assert.That(controlPoints.SamplePointAt(500).SampleBank, Is.EqualTo("drum"));
+ Assert.That(controlPoints.SamplePointAt(1500).SampleBank, Is.EqualTo("drum"));
+ Assert.That(controlPoints.SamplePointAt(2500).SampleBank, Is.EqualTo("normal"));
+ Assert.That(controlPoints.SamplePointAt(3500).SampleBank, Is.EqualTo("drum"));
+
+ Assert.That(controlPoints.TimingPointAt(500).BeatLength, Is.EqualTo(500).Within(0.1));
+ Assert.That(controlPoints.TimingPointAt(1500).BeatLength, Is.EqualTo(500).Within(0.1));
+ Assert.That(controlPoints.TimingPointAt(2500).BeatLength, Is.EqualTo(250).Within(0.1));
+ Assert.That(controlPoints.TimingPointAt(3500).BeatLength, Is.EqualTo(500).Within(0.1));
}
}
diff --git a/osu.Game.Tests/Resources/overlapping-control-points.osu b/osu.Game.Tests/Resources/overlapping-control-points.osu
new file mode 100644
index 0000000000..31d38a3d01
--- /dev/null
+++ b/osu.Game.Tests/Resources/overlapping-control-points.osu
@@ -0,0 +1,19 @@
+osu file format v14
+
+[TimingPoints]
+
+// Timing then inherited
+0,500,4,2,0,100,1,0
+0,-66.6666666666667,4,3,0,100,0,1
+
+// Inherited then timing (equivalent to previous)
+1000,-66.6666666666667,4,3,0,100,0,1
+1000,500,4,2,0,100,1,0
+
+// Inherited then timing (different to previous)
+2000,-133.333333333333,4,1,0,100,0,0
+2000,250,4,2,0,100,1,0
+
+// Timing then inherited (different to previous)
+3000,500,4,2,0,100,1,0
+3000,-66.6666666666667,4,3,0,100,0,1
diff --git a/osu.Game.Tests/Visual/Menus/TestSceneLoaderAnimation.cs b/osu.Game.Tests/Visual/Menus/TestSceneLoaderAnimation.cs
index 813d4df708..000832b784 100644
--- a/osu.Game.Tests/Visual/Menus/TestSceneLoaderAnimation.cs
+++ b/osu.Game.Tests/Visual/Menus/TestSceneLoaderAnimation.cs
@@ -53,38 +53,12 @@ namespace osu.Game.Tests.Visual.Menus
}
[Test]
- public void TestShortLoad()
+ public void TestDelayedLoad()
{
- bool logoVisible = false;
-
AddStep("begin loading", () => LoadScreen(loader = new TestLoader()));
- AddWaitStep("wait", 3);
- AddStep("finish loading", () =>
- {
- logoVisible = loader.Logo?.Alpha > 0;
- loader.AllowLoad.Set();
- });
-
+ AddUntilStep("wait for logo visible", () => loader.Logo?.Alpha > 0);
+ AddStep("finish loading", () => loader.AllowLoad.Set());
AddAssert("loaded", () => loader.Logo != null && loader.ScreenLoaded);
- AddAssert("logo was visible", () => logoVisible);
- AddUntilStep("logo gone", () => loader.Logo?.Alpha == 0);
- }
-
- [Test]
- public void TestLongLoad()
- {
- bool logoVisible = false;
-
- AddStep("begin loading", () => LoadScreen(loader = new TestLoader()));
- AddWaitStep("wait", 10);
- AddStep("finish loading", () =>
- {
- logoVisible = loader.Logo?.Alpha > 0;
- loader.AllowLoad.Set();
- });
-
- AddAssert("loaded", () => loader.Logo != null && loader.ScreenLoaded);
- AddAssert("logo was visible", () => logoVisible);
AddUntilStep("logo gone", () => loader.Logo?.Alpha == 0);
}
diff --git a/osu.Game.Tests/Visual/Online/TestSceneUserProfileHeader.cs b/osu.Game.Tests/Visual/Online/TestSceneUserProfileHeader.cs
index 14c81558c1..d9230090fc 100644
--- a/osu.Game.Tests/Visual/Online/TestSceneUserProfileHeader.cs
+++ b/osu.Game.Tests/Visual/Online/TestSceneUserProfileHeader.cs
@@ -7,6 +7,7 @@ using osu.Framework.Allocation;
using osu.Game.Graphics.UserInterface;
using osu.Game.Online.API;
using osu.Game.Online.API.Requests;
+using osu.Game.Overlays;
using osu.Game.Overlays.Profile;
using osu.Game.Overlays.Profile.Header;
using osu.Game.Overlays.Profile.Header.Components;
@@ -21,7 +22,7 @@ namespace osu.Game.Tests.Visual.Online
typeof(ProfileHeader),
typeof(RankGraph),
typeof(LineGraph),
- typeof(ProfileHeaderTabControl),
+ typeof(OverlayHeaderTabControl),
typeof(CentreHeaderContainer),
typeof(BottomHeaderContainer),
typeof(DetailHeaderContainer),
diff --git a/osu.Game/Beatmaps/ControlPoints/ControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/ControlPoint.cs
index 825b60ae5f..abe7e5e803 100644
--- a/osu.Game/Beatmaps/ControlPoints/ControlPoint.cs
+++ b/osu.Game/Beatmaps/ControlPoints/ControlPoint.cs
@@ -12,17 +12,14 @@ namespace osu.Game.Beatmaps.ControlPoints
///
public double Time;
+ ///
+ /// Whether this timing point was generated internally, as opposed to parsed from the underlying beatmap.
+ ///
+ internal bool AutoGenerated;
+
public int CompareTo(ControlPoint other) => Time.CompareTo(other.Time);
- ///
- /// Whether this provides the same parametric changes as another .
- /// Basically an equality check without considering the .
- ///
- /// The to compare to.
- /// Whether this is equivalent to .
- public virtual bool EquivalentTo(ControlPoint other) => true;
-
public bool Equals(ControlPoint other)
- => EquivalentTo(other) && Time.Equals(other?.Time);
+ => Time.Equals(other?.Time);
}
}
diff --git a/osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs
index 013271d597..a3e3121575 100644
--- a/osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs
+++ b/osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs
@@ -1,11 +1,12 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+using System;
using osuTK;
namespace osu.Game.Beatmaps.ControlPoints
{
- public class DifficultyControlPoint : ControlPoint
+ public class DifficultyControlPoint : ControlPoint, IEquatable
{
///
/// The speed multiplier at this control point.
@@ -18,9 +19,8 @@ namespace osu.Game.Beatmaps.ControlPoints
private double speedMultiplier = 1;
- public override bool EquivalentTo(ControlPoint other)
- => base.EquivalentTo(other)
- && other is DifficultyControlPoint difficulty
- && SpeedMultiplier.Equals(difficulty.SpeedMultiplier);
+ public bool Equals(DifficultyControlPoint other)
+ => base.Equals(other)
+ && SpeedMultiplier.Equals(other?.SpeedMultiplier);
}
}
diff --git a/osu.Game/Beatmaps/ControlPoints/EffectControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/EffectControlPoint.cs
index 3978b7b4b0..354d86dc13 100644
--- a/osu.Game/Beatmaps/ControlPoints/EffectControlPoint.cs
+++ b/osu.Game/Beatmaps/ControlPoints/EffectControlPoint.cs
@@ -1,9 +1,11 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+using System;
+
namespace osu.Game.Beatmaps.ControlPoints
{
- public class EffectControlPoint : ControlPoint
+ public class EffectControlPoint : ControlPoint, IEquatable
{
///
/// Whether this control point enables Kiai mode.
@@ -15,10 +17,8 @@ namespace osu.Game.Beatmaps.ControlPoints
///
public bool OmitFirstBarLine;
- public override bool EquivalentTo(ControlPoint other)
- => base.EquivalentTo(other)
- && other is EffectControlPoint effect
- && KiaiMode.Equals(effect.KiaiMode)
- && OmitFirstBarLine.Equals(effect.OmitFirstBarLine);
+ public bool Equals(EffectControlPoint other)
+ => base.Equals(other)
+ && KiaiMode == other?.KiaiMode && OmitFirstBarLine == other.OmitFirstBarLine;
}
}
diff --git a/osu.Game/Beatmaps/ControlPoints/SampleControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/SampleControlPoint.cs
index 241ce90740..4c45bef862 100644
--- a/osu.Game/Beatmaps/ControlPoints/SampleControlPoint.cs
+++ b/osu.Game/Beatmaps/ControlPoints/SampleControlPoint.cs
@@ -1,11 +1,12 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+using System;
using osu.Game.Audio;
namespace osu.Game.Beatmaps.ControlPoints
{
- public class SampleControlPoint : ControlPoint
+ public class SampleControlPoint : ControlPoint, IEquatable
{
public const string DEFAULT_BANK = "normal";
@@ -44,10 +45,8 @@ namespace osu.Game.Beatmaps.ControlPoints
return newSampleInfo;
}
- public override bool EquivalentTo(ControlPoint other)
- => base.EquivalentTo(other)
- && other is SampleControlPoint sample
- && SampleBank.Equals(sample.SampleBank)
- && SampleVolume.Equals(sample.SampleVolume);
+ public bool Equals(SampleControlPoint other)
+ => base.Equals(other)
+ && string.Equals(SampleBank, other?.SampleBank) && SampleVolume == other?.SampleVolume;
}
}
diff --git a/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs
index 9ec27bdfdf..e5815a3f3b 100644
--- a/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs
+++ b/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs
@@ -1,12 +1,13 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+using System;
using osuTK;
using osu.Game.Beatmaps.Timing;
namespace osu.Game.Beatmaps.ControlPoints
{
- public class TimingControlPoint : ControlPoint
+ public class TimingControlPoint : ControlPoint, IEquatable
{
///
/// The time signature at this control point.
@@ -24,10 +25,8 @@ namespace osu.Game.Beatmaps.ControlPoints
private double beatLength = 1000;
- public override bool EquivalentTo(ControlPoint other)
- => base.EquivalentTo(other)
- && other is TimingControlPoint timing
- && TimeSignature.Equals(timing.TimeSignature)
- && BeatLength.Equals(timing.BeatLength);
+ public bool Equals(TimingControlPoint other)
+ => base.Equals(other)
+ && TimeSignature == other?.TimeSignature && beatLength.Equals(other.beatLength);
}
}
diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs
index b489b5e6d9..3cd425ea44 100644
--- a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs
+++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs
@@ -374,14 +374,16 @@ namespace osu.Game.Beatmaps.Formats
handleDifficultyControlPoint(new DifficultyControlPoint
{
Time = time,
- SpeedMultiplier = speedMultiplier
+ SpeedMultiplier = speedMultiplier,
+ AutoGenerated = timingChange
});
handleEffectControlPoint(new EffectControlPoint
{
Time = time,
KiaiMode = kiaiMode,
- OmitFirstBarLine = omitFirstBarSignature
+ OmitFirstBarLine = omitFirstBarSignature,
+ AutoGenerated = timingChange
});
handleSampleControlPoint(new LegacySampleControlPoint
@@ -389,7 +391,8 @@ namespace osu.Game.Beatmaps.Formats
Time = time,
SampleBank = stringSampleSet,
SampleVolume = sampleVolume,
- CustomSampleBank = customSampleBank
+ CustomSampleBank = customSampleBank,
+ AutoGenerated = timingChange
});
}
catch (FormatException)
@@ -407,7 +410,14 @@ namespace osu.Game.Beatmaps.Formats
var existing = beatmap.ControlPointInfo.TimingPointAt(newPoint.Time);
if (existing.Time == newPoint.Time)
+ {
+ // autogenerated points should not replace non-autogenerated.
+ // this allows for incorrectly ordered timing points to still be correctly handled.
+ if (newPoint.AutoGenerated && !existing.AutoGenerated)
+ return;
+
beatmap.ControlPointInfo.TimingPoints.Remove(existing);
+ }
beatmap.ControlPointInfo.TimingPoints.Add(newPoint);
}
@@ -416,11 +426,15 @@ namespace osu.Game.Beatmaps.Formats
{
var existing = beatmap.ControlPointInfo.DifficultyPointAt(newPoint.Time);
- if (newPoint.EquivalentTo(existing))
- return;
-
if (existing.Time == newPoint.Time)
+ {
+ // autogenerated points should not replace non-autogenerated.
+ // this allows for incorrectly ordered timing points to still be correctly handled.
+ if (newPoint.AutoGenerated && !existing.AutoGenerated)
+ return;
+
beatmap.ControlPointInfo.DifficultyPoints.Remove(existing);
+ }
beatmap.ControlPointInfo.DifficultyPoints.Add(newPoint);
}
@@ -429,11 +443,15 @@ namespace osu.Game.Beatmaps.Formats
{
var existing = beatmap.ControlPointInfo.EffectPointAt(newPoint.Time);
- if (newPoint.EquivalentTo(existing))
- return;
-
if (existing.Time == newPoint.Time)
+ {
+ // autogenerated points should not replace non-autogenerated.
+ // this allows for incorrectly ordered timing points to still be correctly handled.
+ if (newPoint.AutoGenerated && !existing.AutoGenerated)
+ return;
+
beatmap.ControlPointInfo.EffectPoints.Remove(existing);
+ }
beatmap.ControlPointInfo.EffectPoints.Add(newPoint);
}
@@ -442,11 +460,15 @@ namespace osu.Game.Beatmaps.Formats
{
var existing = beatmap.ControlPointInfo.SamplePointAt(newPoint.Time);
- if (newPoint.EquivalentTo(existing))
- return;
-
if (existing.Time == newPoint.Time)
+ {
+ // autogenerated points should not replace non-autogenerated.
+ // this allows for incorrectly ordered timing points to still be correctly handled.
+ if (newPoint.AutoGenerated && !existing.AutoGenerated)
+ return;
+
beatmap.ControlPointInfo.SamplePoints.Remove(existing);
+ }
beatmap.ControlPointInfo.SamplePoints.Add(newPoint);
}
diff --git a/osu.Game/Beatmaps/Formats/LegacyDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyDecoder.cs
index eb5bcfe824..7b7e0e7101 100644
--- a/osu.Game/Beatmaps/Formats/LegacyDecoder.cs
+++ b/osu.Game/Beatmaps/Formats/LegacyDecoder.cs
@@ -189,7 +189,7 @@ namespace osu.Game.Beatmaps.Formats
Foreground = 3
}
- internal class LegacySampleControlPoint : SampleControlPoint
+ internal class LegacySampleControlPoint : SampleControlPoint, IEquatable
{
public int CustomSampleBank;
@@ -203,10 +203,9 @@ namespace osu.Game.Beatmaps.Formats
return baseInfo;
}
- public override bool EquivalentTo(ControlPoint other)
- => base.EquivalentTo(other)
- && other is LegacySampleControlPoint legacy
- && CustomSampleBank == legacy.CustomSampleBank;
+ public bool Equals(LegacySampleControlPoint other)
+ => base.Equals(other)
+ && CustomSampleBank == other?.CustomSampleBank;
}
}
}
diff --git a/osu.Game/Graphics/Containers/OsuClickableContainer.cs b/osu.Game/Graphics/Containers/OsuClickableContainer.cs
index e4d30cebb7..6dbe340efb 100644
--- a/osu.Game/Graphics/Containers/OsuClickableContainer.cs
+++ b/osu.Game/Graphics/Containers/OsuClickableContainer.cs
@@ -4,11 +4,12 @@
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Cursor;
using osu.Game.Graphics.UserInterface;
namespace osu.Game.Graphics.Containers
{
- public class OsuClickableContainer : ClickableContainer
+ public class OsuClickableContainer : ClickableContainer, IHasTooltip
{
private readonly HoverSampleSet sampleSet;
@@ -23,6 +24,8 @@ namespace osu.Game.Graphics.Containers
this.sampleSet = sampleSet;
}
+ public virtual string TooltipText { get; set; }
+
[BackgroundDependencyLoader]
private void load()
{
diff --git a/osu.Game/Graphics/UserInterface/ScreenTitle.cs b/osu.Game/Graphics/UserInterface/ScreenTitle.cs
index b9d9b5427d..fe1936d4e8 100644
--- a/osu.Game/Graphics/UserInterface/ScreenTitle.cs
+++ b/osu.Game/Graphics/UserInterface/ScreenTitle.cs
@@ -1,6 +1,7 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+using System;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
@@ -12,26 +13,33 @@ namespace osu.Game.Graphics.UserInterface
{
public abstract class ScreenTitle : CompositeDrawable, IHasAccentColour
{
- private readonly SpriteIcon iconSprite;
+ public const float ICON_WIDTH = ICON_SIZE + icon_spacing;
+
+ protected const float ICON_SIZE = 25;
+
+ private SpriteIcon iconSprite;
private readonly OsuSpriteText titleText, pageText;
- public const float ICON_WIDTH = icon_size + icon_spacing;
- private const float icon_size = 25, icon_spacing = 10;
+
+ private const float icon_spacing = 10;
protected IconUsage Icon
{
- get => iconSprite.Icon;
- set => iconSprite.Icon = value;
+ set
+ {
+ if (iconSprite == null)
+ throw new InvalidOperationException($"Cannot use {nameof(Icon)} with a custom {nameof(CreateIcon)} function.");
+
+ iconSprite.Icon = value;
+ }
}
protected string Title
{
- get => titleText.Text;
set => titleText.Text = value;
}
protected string Section
{
- get => pageText.Text;
set => pageText.Text = value;
}
@@ -41,6 +49,11 @@ namespace osu.Game.Graphics.UserInterface
set => pageText.Colour = value;
}
+ protected virtual Drawable CreateIcon() => iconSprite = new SpriteIcon
+ {
+ Size = new Vector2(ICON_SIZE),
+ };
+
protected ScreenTitle()
{
AutoSizeAxes = Axes.Both;
@@ -51,12 +64,9 @@ namespace osu.Game.Graphics.UserInterface
{
AutoSizeAxes = Axes.Both,
Spacing = new Vector2(icon_spacing, 0),
- Children = new Drawable[]
+ Children = new[]
{
- iconSprite = new SpriteIcon
- {
- Size = new Vector2(icon_size),
- },
+ CreateIcon(),
new FillFlowContainer
{
AutoSizeAxes = Axes.Both,
diff --git a/osu.Game/Online/Chat/DrawableLinkCompiler.cs b/osu.Game/Online/Chat/DrawableLinkCompiler.cs
index d34ec8091c..d27a3fbffe 100644
--- a/osu.Game/Online/Chat/DrawableLinkCompiler.cs
+++ b/osu.Game/Online/Chat/DrawableLinkCompiler.cs
@@ -1,7 +1,6 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-using osu.Framework.Graphics.Cursor;
using System.Collections.Generic;
using System.Linq;
using osu.Framework.Allocation;
@@ -16,7 +15,7 @@ namespace osu.Game.Online.Chat
///
/// An invisible drawable that brings multiple pieces together to form a consumable clickable link.
///
- public class DrawableLinkCompiler : OsuHoverContainer, IHasTooltip
+ public class DrawableLinkCompiler : OsuHoverContainer
{
///
/// Each word part of a chat link (split for word-wrap support).
@@ -40,8 +39,6 @@ namespace osu.Game.Online.Chat
protected override IEnumerable EffectTargets => Parts;
- public string TooltipText { get; set; }
-
private class LinkHoverSounds : HoverClickSounds
{
private readonly List parts;
diff --git a/osu.Game/Overlays/BeatmapSet/AuthorInfo.cs b/osu.Game/Overlays/BeatmapSet/AuthorInfo.cs
index abe954aa80..7331faa618 100644
--- a/osu.Game/Overlays/BeatmapSet/AuthorInfo.cs
+++ b/osu.Game/Overlays/BeatmapSet/AuthorInfo.cs
@@ -9,8 +9,6 @@ using osu.Game.Graphics.Sprites;
using osu.Game.Users;
using osuTK;
using osuTK.Graphics;
-using osu.Game.Graphics.Containers;
-using osu.Framework.Graphics.Cursor;
using osu.Framework.Graphics.Effects;
using osu.Framework.Graphics.Sprites;
using osu.Game.Graphics;
@@ -129,10 +127,5 @@ namespace osu.Game.Overlays.BeatmapSet
};
}
}
-
- private class ClickableArea : OsuClickableContainer, IHasTooltip
- {
- public string TooltipText => @"View Profile";
- }
}
}
diff --git a/osu.Game/Overlays/OverlayHeader.cs b/osu.Game/Overlays/OverlayHeader.cs
new file mode 100644
index 0000000000..2e032db2ba
--- /dev/null
+++ b/osu.Game/Overlays/OverlayHeader.cs
@@ -0,0 +1,70 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Game.Graphics.UserInterface;
+
+namespace osu.Game.Overlays
+{
+ public abstract class OverlayHeader : Container
+ {
+ protected readonly OverlayHeaderTabControl TabControl;
+
+ private const float cover_height = 150;
+ private const float cover_info_height = 75;
+
+ protected OverlayHeader()
+ {
+ RelativeSizeAxes = Axes.X;
+ AutoSizeAxes = Axes.Y;
+
+ Children = new Drawable[]
+ {
+ new Container
+ {
+ RelativeSizeAxes = Axes.X,
+ Height = cover_height,
+ Masking = true,
+ Child = CreateBackground()
+ },
+ new Container
+ {
+ Margin = new MarginPadding { Left = UserProfileOverlay.CONTENT_X_MARGIN },
+ Y = cover_height,
+ Height = cover_info_height,
+ RelativeSizeAxes = Axes.X,
+ Anchor = Anchor.TopLeft,
+ Origin = Anchor.BottomLeft,
+ Depth = -float.MaxValue,
+ Children = new Drawable[]
+ {
+ CreateTitle().With(t => t.X = -ScreenTitle.ICON_WIDTH),
+ TabControl = new OverlayHeaderTabControl
+ {
+ Anchor = Anchor.BottomLeft,
+ Origin = Anchor.BottomLeft,
+ RelativeSizeAxes = Axes.X,
+ Height = cover_info_height - 30,
+ Margin = new MarginPadding { Left = -UserProfileOverlay.CONTENT_X_MARGIN },
+ Padding = new MarginPadding { Left = UserProfileOverlay.CONTENT_X_MARGIN }
+ }
+ }
+ },
+ new Container
+ {
+ Margin = new MarginPadding { Top = cover_height },
+ RelativeSizeAxes = Axes.X,
+ AutoSizeAxes = Axes.Y,
+ Child = CreateContent()
+ }
+ };
+ }
+
+ protected abstract Drawable CreateBackground();
+
+ protected abstract Drawable CreateContent();
+
+ protected abstract ScreenTitle CreateTitle();
+ }
+}
diff --git a/osu.Game/Overlays/Profile/Header/ProfileHeaderTabControl.cs b/osu.Game/Overlays/OverlayHeaderTabControl.cs
similarity index 90%
rename from osu.Game/Overlays/Profile/Header/ProfileHeaderTabControl.cs
rename to osu.Game/Overlays/OverlayHeaderTabControl.cs
index 3b16b102d5..21b42cfbf4 100644
--- a/osu.Game/Overlays/Profile/Header/ProfileHeaderTabControl.cs
+++ b/osu.Game/Overlays/OverlayHeaderTabControl.cs
@@ -11,13 +11,13 @@ using osu.Game.Graphics.UserInterface;
using osuTK;
using osuTK.Graphics;
-namespace osu.Game.Overlays.Profile.Header
+namespace osu.Game.Overlays
{
- public class ProfileHeaderTabControl : TabControl
+ public class OverlayHeaderTabControl : TabControl
{
private readonly Box bar;
- private Color4 accentColour;
+ private Color4 accentColour = Color4.White;
public Color4 AccentColour
{
@@ -32,7 +32,7 @@ namespace osu.Game.Overlays.Profile.Header
foreach (TabItem tabItem in TabContainer)
{
- ((ProfileHeaderTabItem)tabItem).AccentColour = value;
+ ((HeaderTabItem)tabItem).AccentColour = value;
}
}
}
@@ -43,7 +43,7 @@ namespace osu.Game.Overlays.Profile.Header
set => TabContainer.Padding = value;
}
- public ProfileHeaderTabControl()
+ public OverlayHeaderTabControl()
{
TabContainer.Masking = false;
TabContainer.Spacing = new Vector2(15, 0);
@@ -59,12 +59,12 @@ namespace osu.Game.Overlays.Profile.Header
protected override Dropdown CreateDropdown() => null;
- protected override TabItem CreateTabItem(string value) => new ProfileHeaderTabItem(value)
+ protected override TabItem CreateTabItem(string value) => new HeaderTabItem(value)
{
AccentColour = AccentColour
};
- private class ProfileHeaderTabItem : TabItem
+ private class HeaderTabItem : TabItem
{
private readonly OsuSpriteText text;
private readonly Drawable bar;
@@ -86,7 +86,7 @@ namespace osu.Game.Overlays.Profile.Header
}
}
- public ProfileHeaderTabItem(string value)
+ public HeaderTabItem(string value)
: base(value)
{
AutoSizeAxes = Axes.X;
diff --git a/osu.Game/Overlays/Profile/Header/Components/ProfileHeaderButton.cs b/osu.Game/Overlays/Profile/Header/Components/ProfileHeaderButton.cs
index 1650f11523..ddcf011277 100644
--- a/osu.Game/Overlays/Profile/Header/Components/ProfileHeaderButton.cs
+++ b/osu.Game/Overlays/Profile/Header/Components/ProfileHeaderButton.cs
@@ -4,7 +4,6 @@
using System.Collections.Generic;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
-using osu.Framework.Graphics.Cursor;
using osu.Framework.Graphics.Shapes;
using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
@@ -12,10 +11,8 @@ using osuTK.Graphics;
namespace osu.Game.Overlays.Profile.Header.Components
{
- public abstract class ProfileHeaderButton : OsuHoverContainer, IHasTooltip
+ public abstract class ProfileHeaderButton : OsuHoverContainer
{
- public abstract string TooltipText { get; }
-
private readonly Box background;
private readonly Container content;
diff --git a/osu.Game/Overlays/Profile/ProfileHeader.cs b/osu.Game/Overlays/Profile/ProfileHeader.cs
index f2ac94b7ff..76613c156d 100644
--- a/osu.Game/Overlays/Profile/ProfileHeader.cs
+++ b/osu.Game/Overlays/Profile/ProfileHeader.cs
@@ -15,124 +15,87 @@ using osu.Game.Users;
namespace osu.Game.Overlays.Profile
{
- public class ProfileHeader : Container
+ public class ProfileHeader : OverlayHeader
{
- private readonly UserCoverBackground coverContainer;
- private readonly ProfileHeaderTabControl infoTabControl;
+ private UserCoverBackground coverContainer;
- private const float cover_height = 150;
- private const float cover_info_height = 75;
+ public Bindable User = new Bindable();
+
+ private CentreHeaderContainer centreHeaderContainer;
+ private DetailHeaderContainer detailHeaderContainer;
public ProfileHeader()
{
- CentreHeaderContainer centreHeaderContainer;
- DetailHeaderContainer detailHeaderContainer;
+ User.ValueChanged += e => updateDisplay(e.NewValue);
- RelativeSizeAxes = Axes.X;
- AutoSizeAxes = Axes.Y;
-
- Children = new Drawable[]
- {
- new Container
- {
- RelativeSizeAxes = Axes.X,
- Height = cover_height,
- Masking = true,
- Children = new Drawable[]
- {
- coverContainer = new UserCoverBackground
- {
- RelativeSizeAxes = Axes.Both,
- },
- new Box
- {
- RelativeSizeAxes = Axes.Both,
- Colour = ColourInfo.GradientVertical(OsuColour.FromHex("222").Opacity(0.8f), OsuColour.FromHex("222").Opacity(0.2f))
- },
- }
- },
- new Container
- {
- Margin = new MarginPadding { Left = UserProfileOverlay.CONTENT_X_MARGIN },
- Y = cover_height,
- Height = cover_info_height,
- RelativeSizeAxes = Axes.X,
- Anchor = Anchor.TopLeft,
- Origin = Anchor.BottomLeft,
- Depth = -float.MaxValue,
- Children = new Drawable[]
- {
- new ProfileHeaderTitle
- {
- X = -ScreenTitle.ICON_WIDTH,
- },
- infoTabControl = new ProfileHeaderTabControl
- {
- Anchor = Anchor.BottomLeft,
- Origin = Anchor.BottomLeft,
- RelativeSizeAxes = Axes.X,
- Height = cover_info_height - 30,
- Margin = new MarginPadding { Left = -UserProfileOverlay.CONTENT_X_MARGIN },
- Padding = new MarginPadding { Left = UserProfileOverlay.CONTENT_X_MARGIN }
- }
- }
- },
- new FillFlowContainer
- {
- Margin = new MarginPadding { Top = cover_height },
- RelativeSizeAxes = Axes.X,
- AutoSizeAxes = Axes.Y,
- Direction = FillDirection.Vertical,
- Children = new Drawable[]
- {
- new TopHeaderContainer
- {
- RelativeSizeAxes = Axes.X,
- User = { BindTarget = User },
- },
- centreHeaderContainer = new CentreHeaderContainer
- {
- RelativeSizeAxes = Axes.X,
- User = { BindTarget = User },
- },
- detailHeaderContainer = new DetailHeaderContainer
- {
- RelativeSizeAxes = Axes.X,
- User = { BindTarget = User },
- },
- new MedalHeaderContainer
- {
- RelativeSizeAxes = Axes.X,
- User = { BindTarget = User },
- },
- new BottomHeaderContainer
- {
- RelativeSizeAxes = Axes.X,
- User = { BindTarget = User },
- },
- }
- }
- };
-
- infoTabControl.AddItem("Info");
- infoTabControl.AddItem("Modding");
+ TabControl.AddItem("Info");
+ TabControl.AddItem("Modding");
centreHeaderContainer.DetailsVisible.BindValueChanged(visible => detailHeaderContainer.Expanded = visible.NewValue, true);
- User.ValueChanged += e => updateDisplay(e.NewValue);
}
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
- infoTabControl.AccentColour = colours.Seafoam;
+ TabControl.AccentColour = colours.Seafoam;
}
- public Bindable User = new Bindable();
+ protected override Drawable CreateBackground() =>
+ new Container
+ {
+ RelativeSizeAxes = Axes.Both,
+ Children = new Drawable[]
+ {
+ coverContainer = new UserCoverBackground
+ {
+ RelativeSizeAxes = Axes.Both,
+ },
+ new Box
+ {
+ RelativeSizeAxes = Axes.Both,
+ Colour = ColourInfo.GradientVertical(OsuColour.FromHex("222").Opacity(0.8f), OsuColour.FromHex("222").Opacity(0.2f))
+ },
+ }
+ };
- private void updateDisplay(User user)
+ protected override Drawable CreateContent() => new FillFlowContainer
{
- coverContainer.User = user;
- }
+ RelativeSizeAxes = Axes.X,
+ AutoSizeAxes = Axes.Y,
+ Direction = FillDirection.Vertical,
+ Children = new Drawable[]
+ {
+ new TopHeaderContainer
+ {
+ RelativeSizeAxes = Axes.X,
+ User = { BindTarget = User },
+ },
+ centreHeaderContainer = new CentreHeaderContainer
+ {
+ RelativeSizeAxes = Axes.X,
+ User = { BindTarget = User },
+ },
+ detailHeaderContainer = new DetailHeaderContainer
+ {
+ RelativeSizeAxes = Axes.X,
+ User = { BindTarget = User },
+ },
+ new MedalHeaderContainer
+ {
+ RelativeSizeAxes = Axes.X,
+ User = { BindTarget = User },
+ },
+ new BottomHeaderContainer
+ {
+ RelativeSizeAxes = Axes.X,
+ User = { BindTarget = User },
+ },
+ }
+ };
+
+ protected override ScreenTitle CreateTitle() => new ProfileHeaderTitle();
+
+ private void updateDisplay(User user) => coverContainer.User = user;
private class ProfileHeaderTitle : ScreenTitle
{
diff --git a/osu.Game/Overlays/Profile/Sections/BeatmapMetadataContainer.cs b/osu.Game/Overlays/Profile/Sections/BeatmapMetadataContainer.cs
index bb55816880..16326900f1 100644
--- a/osu.Game/Overlays/Profile/Sections/BeatmapMetadataContainer.cs
+++ b/osu.Game/Overlays/Profile/Sections/BeatmapMetadataContainer.cs
@@ -4,7 +4,6 @@
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
-using osu.Framework.Graphics.Cursor;
using osu.Framework.Localisation;
using osu.Game.Beatmaps;
using osu.Game.Graphics;
@@ -16,7 +15,7 @@ namespace osu.Game.Overlays.Profile.Sections
///
/// Display artist/title/mapper information, commonly used as the left portion of a profile or score display row (see ).
///
- public class BeatmapMetadataContainer : OsuHoverContainer, IHasTooltip
+ public class BeatmapMetadataContainer : OsuHoverContainer
{
private readonly BeatmapInfo beatmap;
@@ -27,8 +26,6 @@ namespace osu.Game.Overlays.Profile.Sections
TooltipText = $"{beatmap.Metadata.Artist} - {beatmap.Metadata.Title}";
}
- public string TooltipText { get; }
-
[BackgroundDependencyLoader(true)]
private void load(BeatmapSetOverlay beatmapSetOverlay)
{
diff --git a/osu.Game/Users/Avatar.cs b/osu.Game/Users/Avatar.cs
index 3df5957ff9..8937f94768 100644
--- a/osu.Game/Users/Avatar.cs
+++ b/osu.Game/Users/Avatar.cs
@@ -6,7 +6,6 @@ using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
-using osu.Framework.Graphics.Cursor;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.Textures;
using osu.Framework.Input.Events;
@@ -72,9 +71,9 @@ namespace osu.Game.Users
game?.ShowUser(user.Id);
}
- private class ClickableArea : OsuClickableContainer, IHasTooltip
+ private class ClickableArea : OsuClickableContainer
{
- public string TooltipText => Enabled.Value ? @"View Profile" : null;
+ public override string TooltipText => Enabled.Value ? @"View Profile" : null;
protected override bool OnClick(ClickEvent e)
{