1
0
mirror of https://github.com/ppy/osu.git synced 2025-03-06 04:33:21 +08:00

Merge branch 'master' into catch-api-request-exceptions

This commit is contained in:
Dan Balasescu 2019-11-01 11:33:23 +09:00 committed by GitHub
commit 54681a6206
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 410 additions and 56 deletions

5
.github/ISSUE_TEMPLATE/config.yml vendored Normal file
View File

@ -0,0 +1,5 @@
blank_issues_enabled: false
contact_links:
- name: osu!stable issues
url: https://github.com/ppy/osu-stable-issues
about: For issues regarding osu!stable (not osu!lazer), open them here.

View File

@ -1,7 +0,0 @@
---
name: Missing for Live
about: Features which are available in osu!stable but not yet in osu!lazer.
---
**Describe the missing feature:**
**Proposal designs of the feature:**

View File

@ -0,0 +1,102 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Graphics.UserInterface;
using osu.Game.Beatmaps;
using osu.Game.Overlays.BeatmapSet;
using osu.Game.Rulesets;
using System;
using System.Collections.Generic;
using System.Linq;
namespace osu.Game.Tests.Visual.Online
{
public class TestSceneBeatmapRulesetSelector : OsuTestScene
{
public override IReadOnlyList<Type> RequiredTypes => new[]
{
typeof(BeatmapRulesetSelector),
typeof(BeatmapRulesetTabItem),
};
private readonly TestRulesetSelector selector;
public TestSceneBeatmapRulesetSelector()
{
Add(selector = new TestRulesetSelector());
}
[Resolved]
private RulesetStore rulesets { get; set; }
[Test]
public void TestMultipleRulesetsBeatmapSet()
{
var enabledRulesets = rulesets.AvailableRulesets.Skip(1).Take(2);
AddStep("load multiple rulesets beatmapset", () =>
{
selector.BeatmapSet = new BeatmapSetInfo
{
Beatmaps = enabledRulesets.Select(r => new BeatmapInfo { Ruleset = r }).ToList()
};
});
var tabItems = selector.TabContainer.TabItems;
AddAssert("other rulesets disabled", () => tabItems.Except(tabItems.Where(t => enabledRulesets.Any(r => r.Equals(t.Value)))).All(t => !t.Enabled.Value));
AddAssert("left-most ruleset selected", () => tabItems.First(t => t.Enabled.Value).Active.Value);
}
[Test]
public void TestSingleRulesetBeatmapSet()
{
var enabledRuleset = rulesets.AvailableRulesets.Last();
AddStep("load single ruleset beatmapset", () =>
{
selector.BeatmapSet = new BeatmapSetInfo
{
Beatmaps = new List<BeatmapInfo>
{
new BeatmapInfo
{
Ruleset = enabledRuleset
}
}
};
});
AddAssert("single ruleset selected", () => selector.SelectedTab.Value.Equals(enabledRuleset));
}
[Test]
public void TestEmptyBeatmapSet()
{
AddStep("load empty beatmapset", () => selector.BeatmapSet = new BeatmapSetInfo
{
Beatmaps = new List<BeatmapInfo>()
});
AddAssert("no ruleset selected", () => selector.SelectedTab == null);
AddAssert("all rulesets disabled", () => selector.TabContainer.TabItems.All(t => !t.Enabled.Value));
}
[Test]
public void TestNullBeatmapSet()
{
AddStep("load null beatmapset", () => selector.BeatmapSet = null);
AddAssert("no ruleset selected", () => selector.SelectedTab == null);
AddAssert("all rulesets disabled", () => selector.TabContainer.TabItems.All(t => !t.Enabled.Value));
}
private class TestRulesetSelector : BeatmapRulesetSelector
{
public new TabItem<RulesetInfo> SelectedTab => base.SelectedTab;
public new TabFillFlowContainer TabContainer => base.TabContainer;
}
}
}

View File

