Merge branch 'master' into timeline-zoom-button-repeat

This commit is contained in:
Dan Balasescu 2020-02-13 17:38:45 +09:00 committed by GitHub
commit d80e9550fb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
35 changed files with 506 additions and 234 deletions

View File

@ -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<float> CircleSize { get; } = new BindableFloat
{
Precision = 0.1f,
@ -20,7 +20,7 @@ public class CatchModDifficultyAdjust : ModDifficultyAdjust
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<float> ApproachRate { get; } = new BindableFloat
{
Precision = 0.1f,

View File

@ -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<float> CircleSize { get; } = new BindableFloat
{
Precision = 0.1f,
@ -20,7 +20,7 @@ public class OsuModDifficultyAdjust : ModDifficultyAdjust
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<float> ApproachRate { get; } = new BindableFloat
{
Precision = 0.1f,

View File

@ -5,9 +5,11 @@
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Utils;
using osu.Game.Beatmaps;
using osu.Game.Overlays;
using osu.Game.Overlays.BeatmapSet;
using osu.Game.Screens.Select.Details;
@ -22,6 +24,9 @@ public class TestSceneBeatmapSetOverlayDetails : OsuTestScene
private RatingsExposingDetails details;
[Cached]
private OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Blue);
[SetUp]
public void Setup() => Schedule(() =>
{
@ -55,8 +60,12 @@ public void TestMetrics()
{
Fails = Enumerable.Range(1, 100).Select(_ => RNG.Next(10)).ToArray(),
Retries = Enumerable.Range(-2, 100).Select(_ => RNG.Next(10)).ToArray(),
}
},
}
},
OnlineInfo = new BeatmapSetOnlineInfo
{
Status = BeatmapSetOnlineStatus.Ranked
}
};
}

View File

