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/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/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/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/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/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/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/MatchSongSelect.cs b/osu.Game/Screens/Select/MatchSongSelect.cs
index 251456bf0d..826677ee30 100644
--- a/osu.Game/Screens/Select/MatchSongSelect.cs
+++ b/osu.Game/Screens/Select/MatchSongSelect.cs
@@ -35,6 +35,8 @@ 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
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)