@ -40,24 +40,19 @@ namespace osu.Game.Tests.Visual.Online
typeof(PreviewButton), typeof(PreviewButton),
typeof(SuccessRate), typeof(SuccessRate),
typeof(BeatmapAvailability), typeof(BeatmapAvailability),
typeof(BeatmapRulesetSelector),
typeof(BeatmapRulesetTabItem),
}; };
protected override bool UseOnlineAPI => true; protected override bool UseOnlineAPI => true;
private RulesetInfo taikoRuleset;
private RulesetInfo maniaRuleset;
public TestSceneBeatmapSetOverlay() public TestSceneBeatmapSetOverlay()
{ {
Add(overlay = new TestBeatmapSetOverlay()); Add(overlay = new TestBeatmapSetOverlay());
} }
[BackgroundDependencyLoader] [Resolved]
private void load(RulesetStore rulesets) private RulesetStore rulesets { get; set; }
{
taikoRuleset = rulesets.GetRuleset(1);
maniaRuleset = rulesets.GetRuleset(3);
}
[Test] [Test]
public void TestLoading() public void TestLoading()
@ -111,7 +106,7 @@ namespace osu.Game.Tests.Visual.Online
StarDifficulty = 9.99, StarDifficulty = 9.99,
Version = @"TEST", Version = @"TEST",
Length = 456000, Length = 456000,
Ruleset = maniaRuleset, Ruleset = rulesets.GetRuleset(3),
BaseDifficulty = new BeatmapDifficulty BaseDifficulty = new BeatmapDifficulty
{ {
CircleSize = 1, CircleSize = 1,
@ -189,7 +184,7 @@ namespace osu.Game.Tests.Visual.Online
StarDifficulty = 5.67, StarDifficulty = 5.67,
Version = @"ANOTHER TEST", Version = @"ANOTHER TEST",
Length = 123000, Length = 123000,
Ruleset = taikoRuleset, Ruleset = rulesets.GetRuleset(1),
BaseDifficulty = new BeatmapDifficulty BaseDifficulty = new BeatmapDifficulty
{ {
CircleSize = 9, CircleSize = 9,
@ -217,6 +212,54 @@ namespace osu.Game.Tests.Visual.Online
downloadAssert(false); downloadAssert(false);
} }
[Test]
public void TestMultipleRulesets()
{
AddStep("show multiple rulesets beatmap", () =>
{
var beatmaps = new List<BeatmapInfo>();
foreach (var ruleset in rulesets.AvailableRulesets.Skip(1))
{
beatmaps.Add(new BeatmapInfo
{
Version = ruleset.Name,
Ruleset = ruleset,
BaseDifficulty = new BeatmapDifficulty(),
OnlineInfo = new BeatmapOnlineInfo(),
Metrics = new BeatmapMetrics
{
Fails = Enumerable.Range(1, 100).Select(i => i % 12 - 6).ToArray(),
Retries = Enumerable.Range(-2, 100).Select(i => i % 12 - 6).ToArray(),
},
});
}
overlay.ShowBeatmapSet(new BeatmapSetInfo
{
Metadata = new BeatmapMetadata
{
Title = @"multiple rulesets beatmap",
Artist = @"none",
Author = new User
{
Username = "BanchoBot",
Id = 3,
}
},
OnlineInfo = new BeatmapSetOnlineInfo
{
Covers = new BeatmapSetOnlineCovers(),
},
Metrics = new BeatmapSetMetrics { Ratings = Enumerable.Range(0, 11).ToArray() },
Beatmaps = beatmaps
});
});
AddAssert("shown beatmaps of current ruleset", () => overlay.Header.Picker.Difficulties.All(b => b.Beatmap.Ruleset.Equals(overlay.Header.RulesetSelector.Current.Value)));
AddAssert("left-most beatmap selected", () => overlay.Header.Picker.Difficulties.First().State == BeatmapPicker.DifficultySelectorState.Selected);
}
[Test] [Test]
public void TestHide() public void TestHide()
{ {
@ -281,12 +324,12 @@ namespace osu.Game.Tests.Visual.Online
private void downloadAssert(bool shown) private void downloadAssert(bool shown)
{ {
AddAssert($"is download button {(shown ? "shown" : "hidden")}", () => overlay.DownloadButtonsVisible == shown); AddAssert($"is download button {(shown ? "shown" : "hidden")}", () => overlay.Header.DownloadButtonsVisible == shown);
} }
private class TestBeatmapSetOverlay : BeatmapSetOverlay private class TestBeatmapSetOverlay : BeatmapSetOverlay
{ {
public bool DownloadButtonsVisible => Header.DownloadButtonsVisible; public new Header Header => base.Header;
} }
} }
} }

View File