@ -5,11 +5,13 @@
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Utils;
using osu.Game.Beatmaps;
using osu.Game.Overlays;
using osu.Game.Overlays.BeatmapSet;
using osu.Game.Screens.Select.Details;
using osuTK;
@ -26,6 +28,9 @@ public class TestSceneBeatmapSetOverlaySuccessRate : OsuTestScene
private GraphExposingSuccessRate successRate;
[Cached]
private OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Blue);
[SetUp]
public void Setup() => Schedule(() =>
{

View File

@ -7,11 +7,16 @@
using osu.Framework.Graphics;
using osu.Framework.Bindables;
using osu.Game.Screens.Select.Leaderboards;
using osu.Framework.Allocation;
using osu.Game.Overlays;
namespace osu.Game.Tests.Visual.Online
{
public class TestSceneLeaderboardScopeSelector : OsuTestScene
{
[Cached]
private OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Blue);
public override IReadOnlyList<Type> RequiredTypes => new[]
{
typeof(LeaderboardScopeSelector),

View File

@ -34,25 +34,7 @@ public TestSceneRankingsHeader()
{
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

View File

@ -35,6 +35,12 @@ public TestSceneRankingsSpotlightSelector()
Add(selector = new SpotlightSelector());
}
[Test]
public void TestVisibility()
{
AddStep("Toggle Visibility", selector.ToggleVisibility);
}
[Test]
public void TestLocalSpotlights()
{

View File

@ -0,0 +1,55 @@
// 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 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<Type> 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<RulesetInfo>(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);
}
}
}

View File

@ -17,6 +17,7 @@
using osu.Framework.Testing;
using osu.Game.Beatmaps;
using osu.Game.Configuration;
using osu.Game.Graphics.UserInterface;
using osu.Game.Overlays;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Mods;
@ -426,6 +427,44 @@ public void TestStartAfterUnMatchingFilterDoesNotStart()
AddAssert("start not requested", () => !startRequested);
}
[TestCase(false)]
[TestCase(true)]
public void TestExternalBeatmapChangeWhileFiltered(bool differentRuleset)
{
createSongSelect();
addManyTestMaps();
changeRuleset(0);
AddUntilStep("has selection", () => songSelect.Carousel.SelectedBeatmap != null);
AddStep("set filter text", () => songSelect.FilterControl.ChildrenOfType<SearchTextBox>().First().Text = "nonono");
AddUntilStep("dummy selected", () => Beatmap.Value is DummyWorkingBeatmap);
AddUntilStep("has no selection", () => songSelect.Carousel.SelectedBeatmap == null);
BeatmapInfo target = null;
AddStep("select beatmap externally", () =>
{
target = manager.GetAllUsableBeatmapSets().Where(b => b.Beatmaps.Any(bi => bi.RulesetID == (differentRuleset ? 1 : 0)))
.ElementAt(5).Beatmaps.First();
Beatmap.Value = manager.GetWorkingBeatmap(target);
});
AddUntilStep("has selection", () => songSelect.Carousel.SelectedBeatmap != null);
AddUntilStep("carousel has correct", () => songSelect.Carousel.SelectedBeatmap?.OnlineBeatmapID == target.OnlineBeatmapID);
AddUntilStep("game has correct", () => Beatmap.Value.BeatmapInfo.OnlineBeatmapID == target.OnlineBeatmapID);
AddStep("reset filter text", () => songSelect.FilterControl.ChildrenOfType<SearchTextBox>().First().Text = string.Empty);
AddAssert("game still correct", () => Beatmap.Value?.BeatmapInfo.OnlineBeatmapID == target.OnlineBeatmapID);
AddAssert("carousel still correct", () => songSelect.Carousel.SelectedBeatmap.OnlineBeatmapID == target.OnlineBeatmapID);
}
[Test]
public void TestAutoplayViaCtrlEnter()
{
@ -468,6 +507,7 @@ public void TestHideSetSelectsCorrectBeatmap()
private void importForRuleset(int id) => manager.Import(createTestBeatmapSet(getImportId(), rulesets.AvailableRulesets.Where(r => r.ID == id).ToArray())).Wait();
private static int importId;
private int getImportId() => ++importId;
private void checkMusicPlaying(bool playing) =>
@ -551,6 +591,8 @@ private class TestSongSelect : PlaySongSelect
public new Bindable<RulesetInfo> Ruleset => base.Ruleset;
public new FilterControl FilterControl => base.FilterControl;
public WorkingBeatmap CurrentBeatmap => Beatmap.Value;
public WorkingBeatmap CurrentBeatmapDetailsBeatmap => BeatmapDetails.Beatmap;
public new BeatmapCarousel Carousel => base.Carousel;

View File

@ -13,6 +13,7 @@ namespace osu.Game.Beatmaps.Drawables
public class BeatmapSetOnlineStatusPill : CircularContainer
{
private readonly OsuSpriteText statusText;
private readonly Box background;
private BeatmapSetOnlineStatus status;
@ -43,6 +44,12 @@ public MarginPadding TextPadding
set => statusText.Padding = value;
}
public Color4 BackgroundColour
{
get => background.Colour;
set => background.Colour = value;
}
public BeatmapSetOnlineStatusPill()
{
AutoSizeAxes = Axes.Both;
@ -50,7 +57,7 @@ public BeatmapSetOnlineStatusPill()
Children = new Drawable[]
{
new Box
background = new Box
{
RelativeSizeAxes = Axes.Both,
Colour = Color4.Black,

View File

@ -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 <see cref="SettingSourceExtensions.CreateSettingsControls"/> to automatically create UI controls.
/// </summary>
/// <remarks>
/// All controls with <see cref="OrderPosition"/> set will be placed first in ascending order.
/// All controls with no <see cref="OrderPosition"/> will come afterward in default order.
/// </remarks>
[MeansImplicitUse]
[AttributeUsage(AttributeTargets.Property)]
public class SettingSourceAttribute : Attribute
@ -24,18 +29,26 @@ public class SettingSourceAttribute : Attribute
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<Drawable> 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 @@ public static IEnumerable<Drawable> CreateSettingsControls(this object obj)
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);
}
}
}

View File

@ -49,14 +49,7 @@ private class GradientLine : GridContainer
public GradientLine()
{
RelativeSizeAxes = Axes.X;
Size = new Vector2(0.8f, 1.5f);
ColumnDimensions = new[]
{
new Dimension(),
new Dimension(mode: GridSizeMode.Relative, size: 0.4f),
new Dimension(),
};
Size = new Vector2(0.8f, 1f);
Content = new[]
{
@ -65,16 +58,12 @@ public GradientLine()
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = ColourInfo.GradientHorizontal(Color4.Transparent, Color4.White)
Colour = ColourInfo.GradientHorizontal(Color4.Transparent, Colour)
},
new Box
{
RelativeSizeAxes = Axes.Both,
},
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = ColourInfo.GradientHorizontal(Color4.White, Color4.Transparent)
Colour = ColourInfo.GradientHorizontal(Colour, Color4.Transparent)
},
}
};

