diff --git a/osu.Android.props b/osu.Android.props
index 25bde037db..494842f38f 100644
--- a/osu.Android.props
+++ b/osu.Android.props
@@ -54,6 +54,6 @@
-
+
diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.cs b/osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.cs
index 8377b3786a..e2465d727e 100644
--- a/osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.cs
+++ b/osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.cs
@@ -10,7 +10,7 @@ namespace osu.Game.Rulesets.Catch.Mods
{
public class CatchModDifficultyAdjust : ModDifficultyAdjust
{
- [SettingSource("Fruit Size", "Override a beatmap's set CS.")]
+ [SettingSource("Circle Size", "Override a beatmap's set CS.", FIRST_SETTING_ORDER - 1)]
public BindableNumber CircleSize { get; } = new BindableFloat
{
Precision = 0.1f,
@@ -20,7 +20,7 @@ namespace osu.Game.Rulesets.Catch.Mods
Value = 5,
};
- [SettingSource("Approach Rate", "Override a beatmap's set AR.")]
+ [SettingSource("Approach Rate", "Override a beatmap's set AR.", LAST_SETTING_ORDER + 1)]
public BindableNumber ApproachRate { get; } = new BindableFloat
{
Precision = 0.1f,
diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNotePlacementBlueprint.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNotePlacementBlueprint.cs
index bcbc1ee527..7bbde400ea 100644
--- a/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNotePlacementBlueprint.cs
+++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNotePlacementBlueprint.cs
@@ -52,7 +52,7 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints
{
base.UpdatePosition(screenSpacePosition);
- if (PlacementBegun)
+ if (PlacementActive)
{
var endTime = TimeAt(screenSpacePosition);
diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaPlacementBlueprint.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaPlacementBlueprint.cs
index 362d6d40a8..a3657d3bb9 100644
--- a/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaPlacementBlueprint.cs
+++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaPlacementBlueprint.cs
@@ -62,7 +62,7 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints
public override void UpdatePosition(Vector2 screenSpacePosition)
{
- if (!PlacementBegun)
+ if (!PlacementActive)
Column = ColumnAt(screenSpacePosition);
if (Column == null) return;
diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneGameplayCursor.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneGameplayCursor.cs
index aa170eae1e..90f1cdb2ea 100644
--- a/osu.Game.Rulesets.Osu.Tests/TestSceneGameplayCursor.cs
+++ b/osu.Game.Rulesets.Osu.Tests/TestSceneGameplayCursor.cs
@@ -7,7 +7,9 @@ using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Testing.Input;
+using osu.Game.Configuration;
using osu.Game.Rulesets.Osu.UI.Cursor;
+using osu.Game.Screens.Play;
using osuTK;
namespace osu.Game.Rulesets.Osu.Tests
@@ -21,12 +23,50 @@ namespace osu.Game.Rulesets.Osu.Tests
typeof(CursorTrail)
};
- [BackgroundDependencyLoader]
- private void load()
+ [Cached]
+ private GameplayBeatmap gameplayBeatmap;
+
+ private ClickingCursorContainer lastContainer;
+
+ [Resolved]
+ private OsuConfigManager config { get; set; }
+
+ public TestSceneGameplayCursor()
+ {
+ gameplayBeatmap = new GameplayBeatmap(CreateBeatmap(new OsuRuleset().RulesetInfo));
+ }
+
+ [TestCase(1, 1)]
+ [TestCase(5, 1)]
+ [TestCase(10, 1)]
+ [TestCase(1, 1.5f)]
+ [TestCase(5, 1.5f)]
+ [TestCase(10, 1.5f)]
+ public void TestSizing(int circleSize, float userScale)
+ {
+ AddStep($"set user scale to {userScale}", () => config.Set(OsuSetting.GameplayCursorSize, userScale));
+ AddStep($"adjust cs to {circleSize}", () => gameplayBeatmap.BeatmapInfo.BaseDifficulty.CircleSize = circleSize);
+ AddStep("turn on autosizing", () => config.Set(OsuSetting.AutoCursorSize, true));
+
+ AddStep("load content", loadContent);
+
+ AddUntilStep("cursor size correct", () => lastContainer.ActiveCursor.Scale.X == OsuCursorContainer.GetScaleForCircleSize(circleSize) * userScale);
+
+ AddStep("set user scale to 1", () => config.Set(OsuSetting.GameplayCursorSize, 1f));
+ AddUntilStep("cursor size correct", () => lastContainer.ActiveCursor.Scale.X == OsuCursorContainer.GetScaleForCircleSize(circleSize));
+
+ AddStep("turn off autosizing", () => config.Set(OsuSetting.AutoCursorSize, false));
+ AddUntilStep("cursor size correct", () => lastContainer.ActiveCursor.Scale.X == 1);
+
+ AddStep($"set user scale to {userScale}", () => config.Set(OsuSetting.GameplayCursorSize, userScale));
+ AddUntilStep("cursor size correct", () => lastContainer.ActiveCursor.Scale.X == userScale);
+ }
+
+ private void loadContent()
{
SetContents(() => new MovingCursorInputManager
{
- Child = new ClickingCursorContainer
+ Child = lastContainer = new ClickingCursorContainer
{
RelativeSizeAxes = Axes.Both,
Masking = true,
diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs
index 75d05b9b6c..a780653796 100644
--- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs
+++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs
@@ -126,7 +126,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
private void beginCurve()
{
- BeginPlacement();
+ BeginPlacement(commitStart: true);
setState(PlacementState.Body);
}
diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Spinners/SpinnerPlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Spinners/SpinnerPlacementBlueprint.cs
index 2c125aa7c3..74b563d922 100644
--- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Spinners/SpinnerPlacementBlueprint.cs
+++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Spinners/SpinnerPlacementBlueprint.cs
@@ -52,7 +52,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners
if (e.Button != MouseButton.Left)
return false;
- BeginPlacement();
+ BeginPlacement(commitStart: true);
piece.FadeTo(1f, 150, Easing.OutQuint);
isPlacingEnd = true;
diff --git a/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditRuleset.cs b/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditRuleset.cs
index 22b4c3e82e..a8719e0aa8 100644
--- a/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditRuleset.cs
+++ b/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditRuleset.cs
@@ -40,6 +40,8 @@ namespace osu.Game.Rulesets.Osu.Edit
if (existing == null)
return;
+ hitObject.RemoveTransform(existing);
+
using (hitObject.BeginAbsoluteSequence(existing.StartTime))
hitObject.FadeOut(editor_hit_object_fade_out_extension).Expire();
break;
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModDifficultyAdjust.cs b/osu.Game.Rulesets.Osu/Mods/OsuModDifficultyAdjust.cs
index 7eee71be81..75de6896a3 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModDifficultyAdjust.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModDifficultyAdjust.cs
@@ -10,7 +10,7 @@ namespace osu.Game.Rulesets.Osu.Mods
{
public class OsuModDifficultyAdjust : ModDifficultyAdjust
{
- [SettingSource("Circle Size", "Override a beatmap's set CS.")]
+ [SettingSource("Circle Size", "Override a beatmap's set CS.", FIRST_SETTING_ORDER - 1)]
public BindableNumber CircleSize { get; } = new BindableFloat
{
Precision = 0.1f,
@@ -20,7 +20,7 @@ namespace osu.Game.Rulesets.Osu.Mods
Value = 5,
};
- [SettingSource("Approach Rate", "Override a beatmap's set AR.")]
+ [SettingSource("Approach Rate", "Override a beatmap's set AR.", LAST_SETTING_ORDER + 1)]
public BindableNumber ApproachRate { get; } = new BindableFloat
{
Precision = 0.1f,
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs
index 649b01c132..d971e777ec 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs
@@ -19,33 +19,46 @@ namespace osu.Game.Rulesets.Osu.Mods
public override string Description => @"You don't need to click. Give your clicking/tapping fingers a break from the heat of things.";
public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(OsuModAutopilot)).ToArray();
+ ///
+ /// How early before a hitobject's start time to trigger a hit.
+ ///
+ private const float relax_leniency = 3;
+
public void Update(Playfield playfield)
{
bool requiresHold = false;
bool requiresHit = false;
- const float relax_leniency = 3;
+ double time = playfield.Clock.CurrentTime;
- foreach (var drawable in playfield.HitObjectContainer.AliveObjects)
+ foreach (var h in playfield.HitObjectContainer.AliveObjects.OfType())
{
- if (!(drawable is DrawableOsuHitObject osuHit))
+ // we are not yet close enough to the object.
+ if (time < h.HitObject.StartTime - relax_leniency)
+ break;
+
+ // already hit or beyond the hittable end time.
+ if (h.IsHit || (h.HitObject is IHasEndTime hasEnd && time > hasEnd.EndTime))
continue;
- double time = osuHit.Clock.CurrentTime;
- double relativetime = time - osuHit.HitObject.StartTime;
-
- if (time < osuHit.HitObject.StartTime - relax_leniency) continue;
-
- if ((osuHit.HitObject is IHasEndTime hasEnd && time > hasEnd.EndTime) || osuHit.IsHit)
- continue;
-
- if (osuHit is DrawableHitCircle && osuHit.IsHovered)
+ switch (h)
{
- Debug.Assert(osuHit.HitObject.HitWindows != null);
- requiresHit |= osuHit.HitObject.HitWindows.CanBeHit(relativetime);
- }
+ case DrawableHitCircle circle:
+ handleHitCircle(circle);
+ break;
- requiresHold |= (osuHit is DrawableSlider slider && (slider.Ball.IsHovered || osuHit.IsHovered)) || osuHit is DrawableSpinner;
+ case DrawableSlider slider:
+ // Handles cases like "2B" beatmaps, where sliders may be overlapping and simply holding is not enough.
+ if (!slider.HeadCircle.IsHit)
+ handleHitCircle(slider.HeadCircle);
+
+ requiresHold |= slider.Ball.IsHovered || h.IsHovered;
+ break;
+
+ case DrawableSpinner _:
+ requiresHold = true;
+ break;
+ }
}
if (requiresHit)
@@ -55,6 +68,15 @@ namespace osu.Game.Rulesets.Osu.Mods
}
addAction(requiresHold);
+
+ void handleHitCircle(DrawableHitCircle circle)
+ {
+ if (!circle.IsHovered)
+ return;
+
+ Debug.Assert(circle.HitObject.HitWindows != null);
+ requiresHit |= circle.HitObject.HitWindows.CanBeHit(time - circle.HitObject.StartTime);
+ }
}
private bool wasHit;
diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorContainer.cs b/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorContainer.cs
index 79b5d1b7f8..28600ef55b 100644
--- a/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorContainer.cs
+++ b/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorContainer.cs
@@ -12,6 +12,7 @@ using osu.Game.Beatmaps;
using osu.Game.Configuration;
using osu.Game.Rulesets.Osu.Configuration;
using osu.Game.Rulesets.UI;
+using osu.Game.Screens.Play;
using osu.Game.Skinning;
using osuTK;
@@ -29,10 +30,10 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
private readonly Drawable cursorTrail;
- public Bindable CursorScale;
+ public Bindable CursorScale = new BindableFloat(1);
+
private Bindable userCursorScale;
private Bindable autoCursorScale;
- private readonly IBindable beatmap = new Bindable();
public OsuCursorContainer()
{
@@ -43,37 +44,16 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
};
}
+ [Resolved(canBeNull: true)]
+ private GameplayBeatmap beatmap { get; set; }
+
+ [Resolved]
+ private OsuConfigManager config { get; set; }
+
[BackgroundDependencyLoader(true)]
- private void load(OsuConfigManager config, OsuRulesetConfigManager rulesetConfig, IBindable beatmap)
+ private void load(OsuConfigManager config, OsuRulesetConfigManager rulesetConfig)
{
rulesetConfig?.BindWith(OsuRulesetSetting.ShowCursorTrail, showTrail);
-
- this.beatmap.BindTo(beatmap);
- this.beatmap.ValueChanged += _ => calculateScale();
-
- userCursorScale = config.GetBindable(OsuSetting.GameplayCursorSize);
- userCursorScale.ValueChanged += _ => calculateScale();
-
- autoCursorScale = config.GetBindable(OsuSetting.AutoCursorSize);
- autoCursorScale.ValueChanged += _ => calculateScale();
-
- CursorScale = new BindableFloat();
- CursorScale.ValueChanged += e => ActiveCursor.Scale = cursorTrail.Scale = new Vector2(e.NewValue);
-
- calculateScale();
- }
-
- private void calculateScale()
- {
- float scale = userCursorScale.Value;
-
- if (autoCursorScale.Value && beatmap.Value != null)
- {
- // if we have a beatmap available, let's get its circle size to figure out an automatic cursor scale modifier.
- scale *= 1f - 0.7f * (1f + beatmap.Value.BeatmapInfo.BaseDifficulty.CircleSize - BeatmapDifficulty.DEFAULT_DIFFICULTY) / BeatmapDifficulty.DEFAULT_DIFFICULTY;
- }
-
- CursorScale.Value = scale;
}
protected override void LoadComplete()
@@ -81,6 +61,46 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
base.LoadComplete();
showTrail.BindValueChanged(v => cursorTrail.FadeTo(v.NewValue ? 1 : 0, 200), true);
+
+ userCursorScale = config.GetBindable(OsuSetting.GameplayCursorSize);
+ userCursorScale.ValueChanged += _ => calculateScale();
+
+ autoCursorScale = config.GetBindable(OsuSetting.AutoCursorSize);
+ autoCursorScale.ValueChanged += _ => calculateScale();
+
+ CursorScale.ValueChanged += e =>
+ {
+ var newScale = new Vector2(e.NewValue);
+
+ ActiveCursor.Scale = newScale;
+ cursorTrail.Scale = newScale;
+ };
+
+ calculateScale();
+ }
+
+ ///
+ /// Get the scale applicable to the ActiveCursor based on a beatmap's circle size.
+ ///
+ public static float GetScaleForCircleSize(float circleSize) =>
+ 1f - 0.7f * (1f + circleSize - BeatmapDifficulty.DEFAULT_DIFFICULTY) / BeatmapDifficulty.DEFAULT_DIFFICULTY;
+
+ private void calculateScale()
+ {
+ float scale = userCursorScale.Value;
+
+ if (autoCursorScale.Value && beatmap != null)
+ {
+ // if we have a beatmap available, let's get its circle size to figure out an automatic cursor scale modifier.
+ scale *= GetScaleForCircleSize(beatmap.BeatmapInfo.BaseDifficulty.CircleSize);
+ }
+
+ CursorScale.Value = scale;
+
+ var newScale = new Vector2(scale);
+
+ ActiveCursor.ScaleTo(newScale, 400, Easing.OutQuint);
+ cursorTrail.Scale = newScale;
}
private int downCount;
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs
index fe14a1ff0a..b5d946d049 100644
--- a/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs
+++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs
@@ -121,10 +121,13 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
room.Playlist.Add(new PlaylistItem
{
- Ruleset = ruleset,
- Beatmap = new BeatmapInfo
+ Ruleset = { Value = ruleset },
+ Beatmap =
{
- Metadata = new BeatmapMetadata()
+ Value = new BeatmapInfo
+ {
+ Metadata = new BeatmapMetadata()
+ }
}
});
}
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchBeatmapPanel.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchBeatmapPanel.cs
index 1e3e06ce7a..f014b08325 100644
--- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchBeatmapPanel.cs
+++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchBeatmapPanel.cs
@@ -7,7 +7,6 @@ using osu.Game.Beatmaps;
using osu.Game.Online.Multiplayer;
using osu.Game.Screens.Multi.Match.Components;
using osu.Framework.Graphics;
-using osu.Framework.Utils;
using osu.Game.Audio;
using osu.Framework.Allocation;
@@ -32,22 +31,11 @@ namespace osu.Game.Tests.Visual.Multiplayer
Origin = Anchor.Centre,
});
- Room.Playlist.Add(new PlaylistItem { Beatmap = new BeatmapInfo { OnlineBeatmapID = 1763072 } });
- Room.Playlist.Add(new PlaylistItem { Beatmap = new BeatmapInfo { OnlineBeatmapID = 2101557 } });
- Room.Playlist.Add(new PlaylistItem { Beatmap = new BeatmapInfo { OnlineBeatmapID = 1973466 } });
- Room.Playlist.Add(new PlaylistItem { Beatmap = new BeatmapInfo { OnlineBeatmapID = 2109801 } });
- Room.Playlist.Add(new PlaylistItem { Beatmap = new BeatmapInfo { OnlineBeatmapID = 1922035 } });
- }
-
- protected override void LoadComplete()
- {
- base.LoadComplete();
-
- AddStep("Select random beatmap", () =>
- {
- Room.CurrentItem.Value = Room.Playlist[RNG.Next(Room.Playlist.Count)];
- previewTrackManager.StopAnyPlaying(this);
- });
+ Room.Playlist.Add(new PlaylistItem { Beatmap = { Value = new BeatmapInfo { OnlineBeatmapID = 1763072 } } });
+ Room.Playlist.Add(new PlaylistItem { Beatmap = { Value = new BeatmapInfo { OnlineBeatmapID = 2101557 } } });
+ Room.Playlist.Add(new PlaylistItem { Beatmap = { Value = new BeatmapInfo { OnlineBeatmapID = 1973466 } } });
+ Room.Playlist.Add(new PlaylistItem { Beatmap = { Value = new BeatmapInfo { OnlineBeatmapID = 2109801 } } });
+ Room.Playlist.Add(new PlaylistItem { Beatmap = { Value = new BeatmapInfo { OnlineBeatmapID = 1922035 } } });
}
}
}
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchHeader.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchHeader.cs
index e42042f2ea..7d7e7f85db 100644
--- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchHeader.cs
+++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchHeader.cs
@@ -23,16 +23,19 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
Room.Playlist.Add(new PlaylistItem
{
- Beatmap = new BeatmapInfo
+ Beatmap =
{
- Metadata = new BeatmapMetadata
+ Value = new BeatmapInfo
{
- Title = "Title",
- Artist = "Artist",
- AuthorString = "Author",
- },
- Version = "Version",
- Ruleset = new OsuRuleset().RulesetInfo
+ Metadata = new BeatmapMetadata
+ {
+ Title = "Title",
+ Artist = "Artist",
+ AuthorString = "Author",
+ },
+ Version = "Version",
+ Ruleset = new OsuRuleset().RulesetInfo
+ }
},
RequiredMods =
{
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchInfo.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchInfo.cs
index a6c036a876..6ee9ceb2dd 100644
--- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchInfo.cs
+++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchInfo.cs
@@ -37,16 +37,19 @@ namespace osu.Game.Tests.Visual.Multiplayer
Room.Playlist.Clear();
Room.Playlist.Add(new PlaylistItem
{
- Beatmap = new BeatmapInfo
+ Beatmap =
{
- StarDifficulty = 2.4,
- Ruleset = rulesets.GetRuleset(0),
- Metadata = new BeatmapMetadata
+ Value = new BeatmapInfo
{
- Title = @"My Song",
- Artist = @"VisualTests",
- AuthorString = @"osu!lazer",
- },
+ StarDifficulty = 2.4,
+ Ruleset = rulesets.GetRuleset(0),
+ Metadata = new BeatmapMetadata
+ {
+ Title = @"My Song",
+ Artist = @"VisualTests",
+ AuthorString = @"osu!lazer",
+ },
+ }
}
});
});
@@ -60,16 +63,19 @@ namespace osu.Game.Tests.Visual.Multiplayer
Room.Playlist.Clear();
Room.Playlist.Add(new PlaylistItem
{
- Beatmap = new BeatmapInfo
+ Beatmap =
{
- StarDifficulty = 4.2,
- Ruleset = rulesets.GetRuleset(3),
- Metadata = new BeatmapMetadata
+ Value = new BeatmapInfo
{
- Title = @"Your Song",
- Artist = @"Tester",
- AuthorString = @"Someone",
- },
+ StarDifficulty = 4.2,
+ Ruleset = rulesets.GetRuleset(3),
+ Metadata = new BeatmapMetadata
+ {
+ Title = @"Your Song",
+ Artist = @"Tester",
+ AuthorString = @"Someone",
+ },
+ }
}
});
});
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchParticipants.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchParticipants.cs
index 1ac914e27d..a6f47961e9 100644
--- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchParticipants.cs
+++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchParticipants.cs
@@ -16,7 +16,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
Add(new Participants { RelativeSizeAxes = Axes.Both });
AddStep(@"set max to null", () => Room.MaxParticipants.Value = null);
- AddStep(@"set users", () => Room.Participants.Value = new[]
+ AddStep(@"set users", () => Room.Participants.AddRange(new[]
{
new User
{
@@ -42,10 +42,10 @@ namespace osu.Game.Tests.Visual.Multiplayer
CoverUrl = @"https://assets.ppy.sh/user-profile-covers/5287410/5cfeaa9dd41cbce038ecdc9d781396ed4b0108089170bf7f50492ef8eadeb368.jpeg",
IsSupporter = true,
},
- });
+ }));
AddStep(@"set max", () => Room.MaxParticipants.Value = 10);
- AddStep(@"clear users", () => Room.Participants.Value = System.Array.Empty());
+ AddStep(@"clear users", () => Room.Participants.Clear());
AddStep(@"set max to null", () => Room.MaxParticipants.Value = null);
}
}
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchSettingsOverlay.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchSettingsOverlay.cs
index 8d842fc865..047e9d860d 100644
--- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchSettingsOverlay.cs
+++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchSettingsOverlay.cs
@@ -56,7 +56,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddStep("set name", () => Room.Name.Value = "Room name");
AddAssert("button disabled", () => !settings.ApplyButton.Enabled.Value);
- AddStep("set beatmap", () => Room.Playlist.Add(new PlaylistItem { Beatmap = CreateBeatmap(Ruleset.Value).BeatmapInfo }));
+ AddStep("set beatmap", () => Room.Playlist.Add(new PlaylistItem { Beatmap = { Value = CreateBeatmap(Ruleset.Value).BeatmapInfo } }));
AddAssert("button enabled", () => settings.ApplyButton.Enabled.Value);
AddStep("clear name", () => Room.Name.Value = "");
diff --git a/osu.Game.Tests/Visual/Online/TestSceneOnlineViewContainer.cs b/osu.Game.Tests/Visual/Online/TestSceneOnlineViewContainer.cs
new file mode 100644
index 0000000000..3c2735ca56
--- /dev/null
+++ b/osu.Game.Tests/Visual/Online/TestSceneOnlineViewContainer.cs
@@ -0,0 +1,97 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using NUnit.Framework;
+using osu.Framework.Extensions.Color4Extensions;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Shapes;
+using osu.Game.Graphics;
+using osu.Game.Graphics.Sprites;
+using osu.Game.Graphics.UserInterface;
+using osu.Game.Online;
+using osu.Game.Online.API;
+using osuTK.Graphics;
+
+namespace osu.Game.Tests.Visual.Online
+{
+ [TestFixture]
+ public class TestSceneOnlineViewContainer : OsuTestScene
+ {
+ private readonly TestOnlineViewContainer onlineView;
+
+ public TestSceneOnlineViewContainer()
+ {
+ Child = new Container
+ {
+ RelativeSizeAxes = Axes.Both,
+ Child = onlineView = new TestOnlineViewContainer()
+ };
+ }
+
+ [Test]
+ public void TestOnlineStateVisibility()
+ {
+ AddStep("set status to online", () => ((DummyAPIAccess)API).State = APIState.Online);
+
+ AddUntilStep("children are visible", () => onlineView.ViewTarget.IsPresent);
+ AddUntilStep("loading animation is not visible", () => !onlineView.LoadingAnimation.IsPresent);
+ }
+
+ [Test]
+ public void TestOfflineStateVisibility()
+ {
+ AddStep("set status to offline", () => ((DummyAPIAccess)API).State = APIState.Offline);
+
+ AddUntilStep("children are not visible", () => !onlineView.ViewTarget.IsPresent);
+ AddUntilStep("loading animation is not visible", () => !onlineView.LoadingAnimation.IsPresent);
+ }
+
+ [Test]
+ public void TestConnectingStateVisibility()
+ {
+ AddStep("set status to connecting", () => ((DummyAPIAccess)API).State = APIState.Connecting);
+
+ AddUntilStep("children are not visible", () => !onlineView.ViewTarget.IsPresent);
+ AddUntilStep("loading animation is visible", () => onlineView.LoadingAnimation.IsPresent);
+ }
+
+ [Test]
+ public void TestFailingStateVisibility()
+ {
+ AddStep("set status to failing", () => ((DummyAPIAccess)API).State = APIState.Failing);
+
+ AddUntilStep("children are not visible", () => !onlineView.ViewTarget.IsPresent);
+ AddUntilStep("loading animation is visible", () => onlineView.LoadingAnimation.IsPresent);
+ }
+
+ private class TestOnlineViewContainer : OnlineViewContainer
+ {
+ public new LoadingAnimation LoadingAnimation => base.LoadingAnimation;
+
+ public CompositeDrawable ViewTarget => base.Content;
+
+ public TestOnlineViewContainer()
+ : base(@"Please sign in to view dummy test content")
+ {
+ RelativeSizeAxes = Axes.Both;
+
+ Children = new Drawable[]
+ {
+ new Box
+ {
+ RelativeSizeAxes = Axes.Both,
+ Colour = Color4.Blue.Opacity(0.8f),
+ },
+ new OsuSpriteText
+ {
+ Text = "dummy online content",
+ Font = OsuFont.Default.With(size: 40),
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ }
+ };
+ }
+ }
+ }
+}
diff --git a/osu.Game.Tests/Visual/Online/TestSceneRankingsHeader.cs b/osu.Game.Tests/Visual/Online/TestSceneRankingsHeader.cs
index 898e461bde..1e711b3cd7 100644
--- a/osu.Game.Tests/Visual/Online/TestSceneRankingsHeader.cs
+++ b/osu.Game.Tests/Visual/Online/TestSceneRankingsHeader.cs
@@ -34,25 +34,7 @@ namespace osu.Game.Tests.Visual.Online
{
Current = { BindTarget = scope },
Country = { BindTarget = countryBindable },
- Ruleset = { BindTarget = ruleset },
- Spotlights = new[]
- {
- new Spotlight
- {
- Id = 1,
- Text = "Spotlight 1"
- },
- new Spotlight
- {
- Id = 2,
- Text = "Spotlight 2"
- },
- new Spotlight
- {
- Id = 3,
- Text = "Spotlight 3"
- }
- }
+ Ruleset = { BindTarget = ruleset }
});
var country = new Country
diff --git a/osu.Game.Tests/Visual/Online/TestSceneRankingsSpotlightSelector.cs b/osu.Game.Tests/Visual/Online/TestSceneRankingsSpotlightSelector.cs
index e46c8a4a71..f27ab1e775 100644
--- a/osu.Game.Tests/Visual/Online/TestSceneRankingsSpotlightSelector.cs
+++ b/osu.Game.Tests/Visual/Online/TestSceneRankingsSpotlightSelector.cs
@@ -35,6 +35,12 @@ namespace osu.Game.Tests.Visual.Online
Add(selector = new SpotlightSelector());
}
+ [Test]
+ public void TestVisibility()
+ {
+ AddStep("Toggle Visibility", selector.ToggleVisibility);
+ }
+
[Test]
public void TestLocalSpotlights()
{
diff --git a/osu.Game.Tests/Visual/Online/TestSceneSpotlightsLayout.cs b/osu.Game.Tests/Visual/Online/TestSceneSpotlightsLayout.cs
new file mode 100644
index 0000000000..d025a8d7c2
--- /dev/null
+++ b/osu.Game.Tests/Visual/Online/TestSceneSpotlightsLayout.cs
@@ -0,0 +1,55 @@
+// 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 System.Collections.Generic;
+using osu.Framework.Allocation;
+using osu.Framework.Bindables;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Game.Overlays;
+using osu.Game.Overlays.Rankings;
+using osu.Game.Rulesets;
+using osu.Game.Rulesets.Catch;
+using osu.Game.Rulesets.Mania;
+using osu.Game.Rulesets.Osu;
+using osu.Game.Rulesets.Taiko;
+
+namespace osu.Game.Tests.Visual.Online
+{
+ public class TestSceneSpotlightsLayout : OsuTestScene
+ {
+ public override IReadOnlyList RequiredTypes => new[]
+ {
+ typeof(SpotlightsLayout),
+ typeof(SpotlightSelector),
+ };
+
+ protected override bool UseOnlineAPI => true;
+
+ [Cached]
+ private readonly OverlayColourProvider overlayColour = new OverlayColourProvider(OverlayColourScheme.Green);
+
+ public TestSceneSpotlightsLayout()
+ {
+ var ruleset = new Bindable(new OsuRuleset().RulesetInfo);
+
+ Add(new BasicScrollContainer
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ RelativeSizeAxes = Axes.Both,
+ Width = 0.8f,
+ Child = new SpotlightsLayout
+ {
+ Ruleset = { BindTarget = ruleset }
+ }
+ });
+
+ AddStep("Osu ruleset", () => ruleset.Value = new OsuRuleset().RulesetInfo);
+ AddStep("Mania ruleset", () => ruleset.Value = new ManiaRuleset().RulesetInfo);
+ AddStep("Taiko ruleset", () => ruleset.Value = new TaikoRuleset().RulesetInfo);
+ AddStep("Catch ruleset", () => ruleset.Value = new CatchRuleset().RulesetInfo);
+ }
+ }
+}
diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModDisplay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModDisplay.cs
new file mode 100644
index 0000000000..8168faa106
--- /dev/null
+++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModDisplay.cs
@@ -0,0 +1,40 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using NUnit.Framework;
+using osu.Framework.Graphics;
+using osu.Game.Rulesets.Mods;
+using osu.Game.Rulesets.Osu.Mods;
+using osu.Game.Screens.Play.HUD;
+
+namespace osu.Game.Tests.Visual.UserInterface
+{
+ public class TestSceneModDisplay : OsuTestScene
+ {
+ [TestCase(ExpansionMode.ExpandOnHover)]
+ [TestCase(ExpansionMode.AlwaysExpanded)]
+ [TestCase(ExpansionMode.AlwaysContracted)]
+ public void TestMode(ExpansionMode mode)
+ {
+ AddStep("create mod display", () =>
+ {
+ Child = new ModDisplay
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ ExpansionMode = mode,
+ Current =
+ {
+ Value = new Mod[]
+ {
+ new OsuModHardRock(),
+ new OsuModDoubleTime(),
+ new OsuModDifficultyAdjust(),
+ new OsuModEasy(),
+ }
+ }
+ };
+ });
+ }
+ }
+}
diff --git a/osu.Game.Tests/WaveformTestBeatmap.cs b/osu.Game.Tests/WaveformTestBeatmap.cs
index df6394ed34..53ce5def32 100644
--- a/osu.Game.Tests/WaveformTestBeatmap.cs
+++ b/osu.Game.Tests/WaveformTestBeatmap.cs
@@ -37,9 +37,9 @@ namespace osu.Game.Tests
trackStore = audioManager.GetTrackStore(getZipReader());
}
- protected override void Dispose(bool isDisposing)
+ ~WaveformTestBeatmap()
{
- base.Dispose(isDisposing);
+ // Remove the track store from the audio manager
trackStore?.Dispose();
}
diff --git a/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs b/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs
index f9d71a2a6e..55c5175c5d 100644
--- a/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs
+++ b/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs
@@ -36,8 +36,9 @@ namespace osu.Game.Beatmaps
using (var stream = new LineBufferedReader(store.GetStream(getPathForFile(BeatmapInfo.Path))))
return Decoder.GetDecoder(stream).Decode(stream);
}
- catch
+ catch (Exception e)
{
+ Logger.Error(e, "Beatmap failed to load");
return null;
}
}
@@ -59,8 +60,9 @@ namespace osu.Game.Beatmaps
{
return textureStore.Get(getPathForFile(Metadata.BackgroundFile));
}
- catch
+ catch (Exception e)
{
+ Logger.Error(e, "Background failed to load");
return null;
}
}
@@ -74,8 +76,9 @@ namespace osu.Game.Beatmaps
{
return new VideoSprite(textureStore.GetStream(getPathForFile(Metadata.VideoFile)));
}
- catch
+ catch (Exception e)
{
+ Logger.Error(e, "Video failed to load");
return null;
}
}
@@ -86,8 +89,9 @@ namespace osu.Game.Beatmaps
{
return (trackStore ??= AudioManager.GetTrackStore(store)).Get(getPathForFile(Metadata.AudioFile));
}
- catch
+ catch (Exception e)
{
+ Logger.Error(e, "Track failed to load");
return null;
}
}
@@ -115,8 +119,9 @@ namespace osu.Game.Beatmaps
var trackData = store.GetStream(getPathForFile(Metadata.AudioFile));
return trackData == null ? null : new Waveform(trackData);
}
- catch
+ catch (Exception e)
{
+ Logger.Error(e, "Waveform failed to load");
return null;
}
}
diff --git a/osu.Game/Beatmaps/WorkingBeatmap.cs b/osu.Game/Beatmaps/WorkingBeatmap.cs
index 5dc483b61c..1e1ffad81e 100644
--- a/osu.Game/Beatmaps/WorkingBeatmap.cs
+++ b/osu.Game/Beatmaps/WorkingBeatmap.cs
@@ -17,10 +17,11 @@ using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.UI;
using osu.Game.Skinning;
using osu.Framework.Graphics.Video;
+using osu.Framework.Logging;
namespace osu.Game.Beatmaps
{
- public abstract class WorkingBeatmap : IWorkingBeatmap, IDisposable
+ public abstract class WorkingBeatmap : IWorkingBeatmap
{
public readonly BeatmapInfo BeatmapInfo;
@@ -133,11 +134,29 @@ namespace osu.Game.Beatmaps
return converted;
}
- public override string ToString() => BeatmapInfo.ToString();
+ private CancellationTokenSource loadCancellation = new CancellationTokenSource();
- public bool BeatmapLoaded => beatmapLoadTask?.IsCompleted ?? false;
+ ///
+ /// Beings loading the contents of this asynchronously.
+ ///
+ public void BeginAsyncLoad()
+ {
+ loadBeatmapAsync();
+ }
- public Task LoadBeatmapAsync() => beatmapLoadTask ??= Task.Factory.StartNew(() =>
+ ///
+ /// Cancels the asynchronous loading of the contents of this .
+ ///
+ public void CancelAsyncLoad()
+ {
+ loadCancellation?.Cancel();
+ loadCancellation = new CancellationTokenSource();
+
+ if (beatmapLoadTask?.IsCompleted != true)
+ beatmapLoadTask = null;
+ }
+
+ private Task loadBeatmapAsync() => beatmapLoadTask ??= Task.Factory.StartNew(() =>
{
// Todo: Handle cancellation during beatmap parsing
var b = GetBeatmap() ?? new Beatmap();
@@ -149,7 +168,11 @@ namespace osu.Game.Beatmaps
b.BeatmapInfo = BeatmapInfo;
return b;
- }, beatmapCancellation.Token, TaskCreationOptions.LongRunning, TaskScheduler.Default);
+ }, loadCancellation.Token, TaskCreationOptions.LongRunning, TaskScheduler.Default);
+
+ public override string ToString() => BeatmapInfo.ToString();
+
+ public bool BeatmapLoaded => beatmapLoadTask?.IsCompleted ?? false;
public IBeatmap Beatmap
{
@@ -157,16 +180,25 @@ namespace osu.Game.Beatmaps
{
try
{
- return LoadBeatmapAsync().Result;
+ return loadBeatmapAsync().Result;
}
- catch (TaskCanceledException)
+ catch (AggregateException ae)
{
+ // This is the exception that is generally expected here, which occurs via natural cancellation of the asynchronous load
+ if (ae.InnerExceptions.FirstOrDefault() is TaskCanceledException)
+ return null;
+
+ Logger.Error(ae, "Beatmap failed to load");
+ return null;
+ }
+ catch (Exception e)
+ {
+ Logger.Error(e, "Beatmap failed to load");
return null;
}
}
}
- private readonly CancellationTokenSource beatmapCancellation = new CancellationTokenSource();
protected abstract IBeatmap GetBeatmap();
private Task beatmapLoadTask;
@@ -217,40 +249,11 @@ namespace osu.Game.Beatmaps
///
public virtual void RecycleTrack() => track.Recycle();
- #region Disposal
-
- public void Dispose()
- {
- Dispose(true);
- GC.SuppressFinalize(this);
- }
-
- private bool isDisposed;
-
- protected virtual void Dispose(bool isDisposing)
- {
- if (isDisposed)
- return;
-
- isDisposed = true;
-
- // recycling logic is not here for the time being, as components which use
- // retrieved objects from WorkingBeatmap may not hold a reference to the WorkingBeatmap itself.
- // this should be fine as each retrieved component do have their own finalizers.
-
- // cancelling the beatmap load is safe for now since the retrieval is a synchronous
- // operation. if we add an async retrieval method this may need to be reconsidered.
- beatmapCancellation?.Cancel();
- total_count.Value--;
- }
-
~WorkingBeatmap()
{
- Dispose(false);
+ total_count.Value--;
}
- #endregion
-
public class RecyclableLazy
{
private Lazy lazy;
diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs
index 6ae3c7ac64..ce959e9057 100644
--- a/osu.Game/Configuration/OsuConfigManager.cs
+++ b/osu.Game/Configuration/OsuConfigManager.cs
@@ -20,7 +20,7 @@ namespace osu.Game.Configuration
Set(OsuSetting.Ruleset, 0, 0, int.MaxValue);
Set(OsuSetting.Skin, 0, -1, int.MaxValue);
- Set(OsuSetting.BeatmapDetailTab, BeatmapDetailTab.Details);
+ Set(OsuSetting.BeatmapDetailTab, PlayBeatmapDetailArea.TabType.Details);
Set(OsuSetting.ShowConvertedBeatmaps, true);
Set(OsuSetting.DisplayStarsMinimum, 0.0, 0, 10, 0.1);
diff --git a/osu.Game/Configuration/SettingSourceAttribute.cs b/osu.Game/Configuration/SettingSourceAttribute.cs
index a3788e4582..4bdbb5fc24 100644
--- a/osu.Game/Configuration/SettingSourceAttribute.cs
+++ b/osu.Game/Configuration/SettingSourceAttribute.cs
@@ -3,6 +3,7 @@
using System;
using System.Collections.Generic;
+using System.Linq;
using System.Reflection;
using JetBrains.Annotations;
using osu.Framework.Bindables;
@@ -16,6 +17,10 @@ namespace osu.Game.Configuration
/// An attribute to mark a bindable as being exposed to the user via settings controls.
/// Can be used in conjunction with to automatically create UI controls.
///
+ ///
+ /// All controls with set will be placed first in ascending order.
+ /// All controls with no will come afterward in default order.
+ ///
[MeansImplicitUse]
[AttributeUsage(AttributeTargets.Property)]
public class SettingSourceAttribute : Attribute
@@ -24,18 +29,26 @@ namespace osu.Game.Configuration
public string Description { get; }
+ public int? OrderPosition { get; }
+
public SettingSourceAttribute(string label, string description = null)
{
Label = label ?? string.Empty;
Description = description ?? string.Empty;
}
+
+ public SettingSourceAttribute(string label, string description, int orderPosition)
+ : this(label, description)
+ {
+ OrderPosition = orderPosition;
+ }
}
public static class SettingSourceExtensions
{
public static IEnumerable CreateSettingsControls(this object obj)
{
- foreach (var (attr, property) in obj.GetSettingsSourceProperties())
+ foreach (var (attr, property) in obj.GetOrderedSettingsSourceProperties())
{
object value = property.GetValue(obj);
@@ -116,5 +129,15 @@ namespace osu.Game.Configuration
yield return (attr, property);
}
}
+
+ public static IEnumerable<(SettingSourceAttribute, PropertyInfo)> GetOrderedSettingsSourceProperties(this object obj)
+ {
+ var original = obj.GetSettingsSourceProperties();
+
+ var orderedRelative = original.Where(attr => attr.Item1.OrderPosition != null).OrderBy(attr => attr.Item1.OrderPosition);
+ var unordered = original.Except(orderedRelative);
+
+ return orderedRelative.Concat(unordered);
+ }
}
}
diff --git a/osu.Game/Graphics/OsuColour.cs b/osu.Game/Graphics/OsuColour.cs
index c8298543a1..59dd823266 100644
--- a/osu.Game/Graphics/OsuColour.cs
+++ b/osu.Game/Graphics/OsuColour.cs
@@ -2,6 +2,7 @@
// See the LICENCE file in the repository root for full licence text.
using System;
+using System.Globalization;
using osu.Game.Beatmaps;
using osuTK.Graphics;
@@ -14,41 +15,40 @@ namespace osu.Game.Graphics
public static Color4 FromHex(string hex)
{
- if (hex[0] == '#')
- hex = hex.Substring(1);
+ var hexSpan = hex[0] == '#' ? hex.AsSpan().Slice(1) : hex.AsSpan();
- switch (hex.Length)
+ switch (hexSpan.Length)
{
default:
throw new ArgumentException(@"Invalid hex string length!");
case 3:
return new Color4(
- (byte)(Convert.ToByte(hex.Substring(0, 1), 16) * 17),
- (byte)(Convert.ToByte(hex.Substring(1, 1), 16) * 17),
- (byte)(Convert.ToByte(hex.Substring(2, 1), 16) * 17),
+ (byte)(byte.Parse(hexSpan.Slice(0, 1), NumberStyles.HexNumber) * 17),
+ (byte)(byte.Parse(hexSpan.Slice(1, 1), NumberStyles.HexNumber) * 17),
+ (byte)(byte.Parse(hexSpan.Slice(2, 1), NumberStyles.HexNumber) * 17),
255);
case 6:
return new Color4(
- Convert.ToByte(hex.Substring(0, 2), 16),
- Convert.ToByte(hex.Substring(2, 2), 16),
- Convert.ToByte(hex.Substring(4, 2), 16),
+ byte.Parse(hexSpan.Slice(0, 2), NumberStyles.HexNumber),
+ byte.Parse(hexSpan.Slice(2, 2), NumberStyles.HexNumber),
+ byte.Parse(hexSpan.Slice(4, 2), NumberStyles.HexNumber),
255);
case 4:
return new Color4(
- (byte)(Convert.ToByte(hex.Substring(0, 1), 16) * 17),
- (byte)(Convert.ToByte(hex.Substring(1, 1), 16) * 17),
- (byte)(Convert.ToByte(hex.Substring(2, 1), 16) * 17),
- (byte)(Convert.ToByte(hex.Substring(3, 1), 16) * 17));
+ (byte)(byte.Parse(hexSpan.Slice(0, 1), NumberStyles.HexNumber) * 17),
+ (byte)(byte.Parse(hexSpan.Slice(1, 1), NumberStyles.HexNumber) * 17),
+ (byte)(byte.Parse(hexSpan.Slice(0, 1), NumberStyles.HexNumber) * 17),
+ (byte)(byte.Parse(hexSpan.Slice(0, 1), NumberStyles.HexNumber) * 17));
case 8:
return new Color4(
- Convert.ToByte(hex.Substring(0, 2), 16),
- Convert.ToByte(hex.Substring(2, 2), 16),
- Convert.ToByte(hex.Substring(4, 2), 16),
- Convert.ToByte(hex.Substring(6, 2), 16));
+ byte.Parse(hexSpan.Slice(0, 2), NumberStyles.HexNumber),
+ byte.Parse(hexSpan.Slice(2, 2), NumberStyles.HexNumber),
+ byte.Parse(hexSpan.Slice(4, 2), NumberStyles.HexNumber),
+ byte.Parse(hexSpan.Slice(6, 2), NumberStyles.HexNumber));
}
}
diff --git a/osu.Game/Online/API/DummyAPIAccess.cs b/osu.Game/Online/API/DummyAPIAccess.cs
index 7f23f9b5d5..a1c3475fd9 100644
--- a/osu.Game/Online/API/DummyAPIAccess.cs
+++ b/osu.Game/Online/API/DummyAPIAccess.cs
@@ -33,7 +33,7 @@ namespace osu.Game.Online.API
public APIState State
{
get => state;
- private set
+ set
{
if (state == value)
return;
diff --git a/osu.Game/Online/API/Requests/Responses/APISpotlight.cs b/osu.Game/Online/API/Requests/Responses/APISpotlight.cs
index 3a002e57b2..4f63ebe3df 100644
--- a/osu.Game/Online/API/Requests/Responses/APISpotlight.cs
+++ b/osu.Game/Online/API/Requests/Responses/APISpotlight.cs
@@ -26,6 +26,9 @@ namespace osu.Game.Online.API.Requests.Responses
[JsonProperty(@"end_date")]
public DateTimeOffset EndDate;
+ [JsonProperty(@"participant_count")]
+ public int? Participants;
+
public override string ToString() => Name;
}
}
diff --git a/osu.Game/Online/Leaderboards/Leaderboard.cs b/osu.Game/Online/Leaderboards/Leaderboard.cs
index bd4fedabd4..87c747675a 100644
--- a/osu.Game/Online/Leaderboards/Leaderboard.cs
+++ b/osu.Game/Online/Leaderboards/Leaderboard.cs
@@ -152,7 +152,7 @@ namespace osu.Game.Online.Leaderboards
break;
case PlaceholderState.NotLoggedIn:
- replacePlaceholder(new LoginPlaceholder());
+ replacePlaceholder(new LoginPlaceholder(@"Please sign in to view online leaderboards!"));
break;
case PlaceholderState.NotSupporter:
diff --git a/osu.Game/Online/Leaderboards/LeaderboardScore.cs b/osu.Game/Online/Leaderboards/LeaderboardScore.cs
index c9131883bb..1f52a4481b 100644
--- a/osu.Game/Online/Leaderboards/LeaderboardScore.cs
+++ b/osu.Game/Online/Leaderboards/LeaderboardScore.cs
@@ -55,7 +55,7 @@ namespace osu.Game.Online.Leaderboards
private List statisticsLabels;
- [Resolved]
+ [Resolved(CanBeNull = true)]
private DialogOverlay dialogOverlay { get; set; }
public LeaderboardScore(ScoreInfo score, int rank, bool allowHighlight = true)
diff --git a/osu.Game/Online/Multiplayer/PlaylistItem.cs b/osu.Game/Online/Multiplayer/PlaylistItem.cs
index 5f8edc607b..69e1f0db13 100644
--- a/osu.Game/Online/Multiplayer/PlaylistItem.cs
+++ b/osu.Game/Online/Multiplayer/PlaylistItem.cs
@@ -1,9 +1,10 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-using System.Collections.Generic;
+using System;
using System.Linq;
using Newtonsoft.Json;
+using osu.Framework.Bindables;
using osu.Game.Beatmaps;
using osu.Game.Online.API;
using osu.Game.Online.API.Requests.Responses;
@@ -12,7 +13,7 @@ using osu.Game.Rulesets.Mods;
namespace osu.Game.Online.Multiplayer
{
- public class PlaylistItem
+ public class PlaylistItem : IEquatable
{
[JsonProperty("id")]
public int ID { get; set; }
@@ -24,24 +25,16 @@ namespace osu.Game.Online.Multiplayer
public int RulesetID { get; set; }
[JsonIgnore]
- public BeatmapInfo Beatmap
- {
- get => beatmap;
- set
- {
- beatmap = value;
- BeatmapID = value?.OnlineBeatmapID ?? 0;
- }
- }
+ public readonly Bindable Beatmap = new Bindable();
[JsonIgnore]
- public RulesetInfo Ruleset { get; set; }
+ public readonly Bindable Ruleset = new Bindable();
[JsonIgnore]
- public readonly List AllowedMods = new List();
+ public readonly BindableList AllowedMods = new BindableList();
[JsonIgnore]
- public readonly List RequiredMods = new List();
+ public readonly BindableList RequiredMods = new BindableList();
[JsonProperty("beatmap")]
private APIBeatmap apiBeatmap { get; set; }
@@ -64,16 +57,20 @@ namespace osu.Game.Online.Multiplayer
set => requiredModsBacking = value;
}
- private BeatmapInfo beatmap;
+ public PlaylistItem()
+ {
+ Beatmap.BindValueChanged(beatmap => BeatmapID = beatmap.NewValue?.OnlineBeatmapID ?? 0);
+ Ruleset.BindValueChanged(ruleset => RulesetID = ruleset.NewValue?.ID ?? 0);
+ }
public void MapObjects(BeatmapManager beatmaps, RulesetStore rulesets)
{
// If we don't have an api beatmap, the request occurred as a result of room creation, so we can query the local beatmap instead
// Todo: Is this a bug? Room creation only returns the beatmap ID
- Beatmap = apiBeatmap == null ? beatmaps.QueryBeatmap(b => b.OnlineBeatmapID == BeatmapID) : apiBeatmap.ToBeatmap(rulesets);
- Ruleset = rulesets.GetRuleset(RulesetID);
+ Beatmap.Value = apiBeatmap == null ? beatmaps.QueryBeatmap(b => b.OnlineBeatmapID == BeatmapID) : apiBeatmap.ToBeatmap(rulesets);
+ Ruleset.Value = rulesets.GetRuleset(RulesetID);
- Ruleset rulesetInstance = Ruleset.CreateInstance();
+ Ruleset rulesetInstance = Ruleset.Value.CreateInstance();
if (allowedModsBacking != null)
{
@@ -94,5 +91,14 @@ namespace osu.Game.Online.Multiplayer
public bool ShouldSerializeID() => false;
public bool ShouldSerializeapiBeatmap() => false;
+
+ public bool Equals(PlaylistItem other) => ID == other?.ID && BeatmapID == other.BeatmapID && RulesetID == other.RulesetID;
+
+ public override int GetHashCode()
+ {
+ // ReSharper disable NonReadonlyMemberInGetHashCode
+ return HashCode.Combine(ID, BeatmapID, RulesetID);
+ // ReSharper restore NonReadonlyMemberInGetHashCode
+ }
}
}
diff --git a/osu.Game/Online/Multiplayer/Room.cs b/osu.Game/Online/Multiplayer/Room.cs
index 53089897f7..400afb39a1 100644
--- a/osu.Game/Online/Multiplayer/Room.cs
+++ b/osu.Game/Online/Multiplayer/Room.cs
@@ -2,7 +2,6 @@
// See the LICENCE file in the repository root for full licence text.
using System;
-using System.Collections.Generic;
using System.Linq;
using Newtonsoft.Json;
using osu.Framework.Allocation;
@@ -31,10 +30,6 @@ namespace osu.Game.Online.Multiplayer
[JsonProperty("playlist")]
public BindableList Playlist { get; private set; } = new BindableList();
- [Cached]
- [JsonIgnore]
- public Bindable CurrentItem { get; private set; } = new Bindable();
-
[Cached]
[JsonProperty("channel_id")]
public Bindable ChannelId { get; private set; } = new Bindable();
@@ -65,23 +60,11 @@ namespace osu.Game.Online.Multiplayer
[Cached]
[JsonIgnore]
- public Bindable> Participants { get; private set; } = new Bindable>(Enumerable.Empty());
+ public BindableList Participants { get; private set; } = new BindableList();
[Cached]
public Bindable ParticipantCount { get; private set; } = new Bindable();
- public Room()
- {
- Playlist.ItemsAdded += updateCurrent;
- Playlist.ItemsRemoved += updateCurrent;
- updateCurrent(Playlist);
- }
-
- private void updateCurrent(IEnumerable playlist)
- {
- CurrentItem.Value = playlist.FirstOrDefault();
- }
-
// todo: TEMPORARY
[JsonProperty("participant_count")]
private int? participantCount
@@ -130,17 +113,18 @@ namespace osu.Game.Online.Multiplayer
Type.Value = other.Type.Value;
MaxParticipants.Value = other.MaxParticipants.Value;
ParticipantCount.Value = other.ParticipantCount.Value;
- Participants.Value = other.Participants.Value.ToArray();
EndDate.Value = other.EndDate.Value;
if (DateTimeOffset.Now >= EndDate.Value)
Status.Value = new RoomStatusEnded();
- // Todo: Temporary, should only remove/add new items (requires framework changes)
- if (Playlist.Count == 0)
- Playlist.AddRange(other.Playlist);
- else if (other.Playlist.Count > 0)
- Playlist.First().ID = other.Playlist.First().ID;
+ foreach (var removedItem in Playlist.Except(other.Playlist).ToArray())
+ Playlist.Remove(removedItem);
+ Playlist.AddRange(other.Playlist.Except(Playlist).ToArray());
+
+ foreach (var removedItem in Participants.Except(other.Participants).ToArray())
+ Participants.Remove(removedItem);
+ Participants.AddRange(other.Participants.Except(Participants).ToArray());
Position = other.Position;
}
diff --git a/osu.Game/Online/OnlineViewContainer.cs b/osu.Game/Online/OnlineViewContainer.cs
new file mode 100644
index 0000000000..689c1c0afb
--- /dev/null
+++ b/osu.Game/Online/OnlineViewContainer.cs
@@ -0,0 +1,100 @@
+// 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.Allocation;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Game.Graphics.UserInterface;
+using osu.Game.Online.API;
+using osu.Game.Online.Placeholders;
+
+namespace osu.Game.Online
+{
+ ///
+ /// A for displaying online content which require a local user to be logged in.
+ /// Shows its children only when the local user is logged in and supports displaying a placeholder if not.
+ ///
+ public abstract class OnlineViewContainer : Container, IOnlineComponent
+ {
+ protected LoadingAnimation LoadingAnimation { get; private set; }
+
+ protected override Container Content { get; } = new Container { RelativeSizeAxes = Axes.Both };
+
+ private readonly string placeholderMessage;
+
+ private Placeholder placeholder;
+
+ private const double transform_duration = 300;
+
+ [Resolved]
+ protected IAPIProvider API { get; private set; }
+
+ protected OnlineViewContainer(string placeholderMessage)
+ {
+ this.placeholderMessage = placeholderMessage;
+ }
+
+ [BackgroundDependencyLoader]
+ private void load()
+ {
+ InternalChildren = new Drawable[]
+ {
+ Content,
+ placeholder = new LoginPlaceholder(placeholderMessage),
+ LoadingAnimation = new LoadingAnimation
+ {
+ Alpha = 0,
+ }
+ };
+ }
+
+ protected override void LoadComplete()
+ {
+ base.LoadComplete();
+
+ API.Register(this);
+ }
+
+ public virtual void APIStateChanged(IAPIProvider api, APIState state)
+ {
+ switch (state)
+ {
+ case APIState.Offline:
+ PopContentOut(Content);
+ placeholder.ScaleTo(0.8f).Then().ScaleTo(1, 3 * transform_duration, Easing.OutQuint);
+ placeholder.FadeInFromZero(2 * transform_duration, Easing.OutQuint);
+ LoadingAnimation.Hide();
+ break;
+
+ case APIState.Online:
+ PopContentIn(Content);
+ placeholder.FadeOut(transform_duration / 2, Easing.OutQuint);
+ LoadingAnimation.Hide();
+ break;
+
+ case APIState.Failing:
+ case APIState.Connecting:
+ PopContentOut(Content);
+ LoadingAnimation.Show();
+ placeholder.FadeOut(transform_duration / 2, Easing.OutQuint);
+ break;
+ }
+ }
+
+ ///
+ /// Applies a transform to the online content to make it hidden.
+ ///
+ protected virtual void PopContentOut(Drawable content) => content.FadeOut(transform_duration / 2, Easing.OutQuint);
+
+ ///
+ /// Applies a transform to the online content to make it visible.
+ ///
+ protected virtual void PopContentIn(Drawable content) => content.FadeIn(transform_duration, Easing.OutQuint);
+
+ protected override void Dispose(bool isDisposing)
+ {
+ API?.Unregister(this);
+ base.Dispose(isDisposing);
+ }
+ }
+}
diff --git a/osu.Game/Online/Placeholders/LoginPlaceholder.cs b/osu.Game/Online/Placeholders/LoginPlaceholder.cs
index 591eb976e2..73b0fa27c3 100644
--- a/osu.Game/Online/Placeholders/LoginPlaceholder.cs
+++ b/osu.Game/Online/Placeholders/LoginPlaceholder.cs
@@ -14,7 +14,7 @@ namespace osu.Game.Online.Placeholders
[Resolved(CanBeNull = true)]
private LoginOverlay login { get; set; }
- public LoginPlaceholder()
+ public LoginPlaceholder(string actionMessage)
{
AddIcon(FontAwesome.Solid.UserLock, cp =>
{
@@ -22,7 +22,7 @@ namespace osu.Game.Online.Placeholders
cp.Padding = new MarginPadding { Right = 10 };
});
- AddText(@"Please sign in to view online leaderboards!");
+ AddText(actionMessage);
}
protected override bool OnMouseDown(MouseDownEvent e)
diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs
index 79616ef97c..e7fffd49b4 100644
--- a/osu.Game/OsuGame.cs
+++ b/osu.Game/OsuGame.cs
@@ -401,15 +401,14 @@ namespace osu.Game
if (nextBeatmap?.Track != null)
nextBeatmap.Track.Completed += currentTrackCompleted;
- using (var oldBeatmap = beatmap.OldValue)
- {
- if (oldBeatmap?.Track != null)
- oldBeatmap.Track.Completed -= currentTrackCompleted;
- }
+ var oldBeatmap = beatmap.OldValue;
+ if (oldBeatmap?.Track != null)
+ oldBeatmap.Track.Completed -= currentTrackCompleted;
updateModDefaults();
- nextBeatmap?.LoadBeatmapAsync();
+ oldBeatmap?.CancelAsyncLoad();
+ nextBeatmap?.BeginAsyncLoad();
}
private void modsChanged(ValueChangedEvent> mods)
diff --git a/osu.Game/Overlays/Direct/DownloadProgressBar.cs b/osu.Game/Overlays/Direct/DownloadProgressBar.cs
index a6cefaae84..9a8644efd2 100644
--- a/osu.Game/Overlays/Direct/DownloadProgressBar.cs
+++ b/osu.Game/Overlays/Direct/DownloadProgressBar.cs
@@ -19,7 +19,7 @@ namespace osu.Game.Overlays.Direct
public DownloadProgressBar(BeatmapSetInfo beatmapSet)
: base(beatmapSet)
{
- AddInternal(progressBar = new ProgressBar
+ AddInternal(progressBar = new InteractionDisabledProgressBar
{
Height = 0,
Alpha = 0,
@@ -64,5 +64,11 @@ namespace osu.Game.Overlays.Direct
}
}, true);
}
+
+ private class InteractionDisabledProgressBar : ProgressBar
+ {
+ public override bool HandlePositionalInput => false;
+ public override bool HandleNonPositionalInput => false;
+ }
}
}
diff --git a/osu.Game/Overlays/Rankings/RankingsOverlayHeader.cs b/osu.Game/Overlays/Rankings/RankingsOverlayHeader.cs
index 94afe4e5a5..2674b3a81e 100644
--- a/osu.Game/Overlays/Rankings/RankingsOverlayHeader.cs
+++ b/osu.Game/Overlays/Rankings/RankingsOverlayHeader.cs
@@ -6,25 +6,14 @@ using osu.Framework.Bindables;
using osu.Game.Graphics.UserInterface;
using osu.Game.Rulesets;
using osu.Game.Users;
-using System.Collections.Generic;
-using osu.Framework.Graphics.Containers;
-using osu.Framework.Graphics.Shapes;
-using osu.Framework.Allocation;
namespace osu.Game.Overlays.Rankings
{
public class RankingsOverlayHeader : TabControlOverlayHeader
{
public readonly Bindable Ruleset = new Bindable();
- public readonly Bindable Spotlight = new Bindable();
public readonly Bindable Country = new Bindable();
- public IEnumerable Spotlights
- {
- get => spotlightsContainer.Spotlights;
- set => spotlightsContainer.Spotlights = value;
- }
-
protected override ScreenTitle CreateTitle() => new RankingsTitle
{
Scope = { BindTarget = Current }
@@ -35,35 +24,11 @@ namespace osu.Game.Overlays.Rankings
Current = Ruleset
};
- private SpotlightsContainer spotlightsContainer;
-
- protected override Drawable CreateContent() => new FillFlowContainer
+ protected override Drawable CreateContent() => new CountryFilter
{
- RelativeSizeAxes = Axes.X,
- AutoSizeAxes = Axes.Y,
- Direction = FillDirection.Vertical,
- Children = new Drawable[]
- {
- new CountryFilter
- {
- Current = Country
- },
- spotlightsContainer = new SpotlightsContainer
- {
- Spotlight = { BindTarget = Spotlight }
- }
- }
+ Current = Country
};
- protected override void LoadComplete()
- {
- Current.BindValueChanged(onCurrentChanged, true);
- base.LoadComplete();
- }
-
- private void onCurrentChanged(ValueChangedEvent scope) =>
- spotlightsContainer.FadeTo(scope.NewValue == RankingsScope.Spotlights ? 1 : 0, 200, Easing.OutQuint);
-
private class RankingsTitle : ScreenTitle
{
public readonly Bindable Scope = new Bindable();
@@ -81,48 +46,6 @@ namespace osu.Game.Overlays.Rankings
protected override Drawable CreateIcon() => new ScreenTitleTextureIcon(@"Icons/rankings");
}
-
- private class SpotlightsContainer : CompositeDrawable
- {
- public readonly Bindable Spotlight = new Bindable();
-
- public IEnumerable Spotlights
- {
- get => dropdown.Items;
- set => dropdown.Items = value;
- }
-
- private readonly OsuDropdown dropdown;
- private readonly Box background;
-
- public SpotlightsContainer()
- {
- Height = 100;
- RelativeSizeAxes = Axes.X;
- InternalChildren = new Drawable[]
- {
- background = new Box
- {
- RelativeSizeAxes = Axes.Both,
- },
- dropdown = new OsuDropdown
- {
- Anchor = Anchor.TopCentre,
- Origin = Anchor.TopCentre,
- RelativeSizeAxes = Axes.X,
- Width = 0.8f,
- Current = Spotlight,
- Y = 20,
- }
- };
- }
-
- [BackgroundDependencyLoader]
- private void load(OverlayColourProvider colourProvider)
- {
- background.Colour = colourProvider.Dark3;
- }
- }
}
public enum RankingsScope
diff --git a/osu.Game/Overlays/Rankings/Spotlight.cs b/osu.Game/Overlays/Rankings/Spotlight.cs
deleted file mode 100644
index e956b4f449..0000000000
--- a/osu.Game/Overlays/Rankings/Spotlight.cs
+++ /dev/null
@@ -1,18 +0,0 @@
-// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
-// See the LICENCE file in the repository root for full licence text.
-
-using Newtonsoft.Json;
-
-namespace osu.Game.Overlays.Rankings
-{
- public class Spotlight
- {
- [JsonProperty("id")]
- public int Id;
-
- [JsonProperty("text")]
- public string Text;
-
- public override string ToString() => Text;
- }
-}
diff --git a/osu.Game/Overlays/Rankings/SpotlightSelector.cs b/osu.Game/Overlays/Rankings/SpotlightSelector.cs
index e34c01113e..f019b50ae8 100644
--- a/osu.Game/Overlays/Rankings/SpotlightSelector.cs
+++ b/osu.Game/Overlays/Rankings/SpotlightSelector.cs
@@ -14,11 +14,14 @@ using osuTK;
using System;
using System.Collections.Generic;
using osu.Framework.Graphics.UserInterface;
+using osu.Game.Online.API.Requests;
namespace osu.Game.Overlays.Rankings
{
- public class SpotlightSelector : CompositeDrawable, IHasCurrentValue
+ public class SpotlightSelector : VisibilityContainer, IHasCurrentValue
{
+ private const int duration = 300;
+
private readonly Box background;
private readonly SpotlightsDropdown dropdown;
@@ -36,50 +39,60 @@ namespace osu.Game.Overlays.Rankings
set => dropdown.Items = value;
}
+ protected override bool StartHidden => true;
+
private readonly InfoColumn startDateColumn;
private readonly InfoColumn endDateColumn;
+ private readonly InfoColumn mapCountColumn;
+ private readonly InfoColumn participantsColumn;
+ private readonly Container content;
public SpotlightSelector()
{
RelativeSizeAxes = Axes.X;
Height = 100;
-
- InternalChildren = new Drawable[]
+ Add(content = new Container
{
- background = new Box
+ RelativeSizeAxes = Axes.Both,
+ Children = new Drawable[]
{
- RelativeSizeAxes = Axes.Both,
- },
- new Container
- {
- RelativeSizeAxes = Axes.Both,
- Padding = new MarginPadding { Horizontal = UserProfileOverlay.CONTENT_X_MARGIN, Vertical = 10 },
- Children = new Drawable[]
+ background = new Box
{
- dropdown = new SpotlightsDropdown
+ RelativeSizeAxes = Axes.Both,
+ },
+ new Container
+ {
+ RelativeSizeAxes = Axes.Both,
+ Padding = new MarginPadding { Horizontal = UserProfileOverlay.CONTENT_X_MARGIN, Vertical = 10 },
+ Children = new Drawable[]
{
- Anchor = Anchor.TopCentre,
- Origin = Anchor.TopCentre,
- RelativeSizeAxes = Axes.X,
- Current = Current,
- Depth = -float.MaxValue
- },
- new FillFlowContainer
- {
- Anchor = Anchor.BottomRight,
- Origin = Anchor.BottomRight,
- AutoSizeAxes = Axes.Both,
- Direction = FillDirection.Horizontal,
- Spacing = new Vector2(15, 0),
- Children = new Drawable[]
+ dropdown = new SpotlightsDropdown
{
- startDateColumn = new InfoColumn(@"Start Date"),
- endDateColumn = new InfoColumn(@"End Date"),
+ Anchor = Anchor.TopCentre,
+ Origin = Anchor.TopCentre,
+ RelativeSizeAxes = Axes.X,
+ Current = Current,
+ Depth = -float.MaxValue
+ },
+ new FillFlowContainer
+ {
+ Anchor = Anchor.BottomRight,
+ Origin = Anchor.BottomRight,
+ AutoSizeAxes = Axes.Both,
+ Direction = FillDirection.Horizontal,
+ Spacing = new Vector2(15, 0),
+ Children = new Drawable[]
+ {
+ startDateColumn = new InfoColumn(@"Start Date"),
+ endDateColumn = new InfoColumn(@"End Date"),
+ mapCountColumn = new InfoColumn(@"Map Count"),
+ participantsColumn = new InfoColumn(@"Participants")
+ }
}
}
}
- },
- };
+ }
+ });
}
[BackgroundDependencyLoader]
@@ -88,18 +101,17 @@ namespace osu.Game.Overlays.Rankings
background.Colour = colourProvider.Dark3;
}
- protected override void LoadComplete()
+ public void ShowInfo(GetSpotlightRankingsResponse response)
{
- base.LoadComplete();
-
- Current.BindValueChanged(onCurrentChanged);
+ startDateColumn.Value = dateToString(response.Spotlight.StartDate);
+ endDateColumn.Value = dateToString(response.Spotlight.EndDate);
+ mapCountColumn.Value = response.BeatmapSets.Count.ToString();
+ participantsColumn.Value = response.Spotlight.Participants?.ToString("N0");
}
- private void onCurrentChanged(ValueChangedEvent spotlight)
- {
- startDateColumn.Value = dateToString(spotlight.NewValue.StartDate);
- endDateColumn.Value = dateToString(spotlight.NewValue.EndDate);
- }
+ protected override void PopIn() => content.FadeIn(duration, Easing.OutQuint);
+
+ protected override void PopOut() => content.FadeOut(duration, Easing.OutQuint);
private string dateToString(DateTimeOffset date) => date.ToString("yyyy-MM-dd");
diff --git a/osu.Game/Overlays/Rankings/SpotlightsLayout.cs b/osu.Game/Overlays/Rankings/SpotlightsLayout.cs
new file mode 100644
index 0000000000..33811cc982
--- /dev/null
+++ b/osu.Game/Overlays/Rankings/SpotlightsLayout.cs
@@ -0,0 +1,161 @@
+// 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.Bindables;
+using osu.Game.Rulesets;
+using osu.Framework.Graphics.Containers;
+using osu.Game.Online.API.Requests.Responses;
+using osuTK;
+using osu.Framework.Allocation;
+using osu.Game.Online.API;
+using osu.Game.Online.API.Requests;
+using osu.Game.Overlays.Rankings.Tables;
+using System.Linq;
+using osu.Game.Overlays.Direct;
+using System.Threading;
+using osu.Game.Graphics.Containers;
+using osu.Game.Graphics.UserInterface;
+
+namespace osu.Game.Overlays.Rankings
+{
+ public class SpotlightsLayout : CompositeDrawable
+ {
+ public readonly Bindable Ruleset = new Bindable();
+
+ private readonly Bindable selectedSpotlight = new Bindable();
+
+ [Resolved]
+ private IAPIProvider api { get; set; }
+
+ [Resolved]
+ private RulesetStore rulesets { get; set; }
+
+ private CancellationTokenSource cancellationToken;
+ private GetSpotlightRankingsRequest getRankingsRequest;
+ private GetSpotlightsRequest spotlightsRequest;
+
+ private SpotlightSelector selector;
+ private Container content;
+ private DimmedLoadingLayer loading;
+
+ [BackgroundDependencyLoader]
+ private void load()
+ {
+ RelativeSizeAxes = Axes.X;
+ AutoSizeAxes = Axes.Y;
+ InternalChild = new ReverseChildIDFillFlowContainer
+ {
+ RelativeSizeAxes = Axes.X,
+ AutoSizeAxes = Axes.Y,
+ Direction = FillDirection.Vertical,
+ Children = new Drawable[]
+ {
+ selector = new SpotlightSelector
+ {
+ Current = selectedSpotlight,
+ },
+ new Container
+ {
+ RelativeSizeAxes = Axes.X,
+ AutoSizeAxes = Axes.Y,
+ Children = new Drawable[]
+ {
+ content = new Container
+ {
+ RelativeSizeAxes = Axes.X,
+ AutoSizeAxes = Axes.Y,
+ Margin = new MarginPadding { Vertical = 10 }
+ },
+ loading = new DimmedLoadingLayer()
+ }
+ }
+ }
+ };
+ }
+
+ protected override void LoadComplete()
+ {
+ base.LoadComplete();
+
+ selector.Show();
+
+ selectedSpotlight.BindValueChanged(onSpotlightChanged);
+ Ruleset.BindValueChanged(onRulesetChanged);
+
+ getSpotlights();
+ }
+
+ private void getSpotlights()
+ {
+ spotlightsRequest = new GetSpotlightsRequest();
+ spotlightsRequest.Success += response => selector.Spotlights = response.Spotlights;
+ api.Queue(spotlightsRequest);
+ }
+
+ private void onRulesetChanged(ValueChangedEvent ruleset)
+ {
+ if (!selector.Spotlights.Any())
+ return;
+
+ selectedSpotlight.TriggerChange();
+ }
+
+ private void onSpotlightChanged(ValueChangedEvent spotlight)
+ {
+ loading.Show();
+
+ cancellationToken?.Cancel();
+ getRankingsRequest?.Cancel();
+
+ getRankingsRequest = new GetSpotlightRankingsRequest(Ruleset.Value, spotlight.NewValue.Id);
+ getRankingsRequest.Success += onSuccess;
+ api.Queue(getRankingsRequest);
+ }
+
+ private void onSuccess(GetSpotlightRankingsResponse response)
+ {
+ LoadComponentAsync(createContent(response), loaded =>
+ {
+ selector.ShowInfo(response);
+
+ content.Clear();
+ content.Add(loaded);
+
+ loading.Hide();
+ }, (cancellationToken = new CancellationTokenSource()).Token);
+ }
+
+ private Drawable createContent(GetSpotlightRankingsResponse response) => new FillFlowContainer
+ {
+ AutoSizeAxes = Axes.Y,
+ RelativeSizeAxes = Axes.X,
+ Direction = FillDirection.Vertical,
+ Spacing = new Vector2(0, 20),
+ Children = new Drawable[]
+ {
+ new ScoresTable(1, response.Users),
+ new FillFlowContainer
+ {
+ AutoSizeAxes = Axes.Y,
+ RelativeSizeAxes = Axes.X,
+ Spacing = new Vector2(10),
+ Children = response.BeatmapSets.Select(b => new DirectGridPanel(b.ToBeatmapSet(rulesets))
+ {
+ Anchor = Anchor.TopCentre,
+ Origin = Anchor.TopCentre,
+ }).ToList()
+ }
+ }
+ };
+
+ protected override void Dispose(bool isDisposing)
+ {
+ base.Dispose(isDisposing);
+
+ spotlightsRequest?.Cancel();
+ getRankingsRequest?.Cancel();
+ cancellationToken?.Cancel();
+ }
+ }
+}
diff --git a/osu.Game/Overlays/RankingsOverlay.cs b/osu.Game/Overlays/RankingsOverlay.cs
index 84470d9caa..f3215d07fa 100644
--- a/osu.Game/Overlays/RankingsOverlay.cs
+++ b/osu.Game/Overlays/RankingsOverlay.cs
@@ -24,7 +24,7 @@ namespace osu.Game.Overlays
private readonly Bindable ruleset = new Bindable();
private readonly BasicScrollContainer scrollFlow;
- private readonly Container tableContainer;
+ private readonly Container contentContainer;
private readonly DimmedLoadingLayer loading;
private readonly Box background;
@@ -69,13 +69,13 @@ namespace osu.Game.Overlays
AutoSizeAxes = Axes.Y,
Children = new Drawable[]
{
- tableContainer = new Container
+ contentContainer = new Container
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
AutoSizeAxes = Axes.Y,
RelativeSizeAxes = Axes.X,
- Margin = new MarginPadding { Vertical = 10 }
+ Margin = new MarginPadding { Bottom = 10 }
},
loading = new DimmedLoadingLayer(),
}
@@ -112,7 +112,13 @@ namespace osu.Game.Overlays
Scheduler.AddOnce(loadNewContent);
}, true);
- ruleset.BindValueChanged(_ => Scheduler.AddOnce(loadNewContent), true);
+ ruleset.BindValueChanged(_ =>
+ {
+ if (Scope.Value == RankingsScope.Spotlights)
+ return;
+
+ Scheduler.AddOnce(loadNewContent);
+ }, true);
base.LoadComplete();
}
@@ -134,17 +140,26 @@ namespace osu.Game.Overlays
cancellationToken?.Cancel();
lastRequest?.Cancel();
+ if (Scope.Value == RankingsScope.Spotlights)
+ {
+ loadContent(new SpotlightsLayout
+ {
+ Ruleset = { BindTarget = ruleset }
+ });
+ return;
+ }
+
var request = createScopedRequest();
lastRequest = request;
if (request == null)
{
- loadTable(null);
+ loadContent(null);
return;
}
- request.Success += () => loadTable(createTableFromResponse(request));
- request.Failure += _ => loadTable(null);
+ request.Success += () => loadContent(createTableFromResponse(request));
+ request.Failure += _ => loadContent(null);
api.Queue(request);
}
@@ -189,21 +204,21 @@ namespace osu.Game.Overlays
return null;
}
- private void loadTable(Drawable table)
+ private void loadContent(Drawable content)
{
scrollFlow.ScrollToStart();
- if (table == null)
+ if (content == null)
{
- tableContainer.Clear();
+ contentContainer.Clear();
loading.Hide();
return;
}
- LoadComponentAsync(table, t =>
+ LoadComponentAsync(content, loaded =>
{
loading.Hide();
- tableContainer.Child = table;
+ contentContainer.Child = loaded;
}, (cancellationToken = new CancellationTokenSource()).Token);
}
}
diff --git a/osu.Game/Rulesets/Edit/PlacementBlueprint.cs b/osu.Game/Rulesets/Edit/PlacementBlueprint.cs
index 24fa96e1c5..ea77a6091a 100644
--- a/osu.Game/Rulesets/Edit/PlacementBlueprint.cs
+++ b/osu.Game/Rulesets/Edit/PlacementBlueprint.cs
@@ -1,8 +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 System;
-using osu.Framework;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
@@ -20,17 +18,12 @@ namespace osu.Game.Rulesets.Edit
///
/// A blueprint which governs the creation of a new to actualisation.
///
- public abstract class PlacementBlueprint : CompositeDrawable, IStateful
+ public abstract class PlacementBlueprint : CompositeDrawable
{
///
- /// Invoked when has changed.
+ /// Whether the is currently mid-placement, but has not necessarily finished being placed.
///
- public event Action StateChanged;
-
- ///
- /// Whether the is currently being placed, but has not necessarily finished being placed.
- ///
- public bool PlacementBegun { get; private set; }
+ public bool PlacementActive { get; private set; }
///
/// The that is being placed.
@@ -53,8 +46,6 @@ namespace osu.Game.Rulesets.Edit
// This is required to allow the blueprint's position to be updated via OnMouseMove/Handle
// on the same frame it is made visible via a PlacementState change.
AlwaysPresent = true;
-
- Alpha = 0;
}
[BackgroundDependencyLoader]
@@ -67,48 +58,29 @@ namespace osu.Game.Rulesets.Edit
ApplyDefaultsToHitObject();
}
- private PlacementState state;
-
- public PlacementState State
- {
- get => state;
- set
- {
- if (state == value)
- return;
-
- state = value;
-
- if (state == PlacementState.Shown)
- Show();
- else
- Hide();
-
- StateChanged?.Invoke(value);
- }
- }
-
///
/// Signals that the placement of has started.
///
/// The start time of at the placement point. If null, the current clock time is used.
- protected void BeginPlacement(double? startTime = null)
+ /// Whether this call is committing a value for HitObject.StartTime and continuing with further adjustments.
+ protected void BeginPlacement(double? startTime = null, bool commitStart = false)
{
HitObject.StartTime = startTime ?? EditorClock.CurrentTime;
placementHandler.BeginPlacement(HitObject);
- PlacementBegun = true;
+ PlacementActive |= commitStart;
}
///
/// Signals that the placement of has finished.
- /// This will destroy this , and add the to the .
+ /// This will destroy this , and add the HitObject.StartTime to the .
///
/// Whether the object should be committed.
public void EndPlacement(bool commit)
{
- if (!PlacementBegun)
+ if (!PlacementActive)
BeginPlacement();
placementHandler.EndPlacement(HitObject, commit);
+ PlacementActive = false;
}
///
@@ -142,10 +114,4 @@ namespace osu.Game.Rulesets.Edit
}
}
}
-
- public enum PlacementState
- {
- Hidden,
- Shown,
- }
}
diff --git a/osu.Game/Rulesets/Mods/ModDifficultyAdjust.cs b/osu.Game/Rulesets/Mods/ModDifficultyAdjust.cs
index d74e2ce2bc..2083671072 100644
--- a/osu.Game/Rulesets/Mods/ModDifficultyAdjust.cs
+++ b/osu.Game/Rulesets/Mods/ModDifficultyAdjust.cs
@@ -28,7 +28,11 @@ namespace osu.Game.Rulesets.Mods
public override Type[] IncompatibleMods => new[] { typeof(ModEasy), typeof(ModHardRock) };
- [SettingSource("Drain Rate", "Override a beatmap's set HP.")]
+ protected const int FIRST_SETTING_ORDER = 1;
+
+ protected const int LAST_SETTING_ORDER = 2;
+
+ [SettingSource("HP Drain", "Override a beatmap's set HP.", FIRST_SETTING_ORDER)]
public BindableNumber DrainRate { get; } = new BindableFloat
{
Precision = 0.1f,
@@ -38,7 +42,7 @@ namespace osu.Game.Rulesets.Mods
Value = 5,
};
- [SettingSource("Overall Difficulty", "Override a beatmap's set OD.")]
+ [SettingSource("Accuracy", "Override a beatmap's set OD.", LAST_SETTING_ORDER)]
public BindableNumber OverallDifficulty { get; } = new BindableFloat
{
Precision = 0.1f,
diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs
index e391157b5b..d3a0b3450f 100644
--- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs
+++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs
@@ -116,7 +116,7 @@ namespace osu.Game.Rulesets.Objects.Drawables
HitObject.DefaultsApplied += onDefaultsApplied;
startTimeBindable = HitObject.StartTimeBindable.GetBoundCopy();
- startTimeBindable.BindValueChanged(_ => updateState(ArmedState.Idle, true));
+ startTimeBindable.BindValueChanged(_ => updateState(State.Value, true));
if (HitObject is IHasComboInformation combo)
{
@@ -250,8 +250,8 @@ namespace osu.Game.Rulesets.Objects.Drawables
double transformTime = HitObject.StartTime - InitialLifetimeOffset;
- base.ApplyTransformsAt(transformTime, true);
- base.ClearTransformsAfter(transformTime, true);
+ base.ApplyTransformsAt(double.MinValue, true);
+ base.ClearTransformsAfter(double.MinValue, true);
using (BeginAbsoluteSequence(transformTime, true))
{
diff --git a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs
index b257688568..0eb77a8561 100644
--- a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs
+++ b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs
@@ -7,7 +7,6 @@ using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Input;
-using osu.Framework.Input.Events;
using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Edit.Tools;
using osu.Game.Rulesets.Objects;
@@ -62,20 +61,8 @@ namespace osu.Game.Screens.Edit.Compose.Components
///
private void refreshTool()
{
- placementBlueprintContainer.Clear();
-
- currentPlacement?.EndPlacement(false);
- currentPlacement = null;
-
- var blueprint = CurrentTool?.CreatePlacementBlueprint();
-
- if (blueprint != null)
- {
- placementBlueprintContainer.Child = currentPlacement = blueprint;
-
- // Fixes a 1-frame position discrepancy due to the first mouse move event happening in the next frame
- updatePlacementPosition(inputManager.CurrentState.Mouse.Position);
- }
+ removePlacement();
+ createPlacement();
}
private void updatePlacementPosition(Vector2 screenSpacePosition)
@@ -88,33 +75,23 @@ namespace osu.Game.Screens.Edit.Compose.Components
#endregion
- protected override bool OnMouseMove(MouseMoveEvent e)
- {
- if (currentPlacement != null)
- {
- updatePlacementPosition(e.ScreenSpaceMousePosition);
- return true;
- }
-
- return base.OnMouseMove(e);
- }
-
protected override void Update()
{
base.Update();
+ if (composer.CursorInPlacementArea)
+ createPlacement();
+ else if (currentPlacement?.PlacementActive == false)
+ removePlacement();
+
if (currentPlacement != null)
- {
- if (composer.CursorInPlacementArea)
- currentPlacement.State = PlacementState.Shown;
- else if (currentPlacement?.PlacementBegun == false)
- currentPlacement.State = PlacementState.Hidden;
- }
+ updatePlacementPosition(inputManager.CurrentState.Mouse.Position);
}
protected sealed override SelectionBlueprint CreateBlueprintFor(HitObject hitObject)
{
var drawable = drawableHitObjects.FirstOrDefault(d => d.HitObject == hitObject);
+
if (drawable == null)
return null;
@@ -129,6 +106,30 @@ namespace osu.Game.Screens.Edit.Compose.Components
base.AddBlueprintFor(hitObject);
}
+ private void createPlacement()
+ {
+ if (currentPlacement != null) return;
+
+ var blueprint = CurrentTool?.CreatePlacementBlueprint();
+
+ if (blueprint != null)
+ {
+ placementBlueprintContainer.Child = currentPlacement = blueprint;
+
+ // Fixes a 1-frame position discrepancy due to the first mouse move event happening in the next frame
+ updatePlacementPosition(inputManager.CurrentState.Mouse.Position);
+ }
+ }
+
+ private void removePlacement()
+ {
+ if (currentPlacement == null) return;
+
+ currentPlacement.EndPlacement(false);
+ currentPlacement.Expire();
+ currentPlacement = null;
+ }
+
private HitObjectCompositionTool currentTool;
///
@@ -137,6 +138,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
public HitObjectCompositionTool CurrentTool
{
get => currentTool;
+
set
{
if (currentTool == value)
diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineButton.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineButton.cs
index 8865bf31ea..5550c6a748 100644
--- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineButton.cs
+++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineButton.cs
@@ -6,10 +6,13 @@ using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
+using osu.Framework.Input.Events;
+using osu.Framework.Threading;
using osu.Game.Graphics;
using osu.Game.Graphics.UserInterface;
using osuTK;
using osuTK.Graphics;
+using osuTK.Input;
namespace osu.Game.Screens.Edit.Compose.Components.Timeline
{
@@ -52,6 +55,45 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
HoverColour = OsuColour.Gray(0.25f);
FlashColour = OsuColour.Gray(0.5f);
}
+
+ private ScheduledDelegate repeatSchedule;
+
+ ///
+ /// The initial delay before mouse down repeat begins.
+ ///
+ private const int repeat_initial_delay = 250;
+
+ ///
+ /// The delay between mouse down repeats after the initial repeat.
+ ///
+ private const int repeat_tick_rate = 70;
+
+ protected override bool OnClick(ClickEvent e)
+ {
+ // don't actuate a click since we are manually handling repeats.
+ return true;
+ }
+
+ protected override bool OnMouseDown(MouseDownEvent e)
+ {
+ if (e.Button == MouseButton.Left)
+ {
+ Action clickAction = () => base.OnClick(new ClickEvent(e.CurrentState, e.Button));
+
+ // run once for initial down
+ clickAction();
+
+ Scheduler.Add(repeatSchedule = new ScheduledDelegate(clickAction, Clock.CurrentTime + repeat_initial_delay, repeat_tick_rate));
+ }
+
+ return base.OnMouseDown(e);
+ }
+
+ protected override void OnMouseUp(MouseUpEvent e)
+ {
+ repeatSchedule?.Cancel();
+ base.OnMouseUp(e);
+ }
}
}
}
diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs
index 7ce8a751e0..227eecf9c7 100644
--- a/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs
+++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs
@@ -2,10 +2,12 @@
// See the LICENCE file in the repository root for full licence text.
using System;
+using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Transforms;
using osu.Framework.Input.Events;
+using osu.Framework.Timing;
using osu.Framework.Utils;
using osu.Game.Graphics.Containers;
using osuTK;
@@ -30,6 +32,9 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
private float currentZoom = 1;
+ [Resolved(canBeNull: true)]
+ private IFrameBasedClock editorClock { get; set; }
+
public ZoomableScrollContainer()
: base(Direction.Horizontal)
{
@@ -104,8 +109,15 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
protected override bool OnScroll(ScrollEvent e)
{
if (e.IsPrecise)
+ {
+ // can't handle scroll correctly while playing.
+ // the editor will handle this case for us.
+ if (editorClock?.IsRunning == true)
+ return false;
+
// for now, we don't support zoom when using a precision scroll device. this needs gesture support.
return base.OnScroll(e);
+ }
setZoomTarget(zoomTarget + e.ScrollDelta.Y, zoomedContent.ToLocalSpace(e.ScreenSpaceMousePosition).X);
return true;
diff --git a/osu.Game/Screens/Menu/LogoVisualisation.cs b/osu.Game/Screens/Menu/LogoVisualisation.cs
index 06ca161fed..dcc68296f6 100644
--- a/osu.Game/Screens/Menu/LogoVisualisation.cs
+++ b/osu.Game/Screens/Menu/LogoVisualisation.cs
@@ -95,7 +95,7 @@ namespace osu.Game.Screens.Menu
private void updateAmplitudes()
{
var track = beatmap.Value.TrackLoaded ? beatmap.Value.Track : null;
- var effect = beatmap.Value.BeatmapLoaded ? beatmap.Value.Beatmap.ControlPointInfo.EffectPointAt(track?.CurrentTime ?? Time.Current) : null;
+ var effect = beatmap.Value.BeatmapLoaded ? beatmap.Value.Beatmap?.ControlPointInfo.EffectPointAt(track?.CurrentTime ?? Time.Current) : null;
float[] temporalAmplitudes = track?.CurrentAmplitudes.FrequencyAmplitudes;
diff --git a/osu.Game/Screens/Multi/Components/BeatmapTitle.cs b/osu.Game/Screens/Multi/Components/BeatmapTitle.cs
index b41b2d073e..baf11dfe0d 100644
--- a/osu.Game/Screens/Multi/Components/BeatmapTitle.cs
+++ b/osu.Game/Screens/Multi/Components/BeatmapTitle.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.Linq;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Localisation;
@@ -25,7 +26,10 @@ namespace osu.Game.Screens.Multi.Components
[BackgroundDependencyLoader]
private void load()
{
- CurrentItem.BindValueChanged(_ => updateText(), true);
+ Playlist.ItemsAdded += _ => updateText();
+ Playlist.ItemsRemoved += _ => updateText();
+
+ updateText();
}
private float textSize = OsuFont.DEFAULT_FONT_SIZE;
@@ -54,7 +58,7 @@ namespace osu.Game.Screens.Multi.Components
textFlow.Clear();
- var beatmap = CurrentItem.Value?.Beatmap;
+ var beatmap = Playlist.FirstOrDefault()?.Beatmap;
if (beatmap == null)
{
@@ -70,7 +74,7 @@ namespace osu.Game.Screens.Multi.Components
{
new OsuSpriteText
{
- Text = new LocalisedString((beatmap.Metadata.ArtistUnicode, beatmap.Metadata.Artist)),
+ Text = new LocalisedString((beatmap.Value.Metadata.ArtistUnicode, beatmap.Value.Metadata.Artist)),
Font = OsuFont.GetFont(size: TextSize),
},
new OsuSpriteText
@@ -80,10 +84,10 @@ namespace osu.Game.Screens.Multi.Components
},
new OsuSpriteText
{
- Text = new LocalisedString((beatmap.Metadata.TitleUnicode, beatmap.Metadata.Title)),
+ Text = new LocalisedString((beatmap.Value.Metadata.TitleUnicode, beatmap.Value.Metadata.Title)),
Font = OsuFont.GetFont(size: TextSize),
}
- }, LinkAction.OpenBeatmap, beatmap.OnlineBeatmapID.ToString(), "Open beatmap");
+ }, LinkAction.OpenBeatmap, beatmap.Value.OnlineBeatmapID.ToString(), "Open beatmap");
}
}
}
diff --git a/osu.Game/Screens/Multi/Components/BeatmapTypeInfo.cs b/osu.Game/Screens/Multi/Components/BeatmapTypeInfo.cs
index d63f2fecd2..a1334101b8 100644
--- a/osu.Game/Screens/Multi/Components/BeatmapTypeInfo.cs
+++ b/osu.Game/Screens/Multi/Components/BeatmapTypeInfo.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.Linq;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
@@ -12,6 +13,8 @@ namespace osu.Game.Screens.Multi.Components
{
public class BeatmapTypeInfo : MultiplayerComposite
{
+ private LinkFlowContainer beatmapAuthor;
+
public BeatmapTypeInfo()
{
AutoSizeAxes = Axes.Both;
@@ -20,8 +23,6 @@ namespace osu.Game.Screens.Multi.Components
[BackgroundDependencyLoader]
private void load()
{
- LinkFlowContainer beatmapAuthor;
-
InternalChild = new FillFlowContainer
{
AutoSizeAxes = Axes.Both,
@@ -50,18 +51,23 @@ namespace osu.Game.Screens.Multi.Components
}
};
- CurrentItem.BindValueChanged(item =>
+ Playlist.ItemsAdded += _ => updateInfo();
+ Playlist.ItemsRemoved += _ => updateInfo();
+
+ updateInfo();
+ }
+
+ private void updateInfo()
+ {
+ beatmapAuthor.Clear();
+
+ var beatmap = Playlist.FirstOrDefault()?.Beatmap;
+
+ if (beatmap != null)
{
- beatmapAuthor.Clear();
-
- var beatmap = item.NewValue?.Beatmap;
-
- if (beatmap != null)
- {
- beatmapAuthor.AddText("mapped by ", s => s.Colour = OsuColour.Gray(0.8f));
- beatmapAuthor.AddUserLink(beatmap.Metadata.Author);
- }
- }, true);
+ beatmapAuthor.AddText("mapped by ", s => s.Colour = OsuColour.Gray(0.8f));
+ beatmapAuthor.AddUserLink(beatmap.Value.Metadata.Author);
+ }
}
}
}
diff --git a/osu.Game/Screens/Multi/Components/ModeTypeInfo.cs b/osu.Game/Screens/Multi/Components/ModeTypeInfo.cs
index 6080458aec..258541bbd6 100644
--- a/osu.Game/Screens/Multi/Components/ModeTypeInfo.cs
+++ b/osu.Game/Screens/Multi/Components/ModeTypeInfo.cs
@@ -1,11 +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.Linq;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Beatmaps.Drawables;
-using osu.Game.Online.Multiplayer;
using osuTK;
namespace osu.Game.Screens.Multi.Components
@@ -46,17 +46,22 @@ namespace osu.Game.Screens.Multi.Components
},
};
- CurrentItem.BindValueChanged(item => updateBeatmap(item.NewValue), true);
-
Type.BindValueChanged(type => gameTypeContainer.Child = new DrawableGameType(type.NewValue) { Size = new Vector2(height) }, true);
+
+ Playlist.ItemsAdded += _ => updateBeatmap();
+ Playlist.ItemsRemoved += _ => updateBeatmap();
+
+ updateBeatmap();
}
- private void updateBeatmap(PlaylistItem item)
+ private void updateBeatmap()
{
+ var item = Playlist.FirstOrDefault();
+
if (item?.Beatmap != null)
{
drawableRuleset.FadeIn(transition_duration);
- drawableRuleset.Child = new DifficultyIcon(item.Beatmap, item.Ruleset) { Size = new Vector2(height) };
+ drawableRuleset.Child = new DifficultyIcon(item.Beatmap.Value, item.Ruleset.Value) { Size = new Vector2(height) };
}
else
drawableRuleset.FadeOut(transition_duration);
diff --git a/osu.Game/Screens/Multi/Components/MultiplayerBackgroundSprite.cs b/osu.Game/Screens/Multi/Components/MultiplayerBackgroundSprite.cs
index 968fa6e72e..5e2f2e530a 100644
--- a/osu.Game/Screens/Multi/Components/MultiplayerBackgroundSprite.cs
+++ b/osu.Game/Screens/Multi/Components/MultiplayerBackgroundSprite.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.Linq;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Game.Beatmaps.Drawables;
@@ -10,6 +11,7 @@ namespace osu.Game.Screens.Multi.Components
public class MultiplayerBackgroundSprite : MultiplayerComposite
{
private readonly BeatmapSetCoverType beatmapSetCoverType;
+ private UpdateableBeatmapBackgroundSprite sprite;
public MultiplayerBackgroundSprite(BeatmapSetCoverType beatmapSetCoverType = BeatmapSetCoverType.Cover)
{
@@ -19,11 +21,17 @@ namespace osu.Game.Screens.Multi.Components
[BackgroundDependencyLoader]
private void load()
{
- UpdateableBeatmapBackgroundSprite sprite;
-
InternalChild = sprite = CreateBackgroundSprite();
- CurrentItem.BindValueChanged(item => sprite.Beatmap.Value = item.NewValue?.Beatmap, true);
+ Playlist.ItemsAdded += _ => updateBeatmap();
+ Playlist.ItemsRemoved += _ => updateBeatmap();
+
+ updateBeatmap();
+ }
+
+ private void updateBeatmap()
+ {
+ sprite.Beatmap.Value = Playlist.FirstOrDefault()?.Beatmap.Value;
}
protected virtual UpdateableBeatmapBackgroundSprite CreateBackgroundSprite() => new UpdateableBeatmapBackgroundSprite(beatmapSetCoverType) { RelativeSizeAxes = Axes.Both };
diff --git a/osu.Game/Screens/Multi/Lounge/Components/RoomsContainer.cs b/osu.Game/Screens/Multi/Lounge/Components/RoomsContainer.cs
index 063957d816..64618a1d85 100644
--- a/osu.Game/Screens/Multi/Lounge/Components/RoomsContainer.cs
+++ b/osu.Game/Screens/Multi/Lounge/Components/RoomsContainer.cs
@@ -68,7 +68,7 @@ namespace osu.Game.Screens.Multi.Lounge.Components
{
bool matchingFilter = true;
- matchingFilter &= r.Room.Playlist.Count == 0 || r.Room.Playlist.Any(i => i.Ruleset.Equals(criteria.Ruleset));
+ matchingFilter &= r.Room.Playlist.Count == 0 || r.Room.Playlist.Any(i => i.Ruleset.Value.Equals(criteria.Ruleset));
if (!string.IsNullOrEmpty(criteria.SearchString))
matchingFilter &= r.FilterTerms.Any(term => term.IndexOf(criteria.SearchString, StringComparison.InvariantCultureIgnoreCase) >= 0);
diff --git a/osu.Game/Screens/Multi/Match/Components/Header.cs b/osu.Game/Screens/Multi/Match/Components/Header.cs
index a52d43acf4..cf1eb6b6ed 100644
--- a/osu.Game/Screens/Multi/Match/Components/Header.cs
+++ b/osu.Game/Screens/Multi/Match/Components/Header.cs
@@ -2,6 +2,7 @@
// See the LICENCE file in the repository root for full licence text.
using System;
+using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Extensions.Color4Extensions;
@@ -32,6 +33,7 @@ namespace osu.Game.Screens.Multi.Match.Components
public Action RequestBeatmapSelection;
private MatchBeatmapPanel beatmapPanel;
+ private ModDisplay modDisplay;
public Header()
{
@@ -43,7 +45,6 @@ namespace osu.Game.Screens.Multi.Match.Components
private void load(OsuColour colours)
{
BeatmapSelectButton beatmapButton;
- ModDisplay modDisplay;
InternalChildren = new Drawable[]
{
@@ -119,9 +120,12 @@ namespace osu.Game.Screens.Multi.Match.Components
},
};
- CurrentItem.BindValueChanged(item => modDisplay.Current.Value = item.NewValue?.RequiredMods?.ToArray() ?? Array.Empty(), true);
-
beatmapButton.Action = () => RequestBeatmapSelection?.Invoke();
+
+ Playlist.ItemsAdded += _ => updateMods();
+ Playlist.ItemsRemoved += _ => updateMods();
+
+ updateMods();
}
protected override void LoadComplete()
@@ -130,6 +134,13 @@ namespace osu.Game.Screens.Multi.Match.Components
ShowBeatmapPanel.BindValueChanged(value => beatmapPanel.FadeTo(value.NewValue ? 1 : 0, 200, Easing.OutQuint), true);
}
+ private void updateMods()
+ {
+ var item = Playlist.FirstOrDefault();
+
+ modDisplay.Current.Value = item?.RequiredMods?.ToArray() ?? Array.Empty();
+ }
+
private class BeatmapSelectButton : HeaderButton
{
[Resolved(typeof(Room), nameof(Room.RoomID))]
diff --git a/osu.Game/Screens/Multi/Match/Components/Info.cs b/osu.Game/Screens/Multi/Match/Components/Info.cs
index 74f000c21f..a320b08cc4 100644
--- a/osu.Game/Screens/Multi/Match/Components/Info.cs
+++ b/osu.Game/Screens/Multi/Match/Components/Info.cs
@@ -2,6 +2,7 @@
// See the LICENCE file in the repository root for full licence text.
using System;
+using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
@@ -18,6 +19,8 @@ namespace osu.Game.Screens.Multi.Match.Components
{
public Action OnStart;
+ private ReadyButton readyButton;
+
public Info()
{
RelativeSizeAxes = Axes.X;
@@ -27,7 +30,6 @@ namespace osu.Game.Screens.Multi.Match.Components
[BackgroundDependencyLoader]
private void load()
{
- ReadyButton readyButton;
HostInfo hostInfo;
InternalChildren = new Drawable[]
@@ -89,9 +91,17 @@ namespace osu.Game.Screens.Multi.Match.Components
},
};
- CurrentItem.BindValueChanged(item => readyButton.Beatmap.Value = item.NewValue?.Beatmap, true);
-
hostInfo.Host.BindTo(Host);
+
+ Playlist.ItemsAdded += _ => updateBeatmap();
+ Playlist.ItemsRemoved += _ => updateBeatmap();
+
+ updateBeatmap();
+ }
+
+ private void updateBeatmap()
+ {
+ readyButton.Beatmap.Value = Playlist.FirstOrDefault()?.Beatmap.Value;
}
}
}
diff --git a/osu.Game/Screens/Multi/Match/Components/MatchBeatmapPanel.cs b/osu.Game/Screens/Multi/Match/Components/MatchBeatmapPanel.cs
index 7c1fe91393..c8de066caa 100644
--- a/osu.Game/Screens/Multi/Match/Components/MatchBeatmapPanel.cs
+++ b/osu.Game/Screens/Multi/Match/Components/MatchBeatmapPanel.cs
@@ -1,10 +1,10 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+using System.Linq;
using System.Threading;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
-using osu.Game.Beatmaps;
using osu.Game.Online.API;
using osu.Game.Online.API.Requests;
using osu.Game.Overlays.Direct;
@@ -32,10 +32,13 @@ namespace osu.Game.Screens.Multi.Match.Components
[BackgroundDependencyLoader]
private void load()
{
- CurrentItem.BindValueChanged(item => loadNewPanel(item.NewValue?.Beatmap), true);
+ Playlist.ItemsAdded += _ => loadNewPanel();
+ Playlist.ItemsRemoved += _ => loadNewPanel();
+
+ loadNewPanel();
}
- private void loadNewPanel(BeatmapInfo beatmap)
+ private void loadNewPanel()
{
loadCancellation?.Cancel();
request?.Cancel();
@@ -44,6 +47,8 @@ namespace osu.Game.Screens.Multi.Match.Components
panel?.Expire();
panel = null;
+ var beatmap = Playlist.FirstOrDefault()?.Beatmap.Value;
+
if (beatmap?.OnlineBeatmapID == null)
return;
diff --git a/osu.Game/Screens/Multi/Match/Components/Participants.cs b/osu.Game/Screens/Multi/Match/Components/Participants.cs
index ad38ec6a99..00d2f3e150 100644
--- a/osu.Game/Screens/Multi/Match/Components/Participants.cs
+++ b/osu.Game/Screens/Multi/Match/Components/Participants.cs
@@ -51,9 +51,9 @@ namespace osu.Game.Screens.Multi.Match.Components
},
};
- Participants.BindValueChanged(participants =>
+ Participants.ItemsAdded += users =>
{
- usersFlow.Children = participants.NewValue.Select(u =>
+ usersFlow.AddRange(users.Select(u =>
{
var panel = new UserPanel(u)
{
@@ -65,8 +65,13 @@ namespace osu.Game.Screens.Multi.Match.Components
panel.OnLoadComplete += d => d.FadeInFromZero(60);
return panel;
- }).ToList();
- }, true);
+ }).ToList());
+ };
+
+ Participants.ItemsRemoved += users =>
+ {
+ usersFlow.RemoveAll(p => users.Contains(p.User));
+ };
}
}
}
diff --git a/osu.Game/Screens/Multi/Match/MatchSubScreen.cs b/osu.Game/Screens/Multi/Match/MatchSubScreen.cs
index c2bb7da6b5..890664e99b 100644
--- a/osu.Game/Screens/Multi/Match/MatchSubScreen.cs
+++ b/osu.Game/Screens/Multi/Match/MatchSubScreen.cs
@@ -2,6 +2,7 @@
// See the LICENCE file in the repository root for full licence text.
using System;
+using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
@@ -40,9 +41,6 @@ namespace osu.Game.Screens.Multi.Match
[Resolved(typeof(Room))]
protected BindableList Playlist { get; private set; }
- [Resolved(typeof(Room))]
- protected Bindable CurrentItem { get; private set; }
-
[Resolved]
private BeatmapManager beatmapManager { get; set; }
@@ -52,6 +50,7 @@ namespace osu.Game.Screens.Multi.Match
[Resolved(CanBeNull = true)]
private OsuGame game { get; set; }
+ private readonly Bindable selectedItem = new Bindable();
private MatchLeaderboard leaderboard;
public MatchSubScreen(Room room)
@@ -165,7 +164,16 @@ namespace osu.Game.Screens.Multi.Match
{
base.LoadComplete();
- CurrentItem.BindValueChanged(currentItemChanged, true);
+ Playlist.ItemsAdded += _ => updateSelectedItem();
+ Playlist.ItemsRemoved += _ => updateSelectedItem();
+
+ updateSelectedItem();
+ }
+
+ private void updateSelectedItem()
+ {
+ selectedItem.Value = Playlist.FirstOrDefault();
+ currentItemChanged();
}
public override bool OnExiting(IScreen next)
@@ -180,16 +188,18 @@ namespace osu.Game.Screens.Multi.Match
///
/// Handles propagation of the current playlist item's content to game-wide mechanisms.
///
- private void currentItemChanged(ValueChangedEvent e)
+ private void currentItemChanged()
{
+ var item = selectedItem.Value;
+
// Retrieve the corresponding local beatmap, since we can't directly use the playlist's beatmap info
- var localBeatmap = e.NewValue?.Beatmap == null ? null : beatmapManager.QueryBeatmap(b => b.OnlineBeatmapID == e.NewValue.Beatmap.OnlineBeatmapID);
+ var localBeatmap = item?.Beatmap == null ? null : beatmapManager.QueryBeatmap(b => b.OnlineBeatmapID == item.Beatmap.Value.OnlineBeatmapID);
Beatmap.Value = beatmapManager.GetWorkingBeatmap(localBeatmap);
- Mods.Value = e.NewValue?.RequiredMods?.ToArray() ?? Array.Empty();
+ Mods.Value = item?.RequiredMods?.ToArray() ?? Array.Empty();
- if (e.NewValue?.Ruleset != null)
- Ruleset.Value = e.NewValue.Ruleset;
+ if (item?.Ruleset != null)
+ Ruleset.Value = item.Ruleset.Value;
previewTrackManager.StopAnyPlaying(this);
}
@@ -202,11 +212,11 @@ namespace osu.Game.Screens.Multi.Match
if (Beatmap.Value != beatmapManager.DefaultBeatmap)
return;
- if (CurrentItem.Value == null)
+ if (selectedItem.Value == null)
return;
// Try to retrieve the corresponding local beatmap
- var localBeatmap = beatmapManager.QueryBeatmap(b => b.OnlineBeatmapID == CurrentItem.Value.Beatmap.OnlineBeatmapID);
+ var localBeatmap = beatmapManager.QueryBeatmap(b => b.OnlineBeatmapID == selectedItem.Value.Beatmap.Value.OnlineBeatmapID);
if (localBeatmap != null)
Beatmap.Value = beatmapManager.GetWorkingBeatmap(localBeatmap);
@@ -223,7 +233,7 @@ namespace osu.Game.Screens.Multi.Match
{
default:
case GameTypeTimeshift _:
- multiplayer?.Start(() => new TimeshiftPlayer(CurrentItem.Value)
+ multiplayer?.Start(() => new TimeshiftPlayer(selectedItem.Value)
{
Exited = () => leaderboard.RefreshScores()
});
diff --git a/osu.Game/Screens/Multi/MultiplayerComposite.cs b/osu.Game/Screens/Multi/MultiplayerComposite.cs
index 8c09d576ff..3f048eceab 100644
--- a/osu.Game/Screens/Multi/MultiplayerComposite.cs
+++ b/osu.Game/Screens/Multi/MultiplayerComposite.cs
@@ -2,7 +2,6 @@
// See the LICENCE file in the repository root for full licence text.
using System;
-using System.Collections.Generic;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics.Containers;
@@ -32,10 +31,7 @@ namespace osu.Game.Screens.Multi
protected BindableList Playlist { get; private set; }
[Resolved(typeof(Room))]
- protected Bindable CurrentItem { get; private set; }
-
- [Resolved(typeof(Room))]
- protected Bindable> Participants { get; private set; }
+ protected BindableList Participants { get; private set; }
[Resolved(typeof(Room))]
protected Bindable ParticipantCount { get; private set; }
diff --git a/osu.Game/Screens/Multi/Play/TimeshiftPlayer.cs b/osu.Game/Screens/Multi/Play/TimeshiftPlayer.cs
index 88c6fc5e2e..3afacf2f31 100644
--- a/osu.Game/Screens/Multi/Play/TimeshiftPlayer.cs
+++ b/osu.Game/Screens/Multi/Play/TimeshiftPlayer.cs
@@ -50,10 +50,10 @@ namespace osu.Game.Screens.Multi.Play
bool failed = false;
// Sanity checks to ensure that TimeshiftPlayer matches the settings for the current PlaylistItem
- if (Beatmap.Value.BeatmapInfo.OnlineBeatmapID != playlistItem.Beatmap.OnlineBeatmapID)
+ if (Beatmap.Value.BeatmapInfo.OnlineBeatmapID != playlistItem.Beatmap.Value.OnlineBeatmapID)
throw new InvalidOperationException("Current Beatmap does not match PlaylistItem's Beatmap");
- if (ruleset.Value.ID != playlistItem.Ruleset.ID)
+ if (ruleset.Value.ID != playlistItem.Ruleset.Value.ID)
throw new InvalidOperationException("Current Ruleset does not match PlaylistItem's Ruleset");
if (!playlistItem.RequiredMods.All(m => Mods.Value.Any(m.Equals)))
diff --git a/osu.Game/Screens/Play/BeatmapMetadataDisplay.cs b/osu.Game/Screens/Play/BeatmapMetadataDisplay.cs
new file mode 100644
index 0000000000..074341226e
--- /dev/null
+++ b/osu.Game/Screens/Play/BeatmapMetadataDisplay.cs
@@ -0,0 +1,180 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System.Collections.Generic;
+using osu.Framework.Allocation;
+using osu.Framework.Bindables;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Sprites;
+using osu.Framework.Localisation;
+using osu.Game.Beatmaps;
+using osu.Game.Graphics;
+using osu.Game.Graphics.Sprites;
+using osu.Game.Graphics.UserInterface;
+using osu.Game.Rulesets.Mods;
+using osu.Game.Screens.Play.HUD;
+using osuTK;
+using osuTK.Graphics;
+
+namespace osu.Game.Screens.Play
+{
+ ///
+ /// Displays beatmap metadata inside
+ ///
+ public class BeatmapMetadataDisplay : Container
+ {
+ private class MetadataLine : Container
+ {
+ public MetadataLine(string left, string right)
+ {
+ AutoSizeAxes = Axes.Both;
+ Children = new Drawable[]
+ {
+ new OsuSpriteText
+ {
+ Anchor = Anchor.TopCentre,
+ Origin = Anchor.TopRight,
+ Margin = new MarginPadding { Right = 5 },
+ Colour = OsuColour.Gray(0.8f),
+ Text = left,
+ },
+ new OsuSpriteText
+ {
+ Anchor = Anchor.TopCentre,
+ Origin = Anchor.TopLeft,
+ Margin = new MarginPadding { Left = 5 },
+ Text = string.IsNullOrEmpty(right) ? @"-" : right,
+ }
+ };
+ }
+ }
+
+ private readonly WorkingBeatmap beatmap;
+ private readonly Bindable> mods;
+ private readonly Drawable facade;
+ private LoadingAnimation loading;
+ private Sprite backgroundSprite;
+
+ public IBindable> Mods => mods;
+
+ public bool Loading
+ {
+ set
+ {
+ if (value)
+ {
+ loading.Show();
+ backgroundSprite.FadeColour(OsuColour.Gray(0.5f), 400, Easing.OutQuint);
+ }
+ else
+ {
+ loading.Hide();
+ backgroundSprite.FadeColour(Color4.White, 400, Easing.OutQuint);
+ }
+ }
+ }
+
+ public BeatmapMetadataDisplay(WorkingBeatmap beatmap, Bindable> mods, Drawable facade)
+ {
+ this.beatmap = beatmap;
+ this.facade = facade;
+
+ this.mods = new Bindable>();
+ this.mods.BindTo(mods);
+ }
+
+ [BackgroundDependencyLoader]
+ private void load()
+ {
+ var metadata = beatmap.BeatmapInfo?.Metadata ?? new BeatmapMetadata();
+
+ AutoSizeAxes = Axes.Both;
+ Children = new Drawable[]
+ {
+ new FillFlowContainer
+ {
+ AutoSizeAxes = Axes.Both,
+ Origin = Anchor.TopCentre,
+ Anchor = Anchor.TopCentre,
+ Direction = FillDirection.Vertical,
+ Children = new[]
+ {
+ facade.With(d =>
+ {
+ d.Anchor = Anchor.TopCentre;
+ d.Origin = Anchor.TopCentre;
+ }),
+ new OsuSpriteText
+ {
+ Text = new LocalisedString((metadata.TitleUnicode, metadata.Title)),
+ Font = OsuFont.GetFont(size: 36, italics: true),
+ Origin = Anchor.TopCentre,
+ Anchor = Anchor.TopCentre,
+ Margin = new MarginPadding { Top = 15 },
+ },
+ new OsuSpriteText
+ {
+ Text = new LocalisedString((metadata.ArtistUnicode, metadata.Artist)),
+ Font = OsuFont.GetFont(size: 26, italics: true),
+ Origin = Anchor.TopCentre,
+ Anchor = Anchor.TopCentre,
+ },
+ new Container
+ {
+ Size = new Vector2(300, 60),
+ Margin = new MarginPadding(10),
+ Origin = Anchor.TopCentre,
+ Anchor = Anchor.TopCentre,
+ CornerRadius = 10,
+ Masking = true,
+ Children = new Drawable[]
+ {
+ backgroundSprite = new Sprite
+ {
+ RelativeSizeAxes = Axes.Both,
+ Texture = beatmap?.Background,
+ Origin = Anchor.Centre,
+ Anchor = Anchor.Centre,
+ FillMode = FillMode.Fill,
+ },
+ loading = new LoadingAnimation { Scale = new Vector2(1.3f) }
+ }
+ },
+ new OsuSpriteText
+ {
+ Text = beatmap?.BeatmapInfo?.Version,
+ Font = OsuFont.GetFont(size: 26, italics: true),
+ Origin = Anchor.TopCentre,
+ Anchor = Anchor.TopCentre,
+ Margin = new MarginPadding
+ {
+ Bottom = 40
+ },
+ },
+ new MetadataLine("Source", metadata.Source)
+ {
+ Origin = Anchor.TopCentre,
+ Anchor = Anchor.TopCentre,
+ },
+ new MetadataLine("Mapper", metadata.AuthorString)
+ {
+ Origin = Anchor.TopCentre,
+ Anchor = Anchor.TopCentre,
+ },
+ new ModDisplay
+ {
+ Anchor = Anchor.TopCentre,
+ Origin = Anchor.TopCentre,
+ AutoSizeAxes = Axes.Both,
+ Margin = new MarginPadding { Top = 20 },
+ Current = mods
+ }
+ },
+ }
+ };
+
+ Loading = true;
+ }
+ }
+}
diff --git a/osu.Game/Screens/Play/GameplayBeatmap.cs b/osu.Game/Screens/Play/GameplayBeatmap.cs
new file mode 100644
index 0000000000..d7f939a883
--- /dev/null
+++ b/osu.Game/Screens/Play/GameplayBeatmap.cs
@@ -0,0 +1,42 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System.Collections.Generic;
+using osu.Framework.Graphics;
+using osu.Game.Beatmaps;
+using osu.Game.Beatmaps.ControlPoints;
+using osu.Game.Beatmaps.Timing;
+using osu.Game.Rulesets.Objects;
+
+namespace osu.Game.Screens.Play
+{
+ public class GameplayBeatmap : Component, IBeatmap
+ {
+ public readonly IBeatmap PlayableBeatmap;
+
+ public GameplayBeatmap(IBeatmap playableBeatmap)
+ {
+ PlayableBeatmap = playableBeatmap;
+ }
+
+ public BeatmapInfo BeatmapInfo
+ {
+ get => PlayableBeatmap.BeatmapInfo;
+ set => PlayableBeatmap.BeatmapInfo = value;
+ }
+
+ public BeatmapMetadata Metadata => PlayableBeatmap.Metadata;
+
+ public ControlPointInfo ControlPointInfo => PlayableBeatmap.ControlPointInfo;
+
+ public List Breaks => PlayableBeatmap.Breaks;
+
+ public double TotalBreakTime => PlayableBeatmap.TotalBreakTime;
+
+ public IReadOnlyList HitObjects => PlayableBeatmap.HitObjects;
+
+ public IEnumerable GetStatistics() => PlayableBeatmap.GetStatistics();
+
+ public IBeatmap Clone() => PlayableBeatmap.Clone();
+ }
+}
diff --git a/osu.Game/Screens/Play/HUD/ModDisplay.cs b/osu.Game/Screens/Play/HUD/ModDisplay.cs
index 00edd4db99..336b03544f 100644
--- a/osu.Game/Screens/Play/HUD/ModDisplay.cs
+++ b/osu.Game/Screens/Play/HUD/ModDisplay.cs
@@ -24,7 +24,7 @@ namespace osu.Game.Screens.Play.HUD
public bool DisplayUnrankedText = true;
- public bool AllowExpand = true;
+ public ExpansionMode ExpansionMode = ExpansionMode.ExpandOnHover;
private readonly Bindable> current = new Bindable>();
@@ -110,11 +110,15 @@ namespace osu.Game.Screens.Play.HUD
private void expand()
{
- if (AllowExpand)
+ if (ExpansionMode != ExpansionMode.AlwaysContracted)
IconsContainer.TransformSpacingTo(new Vector2(5, 0), 500, Easing.OutQuint);
}
- private void contract() => IconsContainer.TransformSpacingTo(new Vector2(-25, 0), 500, Easing.OutQuint);
+ private void contract()
+ {
+ if (ExpansionMode != ExpansionMode.AlwaysExpanded)
+ IconsContainer.TransformSpacingTo(new Vector2(-25, 0), 500, Easing.OutQuint);
+ }
protected override bool OnHover(HoverEvent e)
{
@@ -128,4 +132,22 @@ namespace osu.Game.Screens.Play.HUD
base.OnHoverLost(e);
}
}
+
+ public enum ExpansionMode
+ {
+ ///
+ /// The will expand only when hovered.
+ ///
+ ExpandOnHover,
+
+ ///
+ /// The will always be expanded.
+ ///
+ AlwaysExpanded,
+
+ ///
+ /// The will always be contracted.
+ ///
+ AlwaysContracted
+ }
}
diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs
index aecd35f7dc..9bfdcd79fe 100644
--- a/osu.Game/Screens/Play/Player.cs
+++ b/osu.Game/Screens/Play/Player.cs
@@ -110,6 +110,13 @@ namespace osu.Game.Screens.Play
this.showResults = showResults;
}
+ private GameplayBeatmap gameplayBeatmap;
+
+ private DependencyContainer dependencies;
+
+ protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent)
+ => dependencies = new DependencyContainer(base.CreateChildDependencies(parent));
+
[BackgroundDependencyLoader]
private void load(AudioManager audio, IAPIProvider api, OsuConfigManager config)
{
@@ -143,6 +150,10 @@ namespace osu.Game.Screens.Play
InternalChild = GameplayClockContainer = new GameplayClockContainer(Beatmap.Value, Mods.Value, DrawableRuleset.GameplayStartTime);
+ AddInternal(gameplayBeatmap = new GameplayBeatmap(playableBeatmap));
+
+ dependencies.CacheAs(gameplayBeatmap);
+
addUnderlayComponents(GameplayClockContainer);
addGameplayComponents(GameplayClockContainer, Beatmap.Value);
addOverlayComponents(GameplayClockContainer, Beatmap.Value);
diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs
index f37faac988..01873f7114 100644
--- a/osu.Game/Screens/Play/PlayerLoader.cs
+++ b/osu.Game/Screens/Play/PlayerLoader.cs
@@ -2,7 +2,6 @@
// See the LICENCE file in the repository root for full licence text.
using System;
-using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using osu.Framework.Allocation;
@@ -12,21 +11,15 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Input;
-using osu.Framework.Localisation;
using osu.Framework.Screens;
using osu.Framework.Threading;
-using osu.Game.Beatmaps;
using osu.Game.Configuration;
using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
-using osu.Game.Graphics.Sprites;
-using osu.Game.Graphics.UserInterface;
using osu.Game.Input;
using osu.Game.Overlays;
using osu.Game.Overlays.Notifications;
-using osu.Game.Rulesets.Mods;
using osu.Game.Screens.Menu;
-using osu.Game.Screens.Play.HUD;
using osu.Game.Screens.Play.PlayerSettings;
using osu.Game.Users;
using osuTK;
@@ -38,30 +31,58 @@ namespace osu.Game.Screens.Play
{
protected const float BACKGROUND_BLUR = 15;
+ public override bool HideOverlaysOnEnter => hideOverlays;
+
+ public override bool DisallowExternalBeatmapRulesetChanges => true;
+
+ // Here because IsHovered will not update unless we do so.
+ public override bool HandlePositionalInput => true;
+
+ // We show the previous screen status
+ protected override UserActivity InitialActivity => null;
+
+ protected override bool PlayResumeSound => false;
+
+ protected BeatmapMetadataDisplay MetadataInfo;
+
+ protected VisualSettings VisualSettings;
+
+ protected Task LoadTask { get; private set; }
+
+ protected Task DisposalTask { get; private set; }
+
+ private bool backgroundBrightnessReduction;
+
+ protected bool BackgroundBrightnessReduction
+ {
+ set
+ {
+ if (value == backgroundBrightnessReduction)
+ return;
+
+ backgroundBrightnessReduction = value;
+
+ Background.FadeColour(OsuColour.Gray(backgroundBrightnessReduction ? 0.8f : 1), 200);
+ }
+ }
+
+ private bool readyForPush =>
+ player.LoadState == LoadState.Ready && (IsHovered || idleTracker.IsIdle.Value) && inputManager?.DraggedDrawable == null;
+
private readonly Func createPlayer;
private Player player;
private LogoTrackingContainer content;
- protected BeatmapMetadataDisplay MetadataInfo;
-
private bool hideOverlays;
- public override bool HideOverlaysOnEnter => hideOverlays;
-
- protected override UserActivity InitialActivity => null; //shows the previous screen status
-
- public override bool DisallowExternalBeatmapRulesetChanges => true;
-
- protected override bool PlayResumeSound => false;
-
- protected Task LoadTask { get; private set; }
-
- protected Task DisposalTask { get; private set; }
private InputManager inputManager;
+
private IdleTracker idleTracker;
+ private ScheduledDelegate scheduledPushPlayer;
+
[Resolved(CanBeNull = true)]
private NotificationOverlay notificationOverlay { get; set; }
@@ -71,19 +92,11 @@ namespace osu.Game.Screens.Play
[Resolved]
private AudioManager audioManager { get; set; }
- private Bindable muteWarningShownOnce;
-
public PlayerLoader(Func createPlayer)
{
this.createPlayer = createPlayer;
}
- private void restartRequested()
- {
- hideOverlays = true;
- ValidForResume = true;
- }
-
[BackgroundDependencyLoader]
private void load(SessionStatics sessionStatics)
{
@@ -127,11 +140,13 @@ namespace osu.Game.Screens.Play
inputManager = GetContainingInputManager();
}
+ #region Screen handling
+
public override void OnEntering(IScreen last)
{
base.OnEntering(last);
- loadNewPlayer();
+ prepareNewPlayer();
content.ScaleTo(0.7f);
Background?.FadeColour(Color4.White, 800, Easing.OutQuint);
@@ -141,15 +156,7 @@ namespace osu.Game.Screens.Play
MetadataInfo.Delay(750).FadeIn(500);
this.Delay(1800).Schedule(pushWhenLoaded);
- if (!muteWarningShownOnce.Value)
- {
- //Checks if the notification has not been shown yet and also if master volume is muted, track/music volume is muted or if the whole game is muted.
- if (volumeOverlay?.IsMuted.Value == true || audioManager.Volume.Value <= audioManager.Volume.MinValue || audioManager.VolumeTrack.Value <= audioManager.VolumeTrack.MinValue)
- {
- notificationOverlay?.Post(new MutedNotification());
- muteWarningShownOnce.Value = true;
- }
- }
+ showMuteWarningIfNeeded();
}
public override void OnResuming(IScreen last)
@@ -160,36 +167,32 @@ namespace osu.Game.Screens.Play
MetadataInfo.Loading = true;
- //we will only be resumed if the player has requested a re-run (see ValidForResume setting above)
- loadNewPlayer();
+ // we will only be resumed if the player has requested a re-run (see restartRequested).
+ prepareNewPlayer();
this.Delay(400).Schedule(pushWhenLoaded);
}
- private void loadNewPlayer()
+ public override void OnSuspending(IScreen next)
{
- var restartCount = player?.RestartCount + 1 ?? 0;
+ base.OnSuspending(next);
- player = createPlayer();
- player.RestartCount = restartCount;
- player.RestartRequested = restartRequested;
+ cancelLoad();
- LoadTask = LoadComponentAsync(player, _ => MetadataInfo.Loading = false);
+ BackgroundBrightnessReduction = false;
}
- private void contentIn()
+ public override bool OnExiting(IScreen next)
{
- content.ScaleTo(1, 650, Easing.OutQuint);
- content.FadeInFromZero(400);
- }
+ cancelLoad();
- private void contentOut()
- {
- // Ensure the logo is no longer tracking before we scale the content
- content.StopTracking();
+ content.ScaleTo(0.7f, 150, Easing.InQuint);
+ this.FadeOut(150);
- content.ScaleTo(0.7f, 300, Easing.InQuint);
- content.FadeOut(250);
+ Background.EnableUserDim.Value = false;
+ BackgroundBrightnessReduction = false;
+
+ return base.OnExiting(next);
}
protected override void LogoArriving(OsuLogo logo, bool resuming)
@@ -198,10 +201,7 @@ namespace osu.Game.Screens.Play
const double duration = 300;
- if (!resuming)
- {
- logo.MoveTo(new Vector2(0.5f), duration, Easing.In);
- }
+ if (!resuming) logo.MoveTo(new Vector2(0.5f), duration, Easing.In);
logo.ScaleTo(new Vector2(0.15f), duration, Easing.In);
logo.FadeIn(350);
@@ -219,109 +219,7 @@ namespace osu.Game.Screens.Play
content.StopTracking();
}
- private ScheduledDelegate pushDebounce;
- protected VisualSettings VisualSettings;
-
- // Here because IsHovered will not update unless we do so.
- public override bool HandlePositionalInput => true;
-
- private bool readyForPush => player.LoadState == LoadState.Ready && (IsHovered || idleTracker.IsIdle.Value) && inputManager?.DraggedDrawable == null;
-
- private void pushWhenLoaded()
- {
- if (!this.IsCurrentScreen()) return;
-
- try
- {
- if (!readyForPush)
- {
- // as the pushDebounce below has a delay, we need to keep checking and cancel a future debounce
- // if we become unready for push during the delay.
- cancelLoad();
- return;
- }
-
- if (pushDebounce != null)
- return;
-
- pushDebounce = Scheduler.AddDelayed(() =>
- {
- contentOut();
-
- this.Delay(250).Schedule(() =>
- {
- if (!this.IsCurrentScreen()) return;
-
- LoadTask = null;
-
- //By default, we want to load the player and never be returned to.
- //Note that this may change if the player we load requested a re-run.
- ValidForResume = false;
-
- if (player.LoadedBeatmapSuccessfully)
- this.Push(player);
- else
- this.Exit();
- });
- }, 500);
- }
- finally
- {
- Schedule(pushWhenLoaded);
- }
- }
-
- private void cancelLoad()
- {
- pushDebounce?.Cancel();
- pushDebounce = null;
- }
-
- public override void OnSuspending(IScreen next)
- {
- BackgroundBrightnessReduction = false;
- base.OnSuspending(next);
- cancelLoad();
- }
-
- public override bool OnExiting(IScreen next)
- {
- content.ScaleTo(0.7f, 150, Easing.InQuint);
- this.FadeOut(150);
- cancelLoad();
-
- Background.EnableUserDim.Value = false;
- BackgroundBrightnessReduction = false;
-
- return base.OnExiting(next);
- }
-
- protected override void Dispose(bool isDisposing)
- {
- base.Dispose(isDisposing);
-
- if (isDisposing)
- {
- // if the player never got pushed, we should explicitly dispose it.
- DisposalTask = LoadTask?.ContinueWith(_ => player.Dispose());
- }
- }
-
- private bool backgroundBrightnessReduction;
-
- protected bool BackgroundBrightnessReduction
- {
- get => backgroundBrightnessReduction;
- set
- {
- if (value == backgroundBrightnessReduction)
- return;
-
- backgroundBrightnessReduction = value;
-
- Background.FadeColour(OsuColour.Gray(backgroundBrightnessReduction ? 0.8f : 1), 200);
- }
- }
+ #endregion
protected override void Update()
{
@@ -350,171 +248,129 @@ namespace osu.Game.Screens.Play
}
}
- protected class BeatmapMetadataDisplay : Container
+ private void prepareNewPlayer()
{
- private class MetadataLine : Container
+ var restartCount = player?.RestartCount + 1 ?? 0;
+
+ player = createPlayer();
+ player.RestartCount = restartCount;
+ player.RestartRequested = restartRequested;
+
+ LoadTask = LoadComponentAsync(player, _ => MetadataInfo.Loading = false);
+ }
+
+ private void restartRequested()
+ {
+ hideOverlays = true;
+ ValidForResume = true;
+ }
+
+ private void contentIn()
+ {
+ content.ScaleTo(1, 650, Easing.OutQuint);
+ content.FadeInFromZero(400);
+ }
+
+ private void contentOut()
+ {
+ // Ensure the logo is no longer tracking before we scale the content
+ content.StopTracking();
+
+ content.ScaleTo(0.7f, 300, Easing.InQuint);
+ content.FadeOut(250);
+ }
+
+ private void pushWhenLoaded()
+ {
+ if (!this.IsCurrentScreen()) return;
+
+ try
{
- public MetadataLine(string left, string right)
+ if (!readyForPush)
{
- AutoSizeAxes = Axes.Both;
- Children = new Drawable[]
- {
- new OsuSpriteText
- {
- Anchor = Anchor.TopCentre,
- Origin = Anchor.TopRight,
- Margin = new MarginPadding { Right = 5 },
- Colour = OsuColour.Gray(0.8f),
- Text = left,
- },
- new OsuSpriteText
- {
- Anchor = Anchor.TopCentre,
- Origin = Anchor.TopLeft,
- Margin = new MarginPadding { Left = 5 },
- Text = string.IsNullOrEmpty(right) ? @"-" : right,
- }
- };
+ // as the pushDebounce below has a delay, we need to keep checking and cancel a future debounce
+ // if we become unready for push during the delay.
+ cancelLoad();
+ return;
}
- }
- private readonly WorkingBeatmap beatmap;
- private readonly Bindable> mods;
- private readonly Drawable facade;
- private LoadingAnimation loading;
- private Sprite backgroundSprite;
+ if (scheduledPushPlayer != null)
+ return;
- public IBindable> Mods => mods;
-
- public bool Loading
- {
- set
+ scheduledPushPlayer = Scheduler.AddDelayed(() =>
{
- if (value)
+ contentOut();
+
+ this.Delay(250).Schedule(() =>
{
- loading.Show();
- backgroundSprite.FadeColour(OsuColour.Gray(0.5f), 400, Easing.OutQuint);
- }
- else
- {
- loading.Hide();
- backgroundSprite.FadeColour(Color4.White, 400, Easing.OutQuint);
- }
+ if (!this.IsCurrentScreen()) return;
+
+ LoadTask = null;
+
+ //By default, we want to load the player and never be returned to.
+ //Note that this may change if the player we load requested a re-run.
+ ValidForResume = false;
+
+ if (player.LoadedBeatmapSuccessfully)
+ this.Push(player);
+ else
+ this.Exit();
+ });
+ }, 500);
+ }
+ finally
+ {
+ Schedule(pushWhenLoaded);
+ }
+ }
+
+ private void cancelLoad()
+ {
+ scheduledPushPlayer?.Cancel();
+ scheduledPushPlayer = null;
+ }
+
+ #region Disposal
+
+ protected override void Dispose(bool isDisposing)
+ {
+ base.Dispose(isDisposing);
+
+ if (isDisposing)
+ {
+ // if the player never got pushed, we should explicitly dispose it.
+ DisposalTask = LoadTask?.ContinueWith(_ => player.Dispose());
+ }
+ }
+
+ #endregion
+
+ #region Mute warning
+
+ private Bindable muteWarningShownOnce;
+
+ private void showMuteWarningIfNeeded()
+ {
+ if (!muteWarningShownOnce.Value)
+ {
+ //Checks if the notification has not been shown yet and also if master volume is muted, track/music volume is muted or if the whole game is muted.
+ if (volumeOverlay?.IsMuted.Value == true || audioManager.Volume.Value <= audioManager.Volume.MinValue || audioManager.VolumeTrack.Value <= audioManager.VolumeTrack.MinValue)
+ {
+ notificationOverlay?.Post(new MutedNotification());
+ muteWarningShownOnce.Value = true;
}
}
-
- public BeatmapMetadataDisplay(WorkingBeatmap beatmap, Bindable> mods, Drawable facade)
- {
- this.beatmap = beatmap;
- this.facade = facade;
-
- this.mods = new Bindable>();
- this.mods.BindTo(mods);
- }
-
- [BackgroundDependencyLoader]
- private void load()
- {
- var metadata = beatmap.BeatmapInfo?.Metadata ?? new BeatmapMetadata();
-
- AutoSizeAxes = Axes.Both;
- Children = new Drawable[]
- {
- new FillFlowContainer
- {
- AutoSizeAxes = Axes.Both,
- Origin = Anchor.TopCentre,
- Anchor = Anchor.TopCentre,
- Direction = FillDirection.Vertical,
- Children = new[]
- {
- facade.With(d =>
- {
- d.Anchor = Anchor.TopCentre;
- d.Origin = Anchor.TopCentre;
- }),
- new OsuSpriteText
- {
- Text = new LocalisedString((metadata.TitleUnicode, metadata.Title)),
- Font = OsuFont.GetFont(size: 36, italics: true),
- Origin = Anchor.TopCentre,
- Anchor = Anchor.TopCentre,
- Margin = new MarginPadding { Top = 15 },
- },
- new OsuSpriteText
- {
- Text = new LocalisedString((metadata.ArtistUnicode, metadata.Artist)),
- Font = OsuFont.GetFont(size: 26, italics: true),
- Origin = Anchor.TopCentre,
- Anchor = Anchor.TopCentre,
- },
- new Container
- {
- Size = new Vector2(300, 60),
- Margin = new MarginPadding(10),
- Origin = Anchor.TopCentre,
- Anchor = Anchor.TopCentre,
- CornerRadius = 10,
- Masking = true,
- Children = new Drawable[]
- {
- backgroundSprite = new Sprite
- {
- RelativeSizeAxes = Axes.Both,
- Texture = beatmap?.Background,
- Origin = Anchor.Centre,
- Anchor = Anchor.Centre,
- FillMode = FillMode.Fill,
- },
- loading = new LoadingAnimation { Scale = new Vector2(1.3f) }
- }
- },
- new OsuSpriteText
- {
- Text = beatmap?.BeatmapInfo?.Version,
- Font = OsuFont.GetFont(size: 26, italics: true),
- Origin = Anchor.TopCentre,
- Anchor = Anchor.TopCentre,
- Margin = new MarginPadding
- {
- Bottom = 40
- },
- },
- new MetadataLine("Source", metadata.Source)
- {
- Origin = Anchor.TopCentre,
- Anchor = Anchor.TopCentre,
- },
- new MetadataLine("Mapper", metadata.AuthorString)
- {
- Origin = Anchor.TopCentre,
- Anchor = Anchor.TopCentre,
- },
- new ModDisplay
- {
- Anchor = Anchor.TopCentre,
- Origin = Anchor.TopCentre,
- AutoSizeAxes = Axes.Both,
- Margin = new MarginPadding { Top = 20 },
- Current = mods
- }
- },
- }
- };
-
- Loading = true;
- }
}
private class MutedNotification : SimpleNotification
{
+ public override bool IsImportant => true;
+
public MutedNotification()
{
Text = "Your music volume is set to 0%! Click here to restore it.";
}
- public override bool IsImportant => true;
-
[BackgroundDependencyLoader]
private void load(OsuColour colours, AudioManager audioManager, NotificationOverlay notificationOverlay, VolumeOverlay volumeOverlay)
{
@@ -533,5 +389,7 @@ namespace osu.Game.Screens.Play
};
}
}
+
+ #endregion
}
}
diff --git a/osu.Game/Screens/Select/BeatmapDetailArea.cs b/osu.Game/Screens/Select/BeatmapDetailArea.cs
index 71733c9f06..2e78b1aed2 100644
--- a/osu.Game/Screens/Select/BeatmapDetailArea.cs
+++ b/osu.Game/Screens/Select/BeatmapDetailArea.cs
@@ -2,37 +2,40 @@
// See the LICENCE file in the repository root for full licence text.
using System;
+using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Beatmaps;
-using osu.Game.Screens.Select.Leaderboards;
namespace osu.Game.Screens.Select
{
- public class BeatmapDetailArea : Container
+ public abstract class BeatmapDetailArea : Container
{
private const float details_padding = 10;
- private readonly Container content;
- protected override Container Content => content;
-
- public readonly BeatmapDetails Details;
- public readonly BeatmapLeaderboard Leaderboard;
-
private WorkingBeatmap beatmap;
- public WorkingBeatmap Beatmap
+ public virtual WorkingBeatmap Beatmap
{
get => beatmap;
set
{
beatmap = value;
- Details.Beatmap = beatmap?.BeatmapInfo;
- Leaderboard.Beatmap = beatmap is DummyWorkingBeatmap ? null : beatmap?.BeatmapInfo;
+
+ Details.Beatmap = value?.BeatmapInfo;
}
}
- public BeatmapDetailArea()
+ public readonly BeatmapDetails Details;
+
+ protected Bindable CurrentTab => tabControl.Current;
+
+ private readonly Container content;
+ protected override Container Content => content;
+
+ private readonly BeatmapDetailAreaTabControl tabControl;
+
+ protected BeatmapDetailArea()
{
AddRangeInternal(new Drawable[]
{
@@ -40,51 +43,62 @@ namespace osu.Game.Screens.Select
{
RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding { Top = BeatmapDetailAreaTabControl.HEIGHT },
- },
- new BeatmapDetailAreaTabControl
- {
- RelativeSizeAxes = Axes.X,
- OnFilter = (tab, mods) =>
+ Child = Details = new BeatmapDetails
{
- Leaderboard.FilterMods = mods;
-
- switch (tab)
- {
- case BeatmapDetailTab.Details:
- Details.Show();
- Leaderboard.Hide();
- break;
-
- default:
- Details.Hide();
- Leaderboard.Scope = (BeatmapLeaderboardScope)tab - 1;
- Leaderboard.Show();
- break;
- }
- },
+ RelativeSizeAxes = Axes.X,
+ Alpha = 0,
+ Margin = new MarginPadding { Top = details_padding },
+ }
},
- });
-
- AddRange(new Drawable[]
- {
- Details = new BeatmapDetails
+ tabControl = new BeatmapDetailAreaTabControl
{
RelativeSizeAxes = Axes.X,
- Alpha = 0,
- Margin = new MarginPadding { Top = details_padding },
+ TabItems = CreateTabItems(),
+ OnFilter = OnTabChanged,
},
- Leaderboard = new BeatmapLeaderboard
- {
- RelativeSizeAxes = Axes.Both,
- }
});
}
+ ///
+ /// Refreshes the currently-displayed details.
+ ///
+ public virtual void Refresh()
+ {
+ }
+
protected override void UpdateAfterChildren()
{
base.UpdateAfterChildren();
Details.Height = Math.Min(DrawHeight - details_padding * 3 - BeatmapDetailAreaTabControl.HEIGHT, 450);
}
+
+ ///
+ /// Invoked when a new tab is selected.
+ ///
+ /// The tab that was selected.
+ /// Whether the currently-selected mods should be considered.
+ protected virtual void OnTabChanged(BeatmapDetailAreaTabItem tab, bool selectedMods)
+ {
+ switch (tab)
+ {
+ case BeatmapDetailAreaDetailTabItem _:
+ Details.Show();
+ break;
+
+ default:
+ Details.Hide();
+ break;
+ }
+ }
+
+ ///
+ /// Creates the tabs to be displayed.
+ ///
+ /// The tabs.
+ protected virtual BeatmapDetailAreaTabItem[] CreateTabItems() => new BeatmapDetailAreaTabItem[]
+ {
+ new BeatmapDetailAreaDetailTabItem(),
+ };
}
}
diff --git a/osu.Game/Screens/Select/BeatmapDetailAreaDetailTabItem.cs b/osu.Game/Screens/Select/BeatmapDetailAreaDetailTabItem.cs
new file mode 100644
index 0000000000..7376cb4708
--- /dev/null
+++ b/osu.Game/Screens/Select/BeatmapDetailAreaDetailTabItem.cs
@@ -0,0 +1,10 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+namespace osu.Game.Screens.Select
+{
+ public class BeatmapDetailAreaDetailTabItem : BeatmapDetailAreaTabItem
+ {
+ public override string Name => "Details";
+ }
+}
diff --git a/osu.Game/Screens/Select/BeatmapDetailAreaLeaderboardTabItem.cs b/osu.Game/Screens/Select/BeatmapDetailAreaLeaderboardTabItem.cs
new file mode 100644
index 0000000000..066944e9d2
--- /dev/null
+++ b/osu.Game/Screens/Select/BeatmapDetailAreaLeaderboardTabItem.cs
@@ -0,0 +1,22 @@
+// 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.Screens.Select
+{
+ public class BeatmapDetailAreaLeaderboardTabItem : BeatmapDetailAreaTabItem
+ where TScope : Enum
+ {
+ public override string Name => Scope.ToString();
+
+ public override bool FilterableByMods => true;
+
+ public readonly TScope Scope;
+
+ public BeatmapDetailAreaLeaderboardTabItem(TScope scope)
+ {
+ Scope = scope;
+ }
+ }
+}
diff --git a/osu.Game/Screens/Select/BeatmapDetailAreaTabControl.cs b/osu.Game/Screens/Select/BeatmapDetailAreaTabControl.cs
index 19ecdb6dbf..f4bf1ab059 100644
--- a/osu.Game/Screens/Select/BeatmapDetailAreaTabControl.cs
+++ b/osu.Game/Screens/Select/BeatmapDetailAreaTabControl.cs
@@ -2,6 +2,7 @@
// See the LICENCE file in the repository root for full licence text.
using System;
+using System.Collections.Generic;
using osuTK.Graphics;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
@@ -18,14 +19,25 @@ namespace osu.Game.Screens.Select
public class BeatmapDetailAreaTabControl : Container
{
public const float HEIGHT = 24;
+
+ public Bindable Current
+ {
+ get => tabs.Current;
+ set => tabs.Current = value;
+ }
+
+ public Action OnFilter; //passed the selected tab and if mods is checked
+
+ public IReadOnlyList TabItems
+ {
+ get => tabs.Items;
+ set => tabs.Items = value;
+ }
+
private readonly OsuTabControlCheckbox modsCheckbox;
- private readonly OsuTabControl tabs;
+ private readonly OsuTabControl tabs;
private readonly Container tabsContainer;
- public Action OnFilter; //passed the selected tab and if mods is checked
-
- private Bindable selectedTab;
-
public BeatmapDetailAreaTabControl()
{
Height = HEIGHT;
@@ -43,7 +55,7 @@ namespace osu.Game.Screens.Select
tabsContainer = new Container
{
RelativeSizeAxes = Axes.Both,
- Child = tabs = new OsuTabControl
+ Child = tabs = new OsuTabControl
{
Anchor = Anchor.BottomLeft,
Origin = Anchor.BottomLeft,
@@ -68,29 +80,22 @@ namespace osu.Game.Screens.Select
private void load(OsuColour colour, OsuConfigManager config)
{
modsCheckbox.AccentColour = tabs.AccentColour = colour.YellowLight;
-
- selectedTab = config.GetBindable(OsuSetting.BeatmapDetailTab);
-
- tabs.Current.BindTo(selectedTab);
- tabs.Current.TriggerChange();
}
private void invokeOnFilter()
{
OnFilter?.Invoke(tabs.Current.Value, modsCheckbox.Current.Value);
- modsCheckbox.FadeTo(tabs.Current.Value == BeatmapDetailTab.Details ? 0 : 1, 200, Easing.OutQuint);
-
- tabsContainer.Padding = new MarginPadding { Right = tabs.Current.Value == BeatmapDetailTab.Details ? 0 : 100 };
+ if (tabs.Current.Value.FilterableByMods)
+ {
+ modsCheckbox.FadeTo(1, 200, Easing.OutQuint);
+ tabsContainer.Padding = new MarginPadding { Right = 100 };
+ }
+ else
+ {
+ modsCheckbox.FadeTo(0, 200, Easing.OutQuint);
+ tabsContainer.Padding = new MarginPadding();
+ }
}
}
-
- public enum BeatmapDetailTab
- {
- Details,
- Local,
- Country,
- Global,
- Friends
- }
}
diff --git a/osu.Game/Screens/Select/BeatmapDetailAreaTabItem.cs b/osu.Game/Screens/Select/BeatmapDetailAreaTabItem.cs
new file mode 100644
index 0000000000..f28e5a7c22
--- /dev/null
+++ b/osu.Game/Screens/Select/BeatmapDetailAreaTabItem.cs
@@ -0,0 +1,35 @@
+// 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.Screens.Select
+{
+ public abstract class BeatmapDetailAreaTabItem : IEquatable
+ {
+ ///
+ /// The name of this tab, to be displayed in the tab control.
+ ///
+ public abstract string Name { get; }
+
+ ///
+ /// Whether the contents of this tab can be filtered by the user's currently-selected mods.
+ ///
+ public virtual bool FilterableByMods => false;
+
+ public override string ToString() => Name;
+
+ public bool Equals(BeatmapDetailAreaTabItem other)
+ {
+ if (ReferenceEquals(null, other)) return false;
+ if (ReferenceEquals(this, other)) return true;
+
+ return Name == other.Name;
+ }
+
+ public override int GetHashCode()
+ {
+ return Name != null ? Name.GetHashCode() : 0;
+ }
+ }
+}
diff --git a/osu.Game/Screens/Select/FooterButtonMods.cs b/osu.Game/Screens/Select/FooterButtonMods.cs
index 4f2369847f..2411cf26f9 100644
--- a/osu.Game/Screens/Select/FooterButtonMods.cs
+++ b/osu.Game/Screens/Select/FooterButtonMods.cs
@@ -91,7 +91,7 @@ namespace osu.Game.Screens.Select
public FooterModDisplay()
{
- AllowExpand = false;
+ ExpansionMode = ExpansionMode.AlwaysContracted;
IconsContainer.Margin = new MarginPadding();
}
}
diff --git a/osu.Game/Screens/Select/MatchSongSelect.cs b/osu.Game/Screens/Select/MatchSongSelect.cs
index 6ba4157797..826677ee30 100644
--- a/osu.Game/Screens/Select/MatchSongSelect.cs
+++ b/osu.Game/Screens/Select/MatchSongSelect.cs
@@ -2,6 +2,7 @@
// See the LICENCE file in the repository root for full licence text.
using System;
+using System.Linq;
using Humanizer;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
@@ -21,11 +22,11 @@ namespace osu.Game.Screens.Select
public string ShortTitle => "song selection";
public override string Title => ShortTitle.Humanize();
- [Resolved(typeof(Room))]
- protected Bindable CurrentItem { get; private set; }
-
public override bool AllowEditing => false;
+ [Resolved(typeof(Room), nameof(Room.Playlist))]
+ protected BindableList Playlist { get; private set; }
+
[Resolved]
private BeatmapManager beatmaps { get; set; }
@@ -34,12 +35,14 @@ namespace osu.Game.Screens.Select
Padding = new MarginPadding { Horizontal = HORIZONTAL_OVERFLOW_PADDING };
}
+ protected override BeatmapDetailArea CreateBeatmapDetailArea() => new PlayBeatmapDetailArea(); // Todo: Temporary
+
protected override bool OnStart()
{
var item = new PlaylistItem
{
- Beatmap = Beatmap.Value.BeatmapInfo,
- Ruleset = Ruleset.Value,
+ Beatmap = { Value = Beatmap.Value.BeatmapInfo },
+ Ruleset = { Value = Ruleset.Value },
RulesetID = Ruleset.Value.ID ?? 0
};
@@ -58,11 +61,13 @@ namespace osu.Game.Screens.Select
if (base.OnExiting(next))
return true;
- if (CurrentItem.Value != null)
+ var firstItem = Playlist.FirstOrDefault();
+
+ if (firstItem != null)
{
- Ruleset.Value = CurrentItem.Value.Ruleset;
- Beatmap.Value = beatmaps.GetWorkingBeatmap(CurrentItem.Value.Beatmap);
- Mods.Value = CurrentItem.Value.RequiredMods?.ToArray() ?? Array.Empty();
+ Ruleset.Value = firstItem.Ruleset.Value;
+ Beatmap.Value = beatmaps.GetWorkingBeatmap(firstItem.Beatmap.Value);
+ Mods.Value = firstItem.RequiredMods?.ToArray() ?? Array.Empty();
}
return false;
diff --git a/osu.Game/Screens/Select/PlayBeatmapDetailArea.cs b/osu.Game/Screens/Select/PlayBeatmapDetailArea.cs
new file mode 100644
index 0000000000..d719502a4f
--- /dev/null
+++ b/osu.Game/Screens/Select/PlayBeatmapDetailArea.cs
@@ -0,0 +1,143 @@
+// 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 System.Linq;
+using osu.Framework.Allocation;
+using osu.Framework.Bindables;
+using osu.Framework.Graphics;
+using osu.Game.Beatmaps;
+using osu.Game.Configuration;
+using osu.Game.Screens.Select.Leaderboards;
+
+namespace osu.Game.Screens.Select
+{
+ public class PlayBeatmapDetailArea : BeatmapDetailArea
+ {
+ public readonly BeatmapLeaderboard Leaderboard;
+
+ public override WorkingBeatmap Beatmap
+ {
+ get => base.Beatmap;
+ set
+ {
+ base.Beatmap = value;
+
+ Leaderboard.Beatmap = value is DummyWorkingBeatmap ? null : value?.BeatmapInfo;
+ }
+ }
+
+ private Bindable selectedTab;
+
+ public PlayBeatmapDetailArea()
+ {
+ Add(Leaderboard = new BeatmapLeaderboard { RelativeSizeAxes = Axes.Both });
+ }
+
+ [BackgroundDependencyLoader]
+ private void load(OsuConfigManager config)
+ {
+ selectedTab = config.GetBindable(OsuSetting.BeatmapDetailTab);
+ selectedTab.BindValueChanged(tab => CurrentTab.Value = getTabItemFromTabType(tab.NewValue), true);
+ CurrentTab.BindValueChanged(tab => selectedTab.Value = getTabTypeFromTabItem(tab.NewValue));
+ }
+
+ public override void Refresh()
+ {
+ base.Refresh();
+
+ Leaderboard.RefreshScores();
+ }
+
+ protected override void OnTabChanged(BeatmapDetailAreaTabItem tab, bool selectedMods)
+ {
+ base.OnTabChanged(tab, selectedMods);
+
+ Leaderboard.FilterMods = selectedMods;
+
+ switch (tab)
+ {
+ case BeatmapDetailAreaLeaderboardTabItem leaderboard:
+ Leaderboard.Scope = leaderboard.Scope;
+ Leaderboard.Show();
+ break;
+
+ default:
+ Leaderboard.Hide();
+ break;
+ }
+ }
+
+ protected override BeatmapDetailAreaTabItem[] CreateTabItems() => base.CreateTabItems().Concat(new BeatmapDetailAreaTabItem[]
+ {
+ new BeatmapDetailAreaLeaderboardTabItem(BeatmapLeaderboardScope.Local),
+ new BeatmapDetailAreaLeaderboardTabItem(BeatmapLeaderboardScope.Country),
+ new BeatmapDetailAreaLeaderboardTabItem(BeatmapLeaderboardScope.Global),
+ new BeatmapDetailAreaLeaderboardTabItem(BeatmapLeaderboardScope.Friend),
+ }).ToArray();
+
+ private BeatmapDetailAreaTabItem getTabItemFromTabType(TabType type)
+ {
+ switch (type)
+ {
+ case TabType.Details:
+ return new BeatmapDetailAreaDetailTabItem();
+
+ case TabType.Local:
+ return new BeatmapDetailAreaLeaderboardTabItem(BeatmapLeaderboardScope.Local);
+
+ case TabType.Country:
+ return new BeatmapDetailAreaLeaderboardTabItem(BeatmapLeaderboardScope.Country);
+
+ case TabType.Global:
+ return new BeatmapDetailAreaLeaderboardTabItem(BeatmapLeaderboardScope.Global);
+
+ case TabType.Friends:
+ return new BeatmapDetailAreaLeaderboardTabItem(BeatmapLeaderboardScope.Friend);
+
+ default:
+ throw new ArgumentOutOfRangeException(nameof(type));
+ }
+ }
+
+ private TabType getTabTypeFromTabItem(BeatmapDetailAreaTabItem item)
+ {
+ switch (item)
+ {
+ case BeatmapDetailAreaDetailTabItem _:
+ return TabType.Details;
+
+ case BeatmapDetailAreaLeaderboardTabItem leaderboardTab:
+ switch (leaderboardTab.Scope)
+ {
+ case BeatmapLeaderboardScope.Local:
+ return TabType.Local;
+
+ case BeatmapLeaderboardScope.Country:
+ return TabType.Country;
+
+ case BeatmapLeaderboardScope.Global:
+ return TabType.Global;
+
+ case BeatmapLeaderboardScope.Friend:
+ return TabType.Friends;
+
+ default:
+ throw new ArgumentOutOfRangeException(nameof(item));
+ }
+
+ default:
+ throw new ArgumentOutOfRangeException(nameof(item));
+ }
+ }
+
+ public enum TabType
+ {
+ Details,
+ Local,
+ Country,
+ Global,
+ Friends
+ }
+ }
+}
diff --git a/osu.Game/Screens/Select/PlaySongSelect.cs b/osu.Game/Screens/Select/PlaySongSelect.cs
index f1dd125362..e744fd6a7b 100644
--- a/osu.Game/Screens/Select/PlaySongSelect.cs
+++ b/osu.Game/Screens/Select/PlaySongSelect.cs
@@ -29,8 +29,12 @@ namespace osu.Game.Screens.Select
ValidForResume = false;
Edit();
}, Key.Number4);
+
+ ((PlayBeatmapDetailArea)BeatmapDetails).Leaderboard.ScoreSelected += score => this.Push(new SoloResults(score));
}
+ protected override BeatmapDetailArea CreateBeatmapDetailArea() => new PlayBeatmapDetailArea();
+
public override void OnResuming(IScreen last)
{
base.OnResuming(last);
diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs
index 0da260d752..67626d1e4f 100644
--- a/osu.Game/Screens/Select/SongSelect.cs
+++ b/osu.Game/Screens/Select/SongSelect.cs
@@ -23,7 +23,6 @@ using osu.Game.Rulesets.Mods;
using osu.Game.Screens.Backgrounds;
using osu.Game.Screens.Edit;
using osu.Game.Screens.Menu;
-using osu.Game.Screens.Play;
using osu.Game.Screens.Select.Options;
using osu.Game.Skinning;
using osuTK;
@@ -207,11 +206,11 @@ namespace osu.Game.Screens.Select
Left = left_area_padding,
Right = left_area_padding * 2,
},
- Child = BeatmapDetails = new BeatmapDetailArea
+ Child = BeatmapDetails = CreateBeatmapDetailArea().With(d =>
{
- RelativeSizeAxes = Axes.Both,
- Padding = new MarginPadding { Top = 10, Right = 5 },
- },
+ d.RelativeSizeAxes = Axes.Both;
+ d.Padding = new MarginPadding { Top = 10, Right = 5 };
+ })
},
}
},
@@ -262,8 +261,6 @@ namespace osu.Game.Screens.Select
});
}
- BeatmapDetails.Leaderboard.ScoreSelected += score => this.Push(new SoloResults(score));
-
if (Footer != null)
{
Footer.AddButton(new FooterButtonMods { Current = Mods }, ModSelect);
@@ -319,6 +316,11 @@ namespace osu.Game.Screens.Select
return dependencies;
}
+ ///
+ /// Creates the beatmap details to be displayed underneath the wedge.
+ ///
+ protected abstract BeatmapDetailArea CreateBeatmapDetailArea();
+
public void Edit(BeatmapInfo beatmap = null)
{
if (!AllowEditing)
@@ -533,7 +535,7 @@ namespace osu.Game.Screens.Select
Carousel.AllowSelection = true;
- BeatmapDetails.Leaderboard.RefreshScores();
+ BeatmapDetails.Refresh();
Beatmap.Value.Track.Looping = true;
music?.ResetTrackAdjustments();
@@ -716,7 +718,7 @@ namespace osu.Game.Screens.Select
dialogOverlay?.Push(new BeatmapClearScoresDialog(beatmap, () =>
// schedule done here rather than inside the dialog as the dialog may fade out and never callback.
- Schedule(() => BeatmapDetails.Leaderboard.RefreshScores())));
+ Schedule(() => BeatmapDetails.Refresh())));
}
public virtual bool OnPressed(GlobalAction action)
diff --git a/osu.Game/Tests/Visual/OsuTestScene.cs b/osu.Game/Tests/Visual/OsuTestScene.cs
index 41ab7fce99..b203557fab 100644
--- a/osu.Game/Tests/Visual/OsuTestScene.cs
+++ b/osu.Game/Tests/Visual/OsuTestScene.cs
@@ -191,9 +191,9 @@ namespace osu.Game.Tests.Visual
track = audio?.Tracks.GetVirtual(length);
}
- protected override void Dispose(bool isDisposing)
+ ~ClockBackedTestWorkingBeatmap()
{
- base.Dispose(isDisposing);
+ // Remove the track store from the audio manager
store?.Dispose();
}
diff --git a/osu.Game/Users/UserPanel.cs b/osu.Game/Users/UserPanel.cs
index 6ddbc13a06..6f34466e94 100644
--- a/osu.Game/Users/UserPanel.cs
+++ b/osu.Game/Users/UserPanel.cs
@@ -26,11 +26,12 @@ namespace osu.Game.Users
{
public class UserPanel : OsuClickableContainer, IHasContextMenu
{
- private readonly User user;
private const float height = 100;
private const float content_padding = 10;
private const float status_height = 30;
+ public readonly User User;
+
[Resolved(canBeNull: true)]
private OsuColour colours { get; set; }
@@ -54,7 +55,7 @@ namespace osu.Game.Users
if (user == null)
throw new ArgumentNullException(nameof(user));
- this.user = user;
+ User = user;
Height = height - status_height;
}
@@ -86,7 +87,7 @@ namespace osu.Game.Users
RelativeSizeAxes = Axes.Both,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
- User = user,
+ User = User,
}, 300, 5000)
{
RelativeSizeAxes = Axes.Both,
@@ -106,7 +107,7 @@ namespace osu.Game.Users
new UpdateableAvatar
{
Size = new Vector2(height - status_height - content_padding * 2),
- User = user,
+ User = User,
Masking = true,
CornerRadius = 5,
OpenOnClick = { Value = false },
@@ -125,7 +126,7 @@ namespace osu.Game.Users
{
new OsuSpriteText
{
- Text = user.Username,
+ Text = User.Username,
Font = OsuFont.GetFont(weight: FontWeight.SemiBold, size: 18, italics: true),
},
infoContainer = new FillFlowContainer
@@ -138,7 +139,7 @@ namespace osu.Game.Users
Spacing = new Vector2(5f, 0f),
Children = new Drawable[]
{
- new UpdateableFlag(user.Country)
+ new UpdateableFlag(User.Country)
{
Width = 30f,
RelativeSizeAxes = Axes.Y,
@@ -191,12 +192,12 @@ namespace osu.Game.Users
}
});
- if (user.IsSupporter)
+ if (User.IsSupporter)
{
infoContainer.Add(new SupporterIcon
{
Height = 20f,
- SupportLevel = user.SupportLevel
+ SupportLevel = User.SupportLevel
});
}
@@ -206,7 +207,7 @@ namespace osu.Game.Users
base.Action = ViewProfile = () =>
{
Action?.Invoke();
- profile?.ShowUser(user);
+ profile?.ShowUser(User);
};
}
diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj
index 389fbe8210..50d8c25b11 100644
--- a/osu.Game/osu.Game.csproj
+++ b/osu.Game/osu.Game.csproj
@@ -23,7 +23,7 @@
-
+
diff --git a/osu.iOS.props b/osu.iOS.props
index 3ed25360c5..e56fc41b07 100644
--- a/osu.iOS.props
+++ b/osu.iOS.props
@@ -74,7 +74,7 @@
-
+
@@ -82,7 +82,7 @@
-
+