@ -17,6 +17,7 @@ using osu.Game.Beatmaps.Drawables;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Graphics.Containers; using osu.Game.Graphics.Containers;
using osu.Game.Graphics.Sprites; using osu.Game.Graphics.Sprites;
using osu.Game.Rulesets;
using osuTK; using osuTK;
using osuTK.Graphics; using osuTK.Graphics;
@ -27,10 +28,11 @@ namespace osu.Game.Overlays.BeatmapSet
private const float tile_icon_padding = 7; private const float tile_icon_padding = 7;
private const float tile_spacing = 2; private const float tile_spacing = 2;
private readonly DifficultiesContainer difficulties;
private readonly OsuSpriteText version, starRating; private readonly OsuSpriteText version, starRating;
private readonly Statistic plays, favourites; private readonly Statistic plays, favourites;
public readonly DifficultiesContainer Difficulties;
public readonly Bindable<BeatmapInfo> Beatmap = new Bindable<BeatmapInfo>(); public readonly Bindable<BeatmapInfo> Beatmap = new Bindable<BeatmapInfo>();
private BeatmapSetInfo beatmapSet; private BeatmapSetInfo beatmapSet;
@ -43,38 +45,10 @@ namespace osu.Game.Overlays.BeatmapSet
if (value == beatmapSet) return; if (value == beatmapSet) return;
beatmapSet = value; beatmapSet = value;
updateDisplay(); updateDisplay();
} }
} }
private void updateDisplay()
{
difficulties.Clear();
if (BeatmapSet != null)
{
difficulties.ChildrenEnumerable = BeatmapSet.Beatmaps.OrderBy(beatmap => beatmap.StarDifficulty).Select(b => new DifficultySelectorButton(b)
{
State = DifficultySelectorState.NotSelected,
OnHovered = beatmap =>
{
showBeatmap(beatmap);
starRating.Text = beatmap.StarDifficulty.ToString("Star Difficulty 0.##");
starRating.FadeIn(100);
},
OnClicked = beatmap => { Beatmap.Value = beatmap; },
});
}
starRating.FadeOut(100);
Beatmap.Value = BeatmapSet?.Beatmaps.FirstOrDefault();
plays.Value = BeatmapSet?.OnlineInfo.PlayCount ?? 0;
favourites.Value = BeatmapSet?.OnlineInfo.FavouriteCount ?? 0;
updateDifficultyButtons();
}
public BeatmapPicker() public BeatmapPicker()
{ {
RelativeSizeAxes = Axes.X; RelativeSizeAxes = Axes.X;
@ -89,7 +63,7 @@ namespace osu.Game.Overlays.BeatmapSet
Direction = FillDirection.Vertical, Direction = FillDirection.Vertical,
Children = new Drawable[] Children = new Drawable[]
{ {
difficulties = new DifficultiesContainer Difficulties = new DifficultiesContainer
{ {
RelativeSizeAxes = Axes.X, RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y, AutoSizeAxes = Axes.Y,
@ -147,6 +121,9 @@ namespace osu.Game.Overlays.BeatmapSet
}; };
} }
[Resolved]
private IBindable<RulesetInfo> ruleset { get; set; }
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(OsuColour colours) private void load(OsuColour colours)
{ {
@ -158,10 +135,39 @@ namespace osu.Game.Overlays.BeatmapSet
{ {
base.LoadComplete(); base.LoadComplete();
ruleset.ValueChanged += r => updateDisplay();
// done here so everything can bind in intialization and get the first trigger // done here so everything can bind in intialization and get the first trigger
Beatmap.TriggerChange(); Beatmap.TriggerChange();
} }
private void updateDisplay()
{
Difficulties.Clear();
if (BeatmapSet != null)
{
Difficulties.ChildrenEnumerable = BeatmapSet.Beatmaps.Where(b => b.Ruleset.Equals(ruleset.Value)).OrderBy(b => b.StarDifficulty).Select(b => new DifficultySelectorButton(b)
{
State = DifficultySelectorState.NotSelected,
OnHovered = beatmap =>
{
showBeatmap(beatmap);
starRating.Text = beatmap.StarDifficulty.ToString("Star Difficulty 0.##");
starRating.FadeIn(100);
},
OnClicked = beatmap => { Beatmap.Value = beatmap; },
});
}
starRating.FadeOut(100);
Beatmap.Value = Difficulties.FirstOrDefault()?.Beatmap;
plays.Value = BeatmapSet?.OnlineInfo.PlayCount ?? 0;
favourites.Value = BeatmapSet?.OnlineInfo.FavouriteCount ?? 0;
updateDifficultyButtons();
}
private void showBeatmap(BeatmapInfo beatmap) private void showBeatmap(BeatmapInfo beatmap)
{ {
version.Text = beatmap?.Version; version.Text = beatmap?.Version;
@ -169,10 +175,10 @@ namespace osu.Game.Overlays.BeatmapSet
private void updateDifficultyButtons() private void updateDifficultyButtons()
{ {
difficulties.Children.ToList().ForEach(diff => diff.State = diff.Beatmap == Beatmap.Value ? DifficultySelectorState.Selected : DifficultySelectorState.NotSelected); Difficulties.Children.ToList().ForEach(diff => diff.State = diff.Beatmap == Beatmap.Value ? DifficultySelectorState.Selected : DifficultySelectorState.NotSelected);
} }
private class DifficultiesContainer : FillFlowContainer<DifficultySelectorButton> public class DifficultiesContainer : FillFlowContainer<DifficultySelectorButton>
{ {
public Action OnLostHover; public Action OnLostHover;
@ -183,7 +189,7 @@ namespace osu.Game.Overlays.BeatmapSet
} }
} }
private class DifficultySelectorButton : OsuClickableContainer, IStateful<DifficultySelectorState> public class DifficultySelectorButton : OsuClickableContainer, IStateful<DifficultySelectorState>
{ {
private const float transition_duration = 100; private const float transition_duration = 100;
private const float size = 52; private const float size = 52;
@ -320,7 +326,7 @@ namespace osu.Game.Overlays.BeatmapSet
} }
} }
private enum DifficultySelectorState public enum DifficultySelectorState
{ {
Selected, Selected,
NotSelected, NotSelected,

View File

@ -0,0 +1,48 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.UserInterface;
using osu.Game.Beatmaps;
using osu.Game.Rulesets;
using osuTK;
using System.Linq;
namespace osu.Game.Overlays.BeatmapSet
{
public class BeatmapRulesetSelector : RulesetSelector
{
private readonly Bindable<BeatmapSetInfo> beatmapSet = new Bindable<BeatmapSetInfo>();
public BeatmapSetInfo BeatmapSet
{
get => beatmapSet.Value;
set
{
// propagate value to tab items first to enable only available rulesets.
beatmapSet.Value = value;
SelectTab(TabContainer.TabItems.FirstOrDefault(t => t.Enabled.Value));
}
}
public BeatmapRulesetSelector()
{
AutoSizeAxes = Axes.Both;
}
protected override TabItem<RulesetInfo> CreateTabItem(RulesetInfo value) => new BeatmapRulesetTabItem(value)
{
BeatmapSet = { BindTarget = beatmapSet }
};
protected override TabFillFlowContainer CreateTabFlow() => new TabFillFlowContainer
{
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Horizontal,
Spacing = new Vector2(10, 0),
};
}
}

View File

@ -0,0 +1,145 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.UserInterface;
using osu.Framework.Input.Events;
using osu.Game.Beatmaps;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterface;
using osu.Game.Rulesets;
using osuTK;
using osuTK.Graphics;
using System.Linq;
namespace osu.Game.Overlays.BeatmapSet
{
public class BeatmapRulesetTabItem : TabItem<RulesetInfo>
{
private readonly OsuSpriteText name, count;
private readonly Box bar;
public readonly Bindable<BeatmapSetInfo> BeatmapSet = new Bindable<BeatmapSetInfo>();
public override bool PropagatePositionalInputSubTree => Enabled.Value && !Active.Value && base.PropagatePositionalInputSubTree;
public BeatmapRulesetTabItem(RulesetInfo value)
: base(value)
{
AutoSizeAxes = Axes.Both;
FillFlowContainer nameContainer;
Children = new Drawable[]
{
nameContainer = new FillFlowContainer
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Horizontal,
Margin = new MarginPadding { Bottom = 7.5f },
Spacing = new Vector2(2.5f),
Children = new Drawable[]
{
name = new OsuSpriteText
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Text = value.Name,
Font = OsuFont.Default.With(size: 18),
},
new Container
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
AutoSizeAxes = Axes.Both,
Masking = true,
CornerRadius = 4f,
Children = new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = Color4.Black.Opacity(0.5f),
},
count = new OsuSpriteText
{
Alpha = 0,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Margin = new MarginPadding { Horizontal = 5f },
Font = OsuFont.Default.With(weight: FontWeight.SemiBold),
}
}
}
}
},
bar = new Box
{
Anchor = Anchor.BottomCentre,
Origin = Anchor.BottomCentre,
RelativeSizeAxes = Axes.X,
},
new HoverClickSounds(),
};
BeatmapSet.BindValueChanged(setInfo =>
{
var beatmapsCount = setInfo.NewValue?.Beatmaps.Count(b => b.Ruleset.Equals(Value)) ?? 0;
count.Text = beatmapsCount.ToString();
count.Alpha = beatmapsCount > 0 ? 1f : 0f;
Enabled.Value = beatmapsCount > 0;
}, true);
Enabled.BindValueChanged(v => nameContainer.Alpha = v.NewValue ? 1f : 0.5f, true);
}
[Resolved]
private OsuColour colour { get; set; }
protected override void LoadComplete()
{
base.LoadComplete();
count.Colour = colour.Gray9;
bar.Colour = colour.Blue;
updateState();
}
private void updateState()
{
var isHoveredOrActive = IsHovered || Active.Value;
bar.ResizeHeightTo(isHoveredOrActive ? 4 : 0, 200, Easing.OutQuint);
name.Colour = isHoveredOrActive ? colour.GrayE : colour.GrayC;
name.Font = name.Font.With(weight: Active.Value ? FontWeight.Bold : FontWeight.Regular);
}
#region Hovering and activation logic
protected override void OnActivated() => updateState();
protected override void OnDeactivated() => updateState();
protected override bool OnHover(HoverEvent e)
{
updateState();
return false;
}
protected override void OnHoverLost(HoverLostEvent e) => updateState();
#endregion
}
}