View File

@ -26,6 +26,9 @@ public class APISpotlight
[JsonProperty(@"end_date")]
public DateTimeOffset EndDate;
[JsonProperty(@"participant_count")]
public int? Participants;
public override string ToString() => Name;
}
}

View File

@ -150,7 +150,7 @@ private void load(IAPIProvider api, BeatmapManager beatmaps)
},
new OsuSpriteText
{
Text = BeatmapSet.Value.OnlineInfo.HasVideo && noVideo ? "without Video" : string.Empty,
Text = getVideoSuffixText(),
Font = OsuFont.GetFont(size: 11, weight: FontWeight.Bold)
},
};
@ -163,5 +163,13 @@ private void load(IAPIProvider api, BeatmapManager beatmaps)
private void userChanged(ValueChangedEvent<User> e) => button.Enabled.Value = !(e.NewValue is GuestUser);
private void enabledChanged(ValueChangedEvent<bool> e) => this.FadeColour(e.NewValue ? Color4.White : Color4.Gray, 200, Easing.OutQuint);
private string getVideoSuffixText()
{
if (!BeatmapSet.Value.OnlineInfo.HasVideo)
return string.Empty;
return noVideo ? "without Video" : "with Video";
}
}
}

View File

@ -3,7 +3,6 @@
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;
@ -14,7 +13,6 @@
using osu.Game.Graphics.Containers;
using osu.Game.Overlays.Direct;
using osuTK;
using osuTK.Graphics;
namespace osu.Game.Overlays.BeatmapSet.Buttons
{
@ -22,7 +20,7 @@ public class PreviewButton : OsuClickableContainer
{
private const float transition_duration = 500;
private readonly Box bg, progress;
private readonly Box background, progress;
private readonly PlayButton playButton;
private PreviewTrack preview => playButton.Preview;
@ -40,10 +38,10 @@ public PreviewButton()
Children = new Drawable[]
{
bg = new Box
background = new Box
{
RelativeSizeAxes = Axes.Both,
Colour = Color4.Black.Opacity(0.25f),
Alpha = 0.5f
},
new Container
{
@ -71,9 +69,10 @@ public PreviewButton()
}
[BackgroundDependencyLoader]
private void load(OsuColour colours)
private void load(OsuColour colours, OverlayColourProvider colourProvider)
{
progress.Colour = colours.Yellow;
background.Colour = colourProvider.Background6;
}
protected override void Update()
@ -91,13 +90,13 @@ protected override void Update()
protected override bool OnHover(HoverEvent e)
{
bg.FadeColour(Color4.Black.Opacity(0.5f), 100);
background.FadeTo(0.75f, 80);
return base.OnHover(e);
}
protected override void OnHoverLost(HoverLostEvent e)
{
bg.FadeColour(Color4.Black.Opacity(0.25f), 100);
background.FadeTo(0.5f, 80);
base.OnHoverLost(e);
}
}

View File

@ -2,7 +2,6 @@
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Allocation;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
@ -10,7 +9,6 @@
using osu.Game.Overlays.BeatmapSet.Buttons;
using osu.Game.Screens.Select.Details;
using osuTK;
using osuTK.Graphics;
namespace osu.Game.Overlays.BeatmapSet
{
@ -21,6 +19,7 @@ public class Details : FillFlowContainer
private readonly PreviewButton preview;
private readonly BasicStats basic;
private readonly AdvancedStats advanced;
private readonly DetailBox ratingBox;
private BeatmapSetInfo beatmapSet;
@ -54,6 +53,7 @@ public BeatmapInfo Beatmap
private void updateDisplay()
{
Ratings.Metrics = BeatmapSet?.Metrics;
ratingBox.Alpha = BeatmapSet?.OnlineInfo?.Status > 0 ? 1 : 0;
}
public Details()
@ -86,7 +86,7 @@ public Details()
Margin = new MarginPadding { Vertical = 7.5f },
},
},
new DetailBox
ratingBox = new DetailBox
{
Child = Ratings = new UserRatings
{
@ -107,6 +107,8 @@ private void load()
private class DetailBox : Container
{
private readonly Container content;
private readonly Box background;
protected override Container<Drawable> Content => content;
public DetailBox()
@ -116,10 +118,10 @@ public DetailBox()
InternalChildren = new Drawable[]
{
new Box
background = new Box
{
RelativeSizeAxes = Axes.Both,
Colour = Color4.Black.Opacity(0.5f),
Alpha = 0.5f
},
content = new Container
{
@ -129,6 +131,12 @@ public DetailBox()
},
};
}
[BackgroundDependencyLoader]
private void load(OverlayColourProvider colourProvider)
{
background.Colour = colourProvider.Background6;
}
}
}
}