View File

@ -3,6 +3,7 @@
using System.Linq; using System.Linq;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Colour;
@ -16,6 +17,7 @@ using osu.Game.Graphics.UserInterface;
using osu.Game.Online; using osu.Game.Online;
using osu.Game.Overlays.BeatmapSet.Buttons; using osu.Game.Overlays.BeatmapSet.Buttons;
using osu.Game.Overlays.Direct; using osu.Game.Overlays.Direct;
using osu.Game.Rulesets;
using osuTK; using osuTK;
using osuTK.Graphics; using osuTK.Graphics;
@ -39,6 +41,7 @@ namespace osu.Game.Overlays.BeatmapSet
public bool DownloadButtonsVisible => downloadButtonsContainer.Any(); public bool DownloadButtonsVisible => downloadButtonsContainer.Any();
public readonly BeatmapRulesetSelector RulesetSelector;
public readonly BeatmapPicker Picker; public readonly BeatmapPicker Picker;
private readonly FavouriteButton favouriteButton; private readonly FavouriteButton favouriteButton;
@ -47,6 +50,9 @@ namespace osu.Game.Overlays.BeatmapSet
private readonly LoadingAnimation loading; private readonly LoadingAnimation loading;
[Cached(typeof(IBindable<RulesetInfo>))]
private readonly Bindable<RulesetInfo> ruleset = new Bindable<RulesetInfo>();
public Header() public Header()
{ {
ExternalLinkButton externalLink; ExternalLinkButton externalLink;
@ -69,12 +75,18 @@ namespace osu.Game.Overlays.BeatmapSet
{ {
RelativeSizeAxes = Axes.X, RelativeSizeAxes = Axes.X,
Height = tabs_height, Height = tabs_height,
Children = new[] Children = new Drawable[]
{ {
tabsBg = new Box tabsBg = new Box
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
}, },
RulesetSelector = new BeatmapRulesetSelector
{
Current = ruleset,
Anchor = Anchor.BottomCentre,
Origin = Anchor.BottomCentre,
}
}, },
}, },
new Container new Container
@ -223,7 +235,7 @@ namespace osu.Game.Overlays.BeatmapSet
BeatmapSet.BindValueChanged(setInfo => BeatmapSet.BindValueChanged(setInfo =>
{ {
Picker.BeatmapSet = author.BeatmapSet = beatmapAvailability.BeatmapSet = Details.BeatmapSet = setInfo.NewValue; Picker.BeatmapSet = RulesetSelector.BeatmapSet = author.BeatmapSet = beatmapAvailability.BeatmapSet = Details.BeatmapSet = setInfo.NewValue;
cover.BeatmapSet = setInfo.NewValue; cover.BeatmapSet = setInfo.NewValue;
if (setInfo.NewValue == null) if (setInfo.NewValue == null)