View File

@ -1,4 +1,4 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// 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 System.Linq;
@ -30,6 +30,7 @@ public class Header : BeatmapDownloadTrackingComposite
private const float buttons_spacing = 5;
private readonly UpdateableBeatmapSetCover cover;
private readonly Box coverGradient;
private readonly OsuSpriteText title, artist;
private readonly AuthorInfo author;
private readonly FillFlowContainer downloadButtonsContainer;
@ -93,10 +94,9 @@ public Header()
RelativeSizeAxes = Axes.Both,
Masking = true,
},
new Box
coverGradient = new Box
{
RelativeSizeAxes = Axes.Both,
Colour = ColourInfo.GradientVertical(Color4.Black.Opacity(0.3f), Color4.Black.Opacity(0.8f)),
RelativeSizeAxes = Axes.Both
},
},
},
@ -106,8 +106,7 @@ public Header()
AutoSizeAxes = Axes.Y,
Padding = new MarginPadding
{
Top = 20,
Bottom = 30,
Vertical = BeatmapSetOverlay.Y_PADDING,
Left = BeatmapSetOverlay.X_PADDING,
Right = BeatmapSetOverlay.X_PADDING + BeatmapSetOverlay.RIGHT_WIDTH,
},
@ -130,11 +129,12 @@ public Header()
{
Direction = FillDirection.Horizontal,
AutoSizeAxes = Axes.Both,
Margin = new MarginPadding { Top = 15 },
Children = new Drawable[]
{
title = new OsuSpriteText
{
Font = OsuFont.GetFont(size: 37, weight: FontWeight.Bold, italics: true)
Font = OsuFont.GetFont(size: 30, weight: FontWeight.SemiBold, italics: true)
},
externalLink = new ExternalLinkButton
{
@ -144,7 +144,7 @@ public Header()
},
}
},
artist = new OsuSpriteText { Font = OsuFont.GetFont(size: 25, weight: FontWeight.SemiBold, italics: true) },
artist = new OsuSpriteText { Font = OsuFont.GetFont(size: 20, weight: FontWeight.Medium, italics: true) },
new Container
{
RelativeSizeAxes = Axes.X,
@ -187,7 +187,7 @@ public Header()
Anchor = Anchor.BottomRight,
Origin = Anchor.BottomRight,
AutoSizeAxes = Axes.Both,
Margin = new MarginPadding { Top = BeatmapSetOverlay.TOP_PADDING, Right = BeatmapSetOverlay.X_PADDING },
Margin = new MarginPadding { Top = BeatmapSetOverlay.Y_PADDING, Right = BeatmapSetOverlay.X_PADDING },
Direction = FillDirection.Vertical,
Spacing = new Vector2(10),
Children = new Drawable[]
@ -197,7 +197,7 @@ public Header()
Anchor = Anchor.TopRight,
Origin = Anchor.TopRight,
TextSize = 14,
TextPadding = new MarginPadding { Horizontal = 25, Vertical = 8 }
TextPadding = new MarginPadding { Horizontal = 35, Vertical = 10 }
},
Details = new Details(),
},
@ -215,8 +215,11 @@ public Header()
}
[BackgroundDependencyLoader]
private void load(OsuColour colours)
private void load(OverlayColourProvider colourProvider)
{
coverGradient.Colour = ColourInfo.GradientVertical(colourProvider.Background6.Opacity(0.3f), colourProvider.Background6.Opacity(0.8f));
onlineStatusPill.BackgroundColour = colourProvider.Background6;
State.BindValueChanged(_ => updateDownloadButtons());
BeatmapSet.BindValueChanged(setInfo =>

View File

@ -38,6 +38,8 @@ public BeatmapInfo Beatmap
public Info()
{
MetadataSection source, tags, genre, language;
OsuSpriteText unrankedPlaceholder;
RelativeSizeAxes = Axes.X;
Height = 220;
Masking = true;
@ -110,6 +112,14 @@ public Info()
RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding { Top = 20, Horizontal = 15 },
},
unrankedPlaceholder = new OsuSpriteText
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Alpha = 0,
Text = "Unranked beatmap",
Font = OsuFont.GetFont(size: 12)
},
},
},
},
@ -122,6 +132,9 @@ public Info()
tags.Text = b.NewValue?.Metadata.Tags ?? string.Empty;
genre.Text = b.NewValue?.OnlineInfo?.Genre?.Name ?? string.Empty;
language.Text = b.NewValue?.OnlineInfo?.Language?.Name ?? string.Empty;
var setHasLeaderboard = b.NewValue?.OnlineInfo?.Status > 0;
successRate.Alpha = setHasLeaderboard ? 1 : 0;
unrankedPlaceholder.Alpha = setHasLeaderboard ? 0 : 1;
};
}

View File

@ -26,10 +26,10 @@ public LeaderboardScopeSelector()
}
[BackgroundDependencyLoader]
private void load(OsuColour colours)
private void load(OverlayColourProvider colourProvider)
{
AccentColour = colours.Blue;
LineColour = Color4.Gray;
AccentColour = colourProvider.Highlight1;
LineColour = colourProvider.Background1;
}
private class ScopeSelectorTabItem : PageTabItem

View File

@ -77,9 +77,9 @@ private TableColumn[] createHeaders(ScoreInfo score)
new TableColumn("rank", Anchor.CentreRight, new Dimension(GridSizeMode.AutoSize)),
new TableColumn("", Anchor.Centre, new Dimension(GridSizeMode.Absolute, 70)), // grade
new TableColumn("score", Anchor.CentreLeft, new Dimension(GridSizeMode.AutoSize)),
new TableColumn("accuracy", Anchor.CentreLeft, new Dimension(GridSizeMode.AutoSize)),
new TableColumn("accuracy", Anchor.CentreLeft, new Dimension(GridSizeMode.Distributed, minSize: 60, maxSize: 70)),
new TableColumn("player", Anchor.CentreLeft, new Dimension(GridSizeMode.Distributed, minSize: 150)),
new TableColumn("max combo", Anchor.CentreLeft, new Dimension(GridSizeMode.Distributed, minSize: 70, maxSize: 90))
new TableColumn("max combo", Anchor.CentreLeft, new Dimension(GridSizeMode.Distributed, minSize: 70, maxSize: 110))
};
foreach (var statistic in score.SortedStatistics)

View File

@ -30,7 +30,7 @@ public ScoreTableRowBackground(int index, ScoreInfo score)
RelativeSizeAxes = Axes.X;
Height = 25;
CornerRadius = 3;
CornerRadius = 5;
Masking = true;
InternalChildren = new Drawable[]

View File

@ -164,7 +164,7 @@ public ScoresContainer()
{
RelativeSizeAxes = Axes.Both,
Masking = true,
CornerRadius = 10,
CornerRadius = 5,
Child = loading = new DimmedLoadingLayer(iconScale: 0.8f)
{
Alpha = 0,

View File

@ -96,13 +96,14 @@ public TopScoreUserSection()
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
Font = OsuFont.GetFont(size: 10, weight: FontWeight.Bold)
Font = OsuFont.GetFont(size: 10)
},
flag = new UpdateableFlag
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
Size = new Vector2(19, 13),
Margin = new MarginPadding { Top = 3 }, // makes spacing look more even
ShowPlaceholderOnNull = false,
},
}

View File

@ -42,7 +42,7 @@ private void updateDisplay()
int playCount = beatmap?.OnlineInfo?.PlayCount ?? 0;
var rate = playCount != 0 ? (float)passCount / playCount : 0;
successPercent.Text = rate.ToString("0%");
successPercent.Text = rate.ToString("0.#%");
successRate.Length = rate;
percentContainer.ResizeWidthTo(successRate.Length, 250, Easing.InOutCubic);
@ -105,10 +105,10 @@ public SuccessRate()
}
[BackgroundDependencyLoader]
private void load(OsuColour colours)
private void load(OsuColour colours, OverlayColourProvider colourProvider)
{
successRate.AccentColour = colours.Green;
successRate.BackgroundColour = colours.GrayD;
successRate.BackgroundColour = colourProvider.Background6;
updateDisplay();
}

View File

@ -21,7 +21,7 @@ namespace osu.Game.Overlays
public class BeatmapSetOverlay : FullscreenOverlay
{
public const float X_PADDING = 40;
public const float TOP_PADDING = 25;
public const float Y_PADDING = 25;
public const float RIGHT_WIDTH = 275;
protected readonly Header Header;

View File

@ -6,25 +6,14 @@
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<RankingsScope>
{
public readonly Bindable<RulesetInfo> Ruleset = new Bindable<RulesetInfo>();
public readonly Bindable<Spotlight> Spotlight = new Bindable<Spotlight>();
public readonly Bindable<Country> Country = new Bindable<Country>();
public IEnumerable<Spotlight> Spotlights
{
get => spotlightsContainer.Spotlights;
set => spotlightsContainer.Spotlights = value;
}
protected override ScreenTitle CreateTitle() => new RankingsTitle
{
Scope = { BindTarget = Current }
@ -35,35 +24,11 @@ public IEnumerable<Spotlight> Spotlights
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<RankingsScope> scope) =>
spotlightsContainer.FadeTo(scope.NewValue == RankingsScope.Spotlights ? 1 : 0, 200, Easing.OutQuint);
private class RankingsTitle : ScreenTitle
{
public readonly Bindable<RankingsScope> Scope = new Bindable<RankingsScope>();
@ -81,48 +46,6 @@ protected override void LoadComplete()
protected override Drawable CreateIcon() => new ScreenTitleTextureIcon(@"Icons/rankings");
}
private class SpotlightsContainer : CompositeDrawable
{
public readonly Bindable<Spotlight> Spotlight = new Bindable<Spotlight>();
public IEnumerable<Spotlight> Spotlights
{
get => dropdown.Items;
set => dropdown.Items = value;
}
private readonly OsuDropdown<Spotlight> dropdown;
private readonly Box background;
public SpotlightsContainer()
{
Height = 100;
RelativeSizeAxes = Axes.X;
InternalChildren = new Drawable[]
{
background = new Box
{
RelativeSizeAxes = Axes.Both,
},
dropdown = new OsuDropdown<Spotlight>
{
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

View File

@ -1,18 +0,0 @@
// 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 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;
}
}

View File

@ -14,11 +14,14 @@
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<APISpotlight>
public class SpotlightSelector : VisibilityContainer, IHasCurrentValue<APISpotlight>
{
private const int duration = 300;
private readonly Box background;
private readonly SpotlightsDropdown dropdown;
@ -36,50 +39,60 @@ public IEnumerable<APISpotlight> Spotlights
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 @@ private void load(OverlayColourProvider colourProvider)
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<APISpotlight> 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");

View File

@ -0,0 +1,161 @@
// 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.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<RulesetInfo> Ruleset = new Bindable<RulesetInfo>();
private readonly Bindable<APISpotlight> selectedSpotlight = new Bindable<APISpotlight>();
[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<Drawable>
{
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<RulesetInfo> ruleset)
{
if (!selector.Spotlights.Any())
return;
selectedSpotlight.TriggerChange();
}
private void onSpotlightChanged(ValueChangedEvent<APISpotlight> 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();
}
}
}

View File

@ -24,7 +24,7 @@ public class RankingsOverlay : FullscreenOverlay
private readonly Bindable<RulesetInfo> ruleset = new Bindable<RulesetInfo>();
private readonly BasicScrollContainer scrollFlow;
private readonly Container tableContainer;
private readonly Container contentContainer;
private readonly DimmedLoadingLayer loading;
private readonly Box background;
@ -69,13 +69,13 @@ public RankingsOverlay()
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 @@ protected override void LoadComplete()
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 @@ private void loadNewContent()
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 @@ private Drawable createTableFromResponse(APIRequest request)
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);
}
}

View File

@ -28,7 +28,11 @@ public abstract class ModDifficultyAdjust : Mod, IApplicableToDifficulty
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<float> DrainRate { get; } = new BindableFloat
{
Precision = 0.1f,
@ -38,7 +42,7 @@ public abstract class ModDifficultyAdjust : Mod, IApplicableToDifficulty
Value = 5,
};
[SettingSource("Overall Difficulty", "Override a beatmap's set OD.")]
[SettingSource("Accuracy", "Override a beatmap's set OD.", LAST_SETTING_ORDER)]
public BindableNumber<float> OverallDifficulty { get; } = new BindableFloat
{
Precision = 0.1f,

View File

@ -229,6 +229,17 @@ public bool SelectBeatmap(BeatmapInfo beatmap, bool bypassFilters = true)
if (item != null)
{
select(item);
// if we got here and the set is filtered, it means we were bypassing filters.
// in this case, reapplying the filter is necessary to ensure the panel is in the correct place
// (since it is forcefully being included in the carousel).
if (set.Filtered.Value)
{
Debug.Assert(bypassFilters);
applyActiveCriteria(false, true);
}
return true;
}
}

View File

@ -62,7 +62,7 @@ public override int CompareTo(FilterCriteria criteria, CarouselItem other)
/// <summary>
/// All beatmaps which are not filtered and valid for display.
/// </summary>
protected IEnumerable<BeatmapInfo> ValidBeatmaps => Beatmaps.Where(b => !b.Filtered.Value).Select(b => b.Beatmap);
protected IEnumerable<BeatmapInfo> ValidBeatmaps => Beatmaps.Where(b => !b.Filtered.Value || b.State.Value == CarouselItemState.Selected).Select(b => b.Beatmap);
private int compareUsingAggregateMax(CarouselBeatmapSet other, Func<BeatmapInfo, double> func)
{

View File

@ -16,7 +16,7 @@ public abstract class CarouselItem
/// <summary>
/// This item is not in a hidden state.
/// </summary>
public bool Visible => State.Value != CarouselItemState.Collapsed && !Filtered.Value;
public bool Visible => State.Value == CarouselItemState.Selected || (State.Value != CarouselItemState.Collapsed && !Filtered.Value);
public virtual List<DrawableCarouselItem> Drawables
{

View File

@ -376,16 +376,22 @@ public void FinaliseSelection(BeatmapInfo beatmap = null, bool performStartActio
private void workingBeatmapChanged(ValueChangedEvent<WorkingBeatmap> e)
{
if (e.NewValue is DummyWorkingBeatmap) return;
if (e.NewValue is DummyWorkingBeatmap || !this.IsCurrentScreen()) return;
if (this.IsCurrentScreen() && !Carousel.SelectBeatmap(e.NewValue?.BeatmapInfo, false))
if (!Carousel.SelectBeatmap(e.NewValue.BeatmapInfo, false))
{
// If selecting new beatmap without bypassing filters failed, there's possibly a ruleset mismatch
if (e.NewValue?.BeatmapInfo?.Ruleset != null && !e.NewValue.BeatmapInfo.Ruleset.Equals(decoupledRuleset.Value))
// A selection may not have been possible with filters applied.
// There was possibly a ruleset mismatch. This is a case we can help things along by updating the game-wide ruleset to match.
if (e.NewValue.BeatmapInfo.Ruleset != null && !e.NewValue.BeatmapInfo.Ruleset.Equals(decoupledRuleset.Value))
{
Ruleset.Value = e.NewValue.BeatmapInfo.Ruleset;
Carousel.SelectBeatmap(e.NewValue.BeatmapInfo);
transferRulesetValue();
}
// Even if a ruleset mismatch was not the cause (ie. a text filter is applied),
// we still want to forcefully show the new beatmap, bypassing filters.
Carousel.SelectBeatmap(e.NewValue.BeatmapInfo);
}
}