1
0
mirror of https://github.com/ppy/osu.git synced 2025-03-05 12:32:58 +08:00

Merge branch 'master' into difficulty-cache-difficulty-adjust

This commit is contained in:
Dan Balasescu 2021-08-24 12:08:52 +09:00 committed by GitHub
commit dfd61e413a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
58 changed files with 770 additions and 427 deletions

View File

@ -51,7 +51,7 @@
<Reference Include="Java.Interop" /> <Reference Include="Java.Interop" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="ppy.osu.Game.Resources" Version="2021.820.0" /> <PackageReference Include="ppy.osu.Game.Resources" Version="2021.822.0" />
<PackageReference Include="ppy.osu.Framework.Android" Version="2021.819.0" /> <PackageReference Include="ppy.osu.Framework.Android" Version="2021.819.0" />
</ItemGroup> </ItemGroup>
<ItemGroup Label="Transitive Dependencies"> <ItemGroup Label="Transitive Dependencies">

View File

@ -20,8 +20,21 @@ namespace osu.Android
[Activity(Theme = "@android:style/Theme.NoTitleBar", MainLauncher = true, ScreenOrientation = ScreenOrientation.FullUser, SupportsPictureInPicture = false, ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.ScreenSize, HardwareAccelerated = false, LaunchMode = LaunchMode.SingleInstance, Exported = true)] [Activity(Theme = "@android:style/Theme.NoTitleBar", MainLauncher = true, ScreenOrientation = ScreenOrientation.FullUser, SupportsPictureInPicture = false, ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.ScreenSize, HardwareAccelerated = false, LaunchMode = LaunchMode.SingleInstance, Exported = true)]
[IntentFilter(new[] { Intent.ActionView }, Categories = new[] { Intent.CategoryDefault }, DataScheme = "content", DataPathPattern = ".*\\\\.osz", DataHost = "*", DataMimeType = "*/*")] [IntentFilter(new[] { Intent.ActionView }, Categories = new[] { Intent.CategoryDefault }, DataScheme = "content", DataPathPattern = ".*\\\\.osz", DataHost = "*", DataMimeType = "*/*")]
[IntentFilter(new[] { Intent.ActionView }, Categories = new[] { Intent.CategoryDefault }, DataScheme = "content", DataPathPattern = ".*\\\\.osk", DataHost = "*", DataMimeType = "*/*")] [IntentFilter(new[] { Intent.ActionView }, Categories = new[] { Intent.CategoryDefault }, DataScheme = "content", DataPathPattern = ".*\\\\.osk", DataHost = "*", DataMimeType = "*/*")]
[IntentFilter(new[] { Intent.ActionView }, Categories = new[] { Intent.CategoryDefault }, DataScheme = "content", DataMimeType = "application/x-osu-archive")] [IntentFilter(new[] { Intent.ActionView }, Categories = new[] { Intent.CategoryDefault }, DataScheme = "content", DataMimeType = "application/x-osu-beatmap-archive")]
[IntentFilter(new[] { Intent.ActionSend, Intent.ActionSendMultiple }, Categories = new[] { Intent.CategoryDefault }, DataMimeTypes = new[] { "application/zip", "application/octet-stream", "application/download", "application/x-zip", "application/x-zip-compressed", "application/x-osu-archive" })] [IntentFilter(new[] { Intent.ActionView }, Categories = new[] { Intent.CategoryDefault }, DataScheme = "content", DataMimeType = "application/x-osu-skin-archive")]
[IntentFilter(new[] { Intent.ActionView }, Categories = new[] { Intent.CategoryDefault }, DataScheme = "content", DataMimeType = "application/x-osu-replay")]
[IntentFilter(new[] { Intent.ActionSend, Intent.ActionSendMultiple }, Categories = new[] { Intent.CategoryDefault }, DataMimeTypes = new[]
{
"application/zip",
"application/octet-stream",
"application/download",
"application/x-zip",
"application/x-zip-compressed",
// newer official mime types (see https://osu.ppy.sh/wiki/en/osu%21_File_Formats).
"application/x-osu-beatmap-archive",
"application/x-osu-skin-archive",
"application/x-osu-replay",
})]
[IntentFilter(new[] { Intent.ActionView }, Categories = new[] { Intent.CategoryBrowsable, Intent.CategoryDefault }, DataSchemes = new[] { "osu", "osump" })] [IntentFilter(new[] { Intent.ActionView }, Categories = new[] { Intent.CategoryBrowsable, Intent.CategoryDefault }, DataSchemes = new[] { "osu", "osump" })]
public class OsuGameActivity : AndroidGameActivity public class OsuGameActivity : AndroidGameActivity
{ {
@ -66,12 +79,14 @@ namespace osu.Android
case Intent.ActionSendMultiple: case Intent.ActionSendMultiple:
{ {
var uris = new List<Uri>(); var uris = new List<Uri>();
for (int i = 0; i < intent.ClipData?.ItemCount; i++) for (int i = 0; i < intent.ClipData?.ItemCount; i++)
{ {
var content = intent.ClipData?.GetItemAt(i); var content = intent.ClipData?.GetItemAt(i);
if (content != null) if (content != null)
uris.Add(content.Uri); uris.Add(content.Uri);
} }
handleImportFromUris(uris.ToArray()); handleImportFromUris(uris.ToArray());
break; break;
} }

View File

@ -1,8 +1,12 @@
// 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. // See the LICENCE file in the repository root for full licence text.
using System;
using System.Linq;
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Testing; using osu.Framework.Testing;
using osu.Framework.Utils;
using osu.Game.Configuration;
using osu.Game.Overlays; using osu.Game.Overlays;
namespace osu.Game.Tests.Visual.Menus namespace osu.Game.Tests.Visual.Menus
@ -21,21 +25,48 @@ namespace osu.Game.Tests.Visual.Menus
[Test] [Test]
public void TestScreenOffsettingOnSettingsOverlay() public void TestScreenOffsettingOnSettingsOverlay()
{ {
AddStep("open settings", () => Game.Settings.Show()); foreach (var scalingMode in Enum.GetValues(typeof(ScalingMode)).Cast<ScalingMode>())
AddUntilStep("right screen offset applied", () => Game.ScreenOffsetContainer.X == SettingsPanel.WIDTH * TestOsuGame.SIDE_OVERLAY_OFFSET_RATIO); {
AddStep($"set scaling mode to {scalingMode}", () =>
{
Game.LocalConfig.SetValue(OsuSetting.Scaling, scalingMode);
AddStep("hide settings", () => Game.Settings.Hide()); if (scalingMode != ScalingMode.Off)
AddUntilStep("screen offset removed", () => Game.ScreenOffsetContainer.X == 0f); {
Game.LocalConfig.SetValue(OsuSetting.ScalingSizeX, 0.5f);
Game.LocalConfig.SetValue(OsuSetting.ScalingSizeY, 0.5f);
}
});
AddStep("open settings", () => Game.Settings.Show());
AddUntilStep("right screen offset applied", () => Precision.AlmostEquals(Game.ScreenOffsetContainer.X, SettingsPanel.WIDTH * TestOsuGame.SIDE_OVERLAY_OFFSET_RATIO));
AddStep("hide settings", () => Game.Settings.Hide());
AddUntilStep("screen offset removed", () => Game.ScreenOffsetContainer.X == 0f);
}
} }
[Test] [Test]
public void TestScreenOffsettingOnNotificationOverlay() public void TestScreenOffsettingOnNotificationOverlay()
{ {
AddStep("open notifications", () => Game.Notifications.Show()); foreach (var scalingMode in Enum.GetValues(typeof(ScalingMode)).Cast<ScalingMode>())
AddUntilStep("right screen offset applied", () => Game.ScreenOffsetContainer.X == -NotificationOverlay.WIDTH * TestOsuGame.SIDE_OVERLAY_OFFSET_RATIO); {
if (scalingMode != ScalingMode.Off)
{
AddStep($"set scaling mode to {scalingMode}", () =>
{
Game.LocalConfig.SetValue(OsuSetting.Scaling, scalingMode);
Game.LocalConfig.SetValue(OsuSetting.ScalingSizeX, 0.5f);
Game.LocalConfig.SetValue(OsuSetting.ScalingSizeY, 0.5f);
});
}
AddStep("hide notifications", () => Game.Notifications.Hide()); AddStep("open notifications", () => Game.Notifications.Show());
AddUntilStep("screen offset removed", () => Game.ScreenOffsetContainer.X == 0f); AddUntilStep("right screen offset applied", () => Precision.AlmostEquals(Game.ScreenOffsetContainer.X, -NotificationOverlay.WIDTH * TestOsuGame.SIDE_OVERLAY_OFFSET_RATIO));
AddStep("hide notifications", () => Game.Notifications.Hide());
AddUntilStep("screen offset removed", () => Game.ScreenOffsetContainer.X == 0f);
}
} }
} }
} }

View File

@ -24,12 +24,11 @@ namespace osu.Game.Tests.Visual.Multiplayer
{ {
public class TestSceneDrawableRoom : OsuTestScene public class TestSceneDrawableRoom : OsuTestScene
{ {
[Cached]
private readonly Bindable<Room> selectedRoom = new Bindable<Room>();
[Cached] [Cached]
protected readonly OverlayColourProvider ColourProvider = new OverlayColourProvider(OverlayColourScheme.Plum); protected readonly OverlayColourProvider ColourProvider = new OverlayColourProvider(OverlayColourScheme.Plum);
private readonly Bindable<Room> selectedRoom = new Bindable<Room>();
[Test] [Test]
public void TestMultipleStatuses() public void TestMultipleStatuses()
{ {
@ -153,7 +152,11 @@ namespace osu.Game.Tests.Visual.Multiplayer
})); }));
} }
return new DrawableLoungeRoom(room) { MatchingFilter = true }; return new DrawableLoungeRoom(room)
{
MatchingFilter = true,
SelectedRoom = { BindTarget = selectedRoom }
};
} }
} }
} }

View File

@ -30,6 +30,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
Anchor = Anchor.Centre, Anchor = Anchor.Centre,
Origin = Anchor.Centre, Origin = Anchor.Centre,
Width = 0.5f, Width = 0.5f,
SelectedRoom = { BindTarget = SelectedRoom }
}; };
}); });

View File

@ -1,12 +1,15 @@
// 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. // See the LICENCE file in the repository root for full licence text.
using osu.Framework.Allocation;
using osu.Game.Overlays;
using NUnit.Framework; using NUnit.Framework;
using osu.Game.Users;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Graphics.Containers;
using osu.Game.Overlays;
using osu.Game.Overlays.Rankings; using osu.Game.Overlays.Rankings;
using osu.Game.Rulesets.Catch;
using osu.Game.Rulesets.Mania;
using osu.Game.Rulesets.Osu;
using osu.Game.Users;
namespace osu.Game.Tests.Visual.Online namespace osu.Game.Tests.Visual.Online
{ {
@ -14,25 +17,29 @@ namespace osu.Game.Tests.Visual.Online
{ {
protected override bool UseOnlineAPI => true; protected override bool UseOnlineAPI => true;
[Cached(typeof(RankingsOverlay))] private TestRankingsOverlay rankingsOverlay;
private readonly RankingsOverlay rankingsOverlay;
private readonly Bindable<Country> countryBindable = new Bindable<Country>(); private readonly Bindable<Country> countryBindable = new Bindable<Country>();
private readonly Bindable<RankingsScope> scope = new Bindable<RankingsScope>(); private readonly Bindable<RankingsScope> scope = new Bindable<RankingsScope>();
public TestSceneRankingsOverlay() [SetUp]
{ public void SetUp() => Schedule(loadRankingsOverlay);
Add(rankingsOverlay = new TestRankingsOverlay
{
Country = { BindTarget = countryBindable },
Header = { Current = { BindTarget = scope } },
});
}
[Test] [Test]
public void TestShow() public void TestParentRulesetDecoupledAfterInitialShow()
{ {
AddStep("Show", rankingsOverlay.Show); AddStep("enable global ruleset", () => Ruleset.Disabled = false);
AddStep("set global ruleset to osu!catch", () => Ruleset.Value = new CatchRuleset().RulesetInfo);
AddStep("reload rankings overlay", loadRankingsOverlay);
AddAssert("rankings ruleset set to osu!catch", () => rankingsOverlay.Header.Ruleset.Value.ShortName == CatchRuleset.SHORT_NAME);
AddStep("set global ruleset to osu!", () => Ruleset.Value = new OsuRuleset().RulesetInfo);
AddAssert("rankings ruleset still osu!catch", () => rankingsOverlay.Header.Ruleset.Value.ShortName == CatchRuleset.SHORT_NAME);
AddStep("disable global ruleset", () => Ruleset.Disabled = true);
AddAssert("rankings ruleset still enabled", () => rankingsOverlay.Header.Ruleset.Disabled == false);
AddStep("set rankings ruleset to osu!mania", () => rankingsOverlay.Header.Ruleset.Value = new ManiaRuleset().RulesetInfo);
AddAssert("rankings ruleset set to osu!mania", () => rankingsOverlay.Header.Ruleset.Value.ShortName == ManiaRuleset.SHORT_NAME);
} }
[Test] [Test]
@ -50,10 +57,14 @@ namespace osu.Game.Tests.Visual.Online
AddStep("Show US", () => rankingsOverlay.ShowCountry(us_country)); AddStep("Show US", () => rankingsOverlay.ShowCountry(us_country));
} }
[Test] private void loadRankingsOverlay()
public void TestHide()
{ {
AddStep("Hide", rankingsOverlay.Hide); Child = rankingsOverlay = new TestRankingsOverlay
{
Country = { BindTarget = countryBindable },
Header = { Current = { BindTarget = scope } },
State = { Value = Visibility.Visible },
};
} }
private static readonly Country us_country = new Country private static readonly Country us_country = new Country

View File

@ -3,10 +3,11 @@
using System.Linq; using System.Linq;
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Bindables;
using osu.Framework.Screens; using osu.Framework.Screens;
using osu.Framework.Testing; using osu.Framework.Testing;
using osu.Game.Graphics.Containers; using osu.Game.Graphics.Containers;
using osu.Game.Screens.OnlinePlay.Lounge; using osu.Game.Online.Rooms;
using osu.Game.Screens.OnlinePlay.Lounge.Components; using osu.Game.Screens.OnlinePlay.Lounge.Components;
using osu.Game.Screens.OnlinePlay.Playlists; using osu.Game.Screens.OnlinePlay.Playlists;
using osu.Game.Tests.Visual.OnlinePlay; using osu.Game.Tests.Visual.OnlinePlay;
@ -18,13 +19,13 @@ namespace osu.Game.Tests.Visual.Playlists
{ {
protected new TestRequestHandlingRoomManager RoomManager => (TestRequestHandlingRoomManager)base.RoomManager; protected new TestRequestHandlingRoomManager RoomManager => (TestRequestHandlingRoomManager)base.RoomManager;
private LoungeSubScreen loungeScreen; private TestLoungeSubScreen loungeScreen;
public override void SetUpSteps() public override void SetUpSteps()
{ {
base.SetUpSteps(); base.SetUpSteps();
AddStep("push screen", () => LoadScreen(loungeScreen = new PlaylistsLoungeSubScreen())); AddStep("push screen", () => LoadScreen(loungeScreen = new TestLoungeSubScreen()));
AddUntilStep("wait for present", () => loungeScreen.IsCurrentScreen()); AddUntilStep("wait for present", () => loungeScreen.IsCurrentScreen());
} }
@ -69,21 +70,26 @@ namespace osu.Game.Tests.Visual.Playlists
{ {
AddStep("add rooms", () => RoomManager.AddRooms(1)); AddStep("add rooms", () => RoomManager.AddRooms(1));
AddAssert("selected room is not disabled", () => !OnlinePlayDependencies.SelectedRoom.Disabled); AddAssert("selected room is not disabled", () => !loungeScreen.SelectedRoom.Disabled);
AddStep("select room", () => roomsContainer.Rooms[0].TriggerClick()); AddStep("select room", () => roomsContainer.Rooms[0].TriggerClick());
AddAssert("selected room is non-null", () => OnlinePlayDependencies.SelectedRoom.Value != null); AddAssert("selected room is non-null", () => loungeScreen.SelectedRoom.Value != null);
AddStep("enter room", () => roomsContainer.Rooms[0].TriggerClick()); AddStep("enter room", () => roomsContainer.Rooms[0].TriggerClick());
AddUntilStep("wait for match load", () => Stack.CurrentScreen is PlaylistsRoomSubScreen); AddUntilStep("wait for match load", () => Stack.CurrentScreen is PlaylistsRoomSubScreen);
AddAssert("selected room is non-null", () => OnlinePlayDependencies.SelectedRoom.Value != null); AddAssert("selected room is non-null", () => loungeScreen.SelectedRoom.Value != null);
AddAssert("selected room is disabled", () => OnlinePlayDependencies.SelectedRoom.Disabled); AddAssert("selected room is disabled", () => loungeScreen.SelectedRoom.Disabled);
} }
private bool checkRoomVisible(DrawableRoom room) => private bool checkRoomVisible(DrawableRoom room) =>
loungeScreen.ChildrenOfType<OsuScrollContainer>().First().ScreenSpaceDrawQuad loungeScreen.ChildrenOfType<OsuScrollContainer>().First().ScreenSpaceDrawQuad
.Contains(room.ScreenSpaceDrawQuad.Centre); .Contains(room.ScreenSpaceDrawQuad.Centre);
private class TestLoungeSubScreen : PlaylistsLoungeSubScreen
{
public new Bindable<Room> SelectedRoom => base.SelectedRoom;
}
} }
} }

View File

@ -28,7 +28,7 @@ namespace osu.Game.Tests.Visual.Playlists
{ {
SelectedRoom.Value = new Room(); SelectedRoom.Value = new Room();
Child = settings = new TestRoomSettings Child = settings = new TestRoomSettings(SelectedRoom.Value)
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
State = { Value = Visibility.Visible } State = { Value = Visibility.Visible }
@ -118,6 +118,11 @@ namespace osu.Game.Tests.Visual.Playlists
public OsuDropdown<TimeSpan> DurationField => ((MatchSettings)Settings).DurationField; public OsuDropdown<TimeSpan> DurationField => ((MatchSettings)Settings).DurationField;
public OsuSpriteText ErrorText => ((MatchSettings)Settings).ErrorText; public OsuSpriteText ErrorText => ((MatchSettings)Settings).ErrorText;
public TestRoomSettings(Room room)
: base(room)
{
}
} }
private class TestDependencies : OnlinePlayTestSceneDependencies private class TestDependencies : OnlinePlayTestSceneDependencies

View File

@ -2,6 +2,7 @@
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization;
using System.Linq; using System.Linq;
using JetBrains.Annotations; using JetBrains.Annotations;
using NUnit.Framework; using NUnit.Framework;
@ -11,6 +12,7 @@ using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.UserInterface; using osu.Framework.Graphics.UserInterface;
using osu.Framework.Testing; using osu.Framework.Testing;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Graphics.Sprites; using osu.Game.Graphics.Sprites;
using osu.Game.Rulesets; using osu.Game.Rulesets;
using osu.Game.Rulesets.Catch; using osu.Game.Rulesets.Catch;
@ -19,6 +21,7 @@ using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Legacy; using osu.Game.Rulesets.Objects.Legacy;
using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Osu.Mods;
using osu.Game.Rulesets.Taiko; using osu.Game.Rulesets.Taiko;
using osu.Game.Screens.Select; using osu.Game.Screens.Select;
using osuTK; using osuTK;
@ -141,6 +144,29 @@ namespace osu.Game.Tests.Visual.SongSelect
selectBeatmap(createLongMetadata()); selectBeatmap(createLongMetadata());
} }
[Test]
public void TestBPMUpdates()
{
const float bpm = 120;
IBeatmap beatmap = createTestBeatmap(new OsuRuleset().RulesetInfo);
beatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = 60 * 1000 / bpm });
OsuModDoubleTime doubleTime = null;
selectBeatmap(beatmap);
checkDisplayedBPM(bpm);
AddStep("select DT", () => SelectedMods.Value = new[] { doubleTime = new OsuModDoubleTime() });
checkDisplayedBPM(bpm * 1.5f);
AddStep("change DT rate", () => doubleTime.SpeedChange.Value = 2);
checkDisplayedBPM(bpm * 2);
void checkDisplayedBPM(float target) =>
AddUntilStep($"displayed bpm is {target}", () => this.ChildrenOfType<BeatmapInfoWedge.WedgeInfoText.InfoLabel>().Any(
label => label.Statistic.Name == "BPM" && label.Statistic.Content == target.ToString(CultureInfo.InvariantCulture)));
}
private void selectBeatmap([CanBeNull] IBeatmap b) private void selectBeatmap([CanBeNull] IBeatmap b)
{ {
Container containerBefore = null; Container containerBefore = null;

View File

@ -128,6 +128,7 @@ namespace osu.Game.Beatmaps
BaseDifficulty = new BeatmapDifficulty(), BaseDifficulty = new BeatmapDifficulty(),
Ruleset = ruleset, Ruleset = ruleset,
Metadata = metadata, Metadata = metadata,
WidescreenStoryboard = true,
} }
} }
}; };

View File

@ -93,8 +93,8 @@ namespace osu.Game.Beatmaps.Formats
// writer.WriteLine(@"OverlayPosition: " + b.OverlayPosition); // writer.WriteLine(@"OverlayPosition: " + b.OverlayPosition);
// if (!string.IsNullOrEmpty(b.SkinPreference)) // if (!string.IsNullOrEmpty(b.SkinPreference))
// writer.WriteLine(@"SkinPreference:" + b.SkinPreference); // writer.WriteLine(@"SkinPreference:" + b.SkinPreference);
// if (b.EpilepsyWarning) if (beatmap.BeatmapInfo.EpilepsyWarning)
// writer.WriteLine(@"EpilepsyWarning: 1"); writer.WriteLine(@"EpilepsyWarning: 1");
// if (b.CountdownOffset > 0) // if (b.CountdownOffset > 0)
// writer.WriteLine(@"CountdownOffset: " + b.CountdownOffset.ToString()); // writer.WriteLine(@"CountdownOffset: " + b.CountdownOffset.ToString());
if (beatmap.BeatmapInfo.RulesetID == 3) if (beatmap.BeatmapInfo.RulesetID == 3)

View File

@ -39,6 +39,16 @@ namespace osu.Game.Localisation
/// </summary> /// </summary>
public static LocalisableString Height => new TranslatableString(getKey(@"height"), @"Height"); public static LocalisableString Height => new TranslatableString(getKey(@"height"), @"Height");
/// <summary>
/// "Downloading..."
/// </summary>
public static LocalisableString Downloading => new TranslatableString(getKey(@"downloading"), @"Downloading...");
/// <summary>
/// "Importing..."
/// </summary>
public static LocalisableString Importing => new TranslatableString(getKey(@"importing"), @"Importing...");
private static string getKey(string key) => $@"{prefix}:{key}"; private static string getKey(string key) => $@"{prefix}:{key}";
} }
} }

View File

@ -43,7 +43,8 @@ namespace osu.Game.Online.Chat
RegexOptions.IgnoreCase); RegexOptions.IgnoreCase);
// 00:00:000 (1,2,3) - test // 00:00:000 (1,2,3) - test
private static readonly Regex time_regex = new Regex(@"\d\d:\d\d:\d\d\d? [^-]*"); // regex from https://github.com/ppy/osu-web/blob/651a9bac2b60d031edd7e33b8073a469bf11edaa/resources/assets/coffee/_classes/beatmap-discussion-helper.coffee#L10
private static readonly Regex time_regex = new Regex(@"\b(((\d{2,}):([0-5]\d)[:.](\d{3}))(\s\((?:\d+[,|])*\d+\))?)");
// #osu // #osu
private static readonly Regex channel_regex = new Regex(@"(#[a-zA-Z]+[a-zA-Z0-9]+)"); private static readonly Regex channel_regex = new Regex(@"(#[a-zA-Z]+[a-zA-Z0-9]+)");

View File

@ -1018,10 +1018,13 @@ namespace osu.Game
var horizontalOffset = 0f; var horizontalOffset = 0f;
// Content.ToLocalSpace() is used instead of this.ToLocalSpace() to correctly calculate the offset with scaling modes active.
// Content is a child of a scaling container with ScalingMode.Everything set, while the game itself is never scaled.
// this avoids a visible jump in the positioning of the screen offset container.
if (Settings.IsLoaded && Settings.IsPresent) if (Settings.IsLoaded && Settings.IsPresent)
horizontalOffset += ToLocalSpace(Settings.ScreenSpaceDrawQuad.TopRight).X * SIDE_OVERLAY_OFFSET_RATIO; horizontalOffset += Content.ToLocalSpace(Settings.ScreenSpaceDrawQuad.TopRight).X * SIDE_OVERLAY_OFFSET_RATIO;
if (Notifications.IsLoaded && Notifications.IsPresent) if (Notifications.IsLoaded && Notifications.IsPresent)
horizontalOffset += (ToLocalSpace(Notifications.ScreenSpaceDrawQuad.TopLeft).X - DrawWidth) * SIDE_OVERLAY_OFFSET_RATIO; horizontalOffset += (Content.ToLocalSpace(Notifications.ScreenSpaceDrawQuad.TopLeft).X - Content.DrawWidth) * SIDE_OVERLAY_OFFSET_RATIO;
ScreenOffsetContainer.X = horizontalOffset; ScreenOffsetContainer.X = horizontalOffset;

View File

@ -13,6 +13,7 @@ using osu.Game.Beatmaps;
using osu.Game.Extensions; using osu.Game.Extensions;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Graphics.Sprites; using osu.Game.Graphics.Sprites;
using osu.Game.Resources.Localisation.Web;
using osuTK; using osuTK;
namespace osu.Game.Overlays.BeatmapSet namespace osu.Game.Overlays.BeatmapSet
@ -53,7 +54,7 @@ namespace osu.Game.Overlays.BeatmapSet
private void updateDisplay() private void updateDisplay()
{ {
bpm.Value = BeatmapSet?.OnlineInfo?.BPM.ToString(@"0.##") ?? "-"; bpm.Value = BeatmapSet?.OnlineInfo?.BPM.ToLocalisableString(@"0.##") ?? (LocalisableString)"-";
if (beatmap == null) if (beatmap == null)
{ {
@ -63,9 +64,11 @@ namespace osu.Game.Overlays.BeatmapSet
} }
else else
{ {
length.TooltipText = BeatmapsetsStrings.ShowStatsTotalLength(TimeSpan.FromMilliseconds(beatmap.Length).ToFormattedDuration());
length.Value = TimeSpan.FromMilliseconds(beatmap.Length).ToFormattedDuration(); length.Value = TimeSpan.FromMilliseconds(beatmap.Length).ToFormattedDuration();
circleCount.Value = beatmap.OnlineInfo.CircleCount.ToString();
sliderCount.Value = beatmap.OnlineInfo.SliderCount.ToString(); circleCount.Value = beatmap.OnlineInfo.CircleCount.ToLocalisableString(@"N0");
sliderCount.Value = beatmap.OnlineInfo.SliderCount.ToLocalisableString(@"N0");
} }
} }
@ -78,10 +81,26 @@ namespace osu.Game.Overlays.BeatmapSet
Direction = FillDirection.Horizontal, Direction = FillDirection.Horizontal,
Children = new[] Children = new[]
{ {
length = new Statistic(BeatmapStatisticsIconType.Length, "Length") { Width = 0.25f }, length = new Statistic(BeatmapStatisticsIconType.Length)
bpm = new Statistic(BeatmapStatisticsIconType.Bpm, "BPM") { Width = 0.25f }, {
circleCount = new Statistic(BeatmapStatisticsIconType.Circles, "Circle Count") { Width = 0.25f }, Width = 0.25f,
sliderCount = new Statistic(BeatmapStatisticsIconType.Sliders, "Slider Count") { Width = 0.25f }, TooltipText = default,
},
bpm = new Statistic(BeatmapStatisticsIconType.Bpm)
{
Width = 0.25f,
TooltipText = BeatmapsetsStrings.ShowStatsBpm
},
circleCount = new Statistic(BeatmapStatisticsIconType.Circles)
{
Width = 0.25f,
TooltipText = BeatmapsetsStrings.ShowStatsCountCircles
},
sliderCount = new Statistic(BeatmapStatisticsIconType.Sliders)
{
Width = 0.25f,
TooltipText = BeatmapsetsStrings.ShowStatsCountSliders
},
}, },
}; };
} }
@ -96,7 +115,7 @@ namespace osu.Game.Overlays.BeatmapSet
{ {
private readonly OsuSpriteText value; private readonly OsuSpriteText value;
public LocalisableString TooltipText { get; } public LocalisableString TooltipText { get; set; }
public LocalisableString Value public LocalisableString Value
{ {
@ -104,9 +123,8 @@ namespace osu.Game.Overlays.BeatmapSet
set => this.value.Text = value; set => this.value.Text = value;
} }
public Statistic(BeatmapStatisticsIconType icon, string name) public Statistic(BeatmapStatisticsIconType icon)
{ {
TooltipText = name;
RelativeSizeAxes = Axes.X; RelativeSizeAxes = Axes.X;
Height = 24f; Height = 24f;

View File

@ -11,11 +11,13 @@ using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Sprites;
using osu.Framework.Input.Events; using osu.Framework.Input.Events;
using osu.Framework.Localisation;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Beatmaps.Drawables; 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.Resources.Localisation.Web;
using osu.Game.Rulesets; using osu.Game.Rulesets;
using osuTK; using osuTK;
@ -26,7 +28,8 @@ 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 OsuSpriteText version, starRating; private readonly OsuSpriteText version, starRating, starRatingText;
private readonly FillFlowContainer starRatingContainer;
private readonly Statistic plays, favourites; private readonly Statistic plays, favourites;
public readonly DifficultiesContainer Difficulties; public readonly DifficultiesContainer Difficulties;
@ -68,14 +71,14 @@ namespace osu.Game.Overlays.BeatmapSet
OnLostHover = () => OnLostHover = () =>
{ {
showBeatmap(Beatmap.Value); showBeatmap(Beatmap.Value);
starRating.FadeOut(100); starRatingContainer.FadeOut(100);
}, },
}, },
new FillFlowContainer new FillFlowContainer
{ {
AutoSizeAxes = Axes.Both, AutoSizeAxes = Axes.Both,
Spacing = new Vector2(5f), Spacing = new Vector2(5f),
Children = new[] Children = new Drawable[]
{ {
version = new OsuSpriteText version = new OsuSpriteText
{ {
@ -83,14 +86,31 @@ namespace osu.Game.Overlays.BeatmapSet
Origin = Anchor.BottomLeft, Origin = Anchor.BottomLeft,
Font = OsuFont.GetFont(size: 17, weight: FontWeight.Bold) Font = OsuFont.GetFont(size: 17, weight: FontWeight.Bold)
}, },
starRating = new OsuSpriteText starRatingContainer = new FillFlowContainer
{ {
Anchor = Anchor.BottomLeft, Anchor = Anchor.BottomLeft,
Origin = Anchor.BottomLeft, Origin = Anchor.BottomLeft,
Font = OsuFont.GetFont(size: 11, weight: FontWeight.Bold),
Text = "Star Difficulty",
Alpha = 0, Alpha = 0,
Direction = FillDirection.Horizontal,
Spacing = new Vector2(2f, 0),
Margin = new MarginPadding { Bottom = 1 }, Margin = new MarginPadding { Bottom = 1 },
Children = new[]
{
starRatingText = new OsuSpriteText
{
Anchor = Anchor.BottomLeft,
Origin = Anchor.BottomLeft,
Font = OsuFont.GetFont(size: 11, weight: FontWeight.Bold),
Text = BeatmapsetsStrings.ShowStatsStars,
},
starRating = new OsuSpriteText
{
Anchor = Anchor.BottomLeft,
Origin = Anchor.BottomLeft,
Font = OsuFont.GetFont(size: 11, weight: FontWeight.Bold),
Text = string.Empty,
},
}
}, },
}, },
}, },
@ -124,6 +144,7 @@ namespace osu.Game.Overlays.BeatmapSet
private void load(OsuColour colours) private void load(OsuColour colours)
{ {
starRating.Colour = colours.Yellow; starRating.Colour = colours.Yellow;
starRatingText.Colour = colours.Yellow;
updateDisplay(); updateDisplay();
} }
@ -149,14 +170,14 @@ namespace osu.Game.Overlays.BeatmapSet
OnHovered = beatmap => OnHovered = beatmap =>
{ {
showBeatmap(beatmap); showBeatmap(beatmap);
starRating.Text = beatmap.StarDifficulty.ToString("Star Difficulty 0.##"); starRating.Text = beatmap.StarDifficulty.ToLocalisableString(@"0.##");
starRating.FadeIn(100); starRatingContainer.FadeIn(100);
}, },
OnClicked = beatmap => { Beatmap.Value = beatmap; }, OnClicked = beatmap => { Beatmap.Value = beatmap; },
}); });
} }
starRating.FadeOut(100); starRatingContainer.FadeOut(100);
Beatmap.Value = Difficulties.FirstOrDefault()?.Beatmap; Beatmap.Value = Difficulties.FirstOrDefault()?.Beatmap;
plays.Value = BeatmapSet?.OnlineInfo.PlayCount ?? 0; plays.Value = BeatmapSet?.OnlineInfo.PlayCount ?? 0;
favourites.Value = BeatmapSet?.OnlineInfo.FavouriteCount ?? 0; favourites.Value = BeatmapSet?.OnlineInfo.FavouriteCount ?? 0;
@ -300,7 +321,7 @@ namespace osu.Game.Overlays.BeatmapSet
set set
{ {
this.value = value; this.value = value;
text.Text = Value.ToString(@"N0"); text.Text = Value.ToLocalisableString(@"N0");
} }
} }

View File

@ -13,6 +13,7 @@ using osu.Game.Graphics.UserInterface;
using osu.Game.Online.API; using osu.Game.Online.API;
using osu.Game.Online.API.Requests; using osu.Game.Online.API.Requests;
using osu.Game.Overlays.Notifications; using osu.Game.Overlays.Notifications;
using osu.Game.Resources.Localisation.Web;
using osu.Game.Users; using osu.Game.Users;
using osuTK; using osuTK;
@ -35,7 +36,7 @@ namespace osu.Game.Overlays.BeatmapSet.Buttons
{ {
if (!Enabled.Value) return string.Empty; if (!Enabled.Value) return string.Empty;
return (favourited.Value ? "Unfavourite" : "Favourite") + " this beatmapset"; return favourited.Value ? BeatmapsetsStrings.ShowDetailsUnfavourite : BeatmapsetsStrings.ShowDetailsFavourite;
} }
} }

View File

@ -15,6 +15,7 @@ using osu.Game.Graphics.Sprites;
using osu.Game.Online; using osu.Game.Online;
using osu.Game.Online.API; using osu.Game.Online.API;
using osu.Game.Overlays.BeatmapListing.Panels; using osu.Game.Overlays.BeatmapListing.Panels;
using osu.Game.Resources.Localisation.Web;
using osu.Game.Users; using osu.Game.Users;
using osuTK; using osuTK;
using osuTK.Graphics; using osuTK.Graphics;
@ -27,7 +28,7 @@ namespace osu.Game.Overlays.BeatmapSet.Buttons
private readonly bool noVideo; private readonly bool noVideo;
public LocalisableString TooltipText => button.Enabled.Value ? "download this beatmap" : "login to download"; public LocalisableString TooltipText => BeatmapsetsStrings.ShowDetailsDownloadDefault;
private readonly IBindable<User> localUser = new Bindable<User>(); private readonly IBindable<User> localUser = new Bindable<User>();
@ -113,7 +114,7 @@ namespace osu.Game.Overlays.BeatmapSet.Buttons
{ {
new OsuSpriteText new OsuSpriteText
{ {
Text = "Downloading...", Text = Localisation.CommonStrings.Downloading,
Font = OsuFont.GetFont(size: text_size, weight: FontWeight.Bold) Font = OsuFont.GetFont(size: text_size, weight: FontWeight.Bold)
}, },
}; };
@ -124,7 +125,7 @@ namespace osu.Game.Overlays.BeatmapSet.Buttons
{ {
new OsuSpriteText new OsuSpriteText
{ {
Text = "Importing...", Text = Localisation.CommonStrings.Importing,
Font = OsuFont.GetFont(size: text_size, weight: FontWeight.Bold) Font = OsuFont.GetFont(size: text_size, weight: FontWeight.Bold)
}, },
}; };
@ -139,7 +140,7 @@ namespace osu.Game.Overlays.BeatmapSet.Buttons
{ {
new OsuSpriteText new OsuSpriteText
{ {
Text = "Download", Text = BeatmapsetsStrings.ShowDetailsDownloadDefault,
Font = OsuFont.GetFont(size: text_size, weight: FontWeight.Bold) Font = OsuFont.GetFont(size: text_size, weight: FontWeight.Bold)
}, },
new OsuSpriteText new OsuSpriteText
@ -158,12 +159,12 @@ namespace osu.Game.Overlays.BeatmapSet.Buttons
private void enabledChanged(ValueChangedEvent<bool> e) => this.FadeColour(e.NewValue ? Color4.White : Color4.Gray, 200, Easing.OutQuint); private void enabledChanged(ValueChangedEvent<bool> e) => this.FadeColour(e.NewValue ? Color4.White : Color4.Gray, 200, Easing.OutQuint);
private string getVideoSuffixText() private LocalisableString getVideoSuffixText()
{ {
if (!BeatmapSet.Value.OnlineInfo.HasVideo) if (!BeatmapSet.Value.OnlineInfo.HasVideo)
return string.Empty; return string.Empty;
return noVideo ? "without Video" : "with Video"; return noVideo ? BeatmapsetsStrings.ShowDetailsDownloadNoVideo : BeatmapsetsStrings.ShowDetailsDownloadVideo;
} }
} }
} }

View File

@ -1,6 +1,7 @@
// 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. // See the LICENCE file in the repository root for full licence text.
using osu.Framework.Extensions;
using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
@ -46,7 +47,7 @@ namespace osu.Game.Overlays.BeatmapSet
AutoSizeAxes = Axes.Y, AutoSizeAxes = Axes.Y,
Child = new OsuSpriteText Child = new OsuSpriteText
{ {
Text = this.type.ToString(), Text = this.type.GetLocalisableDescription(),
Font = OsuFont.GetFont(weight: FontWeight.Bold, size: 14), Font = OsuFont.GetFont(weight: FontWeight.Bold, size: 14),
}, },
}, },

View File

@ -1,14 +1,26 @@
// 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. // See the LICENCE file in the repository root for full licence text.
using osu.Framework.Localisation;
using osu.Game.Resources.Localisation.Web;
namespace osu.Game.Overlays.BeatmapSet namespace osu.Game.Overlays.BeatmapSet
{ {
public enum MetadataType public enum MetadataType
{ {
[LocalisableDescription(typeof(BeatmapsetsStrings), nameof(BeatmapsetsStrings.ShowInfoTags))]
Tags, Tags,
[LocalisableDescription(typeof(BeatmapsetsStrings), nameof(BeatmapsetsStrings.ShowInfoSource))]
Source, Source,
[LocalisableDescription(typeof(BeatmapsetsStrings), nameof(BeatmapsetsStrings.ShowInfoDescription))]
Description, Description,
[LocalisableDescription(typeof(BeatmapsetsStrings), nameof(BeatmapsetsStrings.ShowInfoGenre))]
Genre, Genre,
[LocalisableDescription(typeof(BeatmapsetsStrings), nameof(BeatmapsetsStrings.ShowInfoLanguage))]
Language Language
} }
} }

View File

@ -6,6 +6,7 @@ using osu.Framework.Graphics.Containers;
using osu.Game.Screens.Select.Leaderboards; using osu.Game.Screens.Select.Leaderboards;
using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Sprites;
using osu.Game.Graphics.Sprites; using osu.Game.Graphics.Sprites;
using osu.Game.Resources.Localisation.Web;
namespace osu.Game.Overlays.BeatmapSet.Scores namespace osu.Game.Overlays.BeatmapSet.Scores
{ {
@ -30,15 +31,15 @@ namespace osu.Game.Overlays.BeatmapSet.Scores
switch (scope) switch (scope)
{ {
default: default:
text.Text = @"No scores have been set yet. Maybe you can be the first!"; text.Text = BeatmapsetsStrings.ShowScoreboardNoScoresGlobal;
break; break;
case BeatmapLeaderboardScope.Friend: case BeatmapLeaderboardScope.Friend:
text.Text = @"None of your friends have set a score on this map yet."; text.Text = BeatmapsetsStrings.ShowScoreboardNoScoresFriend;
break; break;
case BeatmapLeaderboardScope.Country: case BeatmapLeaderboardScope.Country:
text.Text = @"No one from your country has set a score on this map yet."; text.Text = BeatmapsetsStrings.ShowScoreboardNoScoresCountry;
break; break;
} }
} }

View File

@ -20,6 +20,7 @@ using osuTK;
using osuTK.Graphics; using osuTK.Graphics;
using osu.Framework.Localisation; using osu.Framework.Localisation;
using osu.Framework.Extensions.LocalisationExtensions; using osu.Framework.Extensions.LocalisationExtensions;
using osu.Game.Resources.Localisation.Web;
namespace osu.Game.Overlays.BeatmapSet.Scores namespace osu.Game.Overlays.BeatmapSet.Scores
{ {
@ -93,13 +94,13 @@ namespace osu.Game.Overlays.BeatmapSet.Scores
{ {
var columns = new List<TableColumn> var columns = new List<TableColumn>
{ {
new TableColumn("rank", Anchor.CentreRight, new Dimension(GridSizeMode.AutoSize)), new TableColumn(BeatmapsetsStrings.ShowScoreboardHeadersRank, Anchor.CentreRight, new Dimension(GridSizeMode.AutoSize)),
new TableColumn("", Anchor.Centre, new Dimension(GridSizeMode.Absolute, 70)), // grade new TableColumn("", Anchor.Centre, new Dimension(GridSizeMode.Absolute, 70)), // grade
new TableColumn("score", Anchor.CentreLeft, new Dimension(GridSizeMode.AutoSize)), new TableColumn(BeatmapsetsStrings.ShowScoreboardHeadersScore, Anchor.CentreLeft, new Dimension(GridSizeMode.AutoSize)),
new TableColumn("accuracy", Anchor.CentreLeft, new Dimension(GridSizeMode.Absolute, minSize: 60, maxSize: 70)), new TableColumn(BeatmapsetsStrings.ShowScoreboardHeadersAccuracy, Anchor.CentreLeft, new Dimension(GridSizeMode.Absolute, minSize: 60, maxSize: 70)),
new TableColumn("", Anchor.CentreLeft, new Dimension(GridSizeMode.Absolute, 25)), // flag new TableColumn("", Anchor.CentreLeft, new Dimension(GridSizeMode.Absolute, 25)), // flag
new TableColumn("player", Anchor.CentreLeft, new Dimension(GridSizeMode.Distributed, minSize: 125)), new TableColumn(BeatmapsetsStrings.ShowScoreboardHeadersPlayer, Anchor.CentreLeft, new Dimension(GridSizeMode.Distributed, minSize: 125)),
new TableColumn("max combo", Anchor.CentreLeft, new Dimension(GridSizeMode.Distributed, minSize: 70, maxSize: 120)) new TableColumn(BeatmapsetsStrings.ShowScoreboardHeadersCombo, Anchor.CentreLeft, new Dimension(GridSizeMode.Distributed, minSize: 70, maxSize: 120))
}; };
// All statistics across all scores, unordered. // All statistics across all scores, unordered.
@ -124,9 +125,9 @@ namespace osu.Game.Overlays.BeatmapSet.Scores
} }
if (showPerformancePoints) if (showPerformancePoints)
columns.Add(new TableColumn("pp", Anchor.CentreLeft, new Dimension(GridSizeMode.Absolute, 30))); columns.Add(new TableColumn(BeatmapsetsStrings.ShowScoreboardHeaderspp, Anchor.CentreLeft, new Dimension(GridSizeMode.Absolute, 30)));
columns.Add(new TableColumn("mods", Anchor.CentreLeft, new Dimension(GridSizeMode.AutoSize))); columns.Add(new TableColumn(BeatmapsetsStrings.ShowScoreboardHeadersMods, Anchor.CentreLeft, new Dimension(GridSizeMode.AutoSize)));
return columns.ToArray(); return columns.ToArray();
} }
@ -140,7 +141,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores
{ {
new OsuSpriteText new OsuSpriteText
{ {
Text = $"#{index + 1}", Text = (index + 1).ToLocalisableString(@"\##"),
Font = OsuFont.GetFont(size: text_size, weight: FontWeight.Bold) Font = OsuFont.GetFont(size: text_size, weight: FontWeight.Bold)
}, },
new UpdateableRank(score.Rank) new UpdateableRank(score.Rank)
@ -168,7 +169,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores
username, username,
new OsuSpriteText new OsuSpriteText
{ {
Text = $@"{score.MaxCombo:N0}x", Text = score.MaxCombo.ToLocalisableString(@"0\x"),
Font = OsuFont.GetFont(size: text_size), Font = OsuFont.GetFont(size: text_size),
Colour = score.MaxCombo == score.Beatmap?.MaxCombo ? highAccuracyColour : Color4.White Colour = score.MaxCombo == score.Beatmap?.MaxCombo ? highAccuracyColour : Color4.White
} }
@ -183,7 +184,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores
content.Add(new OsuSpriteText content.Add(new OsuSpriteText
{ {
Text = stat.MaxCount == null ? $"{stat.Count}" : $"{stat.Count}/{stat.MaxCount}", Text = stat.MaxCount == null ? stat.Count.ToLocalisableString(@"N0") : (LocalisableString)$"{stat.Count}/{stat.MaxCount}",
Font = OsuFont.GetFont(size: text_size), Font = OsuFont.GetFont(size: text_size),
Colour = stat.Count == 0 ? Color4.Gray : Color4.White Colour = stat.Count == 0 ? Color4.Gray : Color4.White
}); });
@ -193,7 +194,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores
{ {
content.Add(new OsuSpriteText content.Add(new OsuSpriteText
{ {
Text = $@"{score.PP:N0}", Text = score.PP.ToLocalisableString(@"N0"),
Font = OsuFont.GetFont(size: text_size) Font = OsuFont.GetFont(size: text_size)
}); });
} }

View File

@ -5,6 +5,7 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Extensions.LocalisationExtensions;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Shapes;
@ -13,6 +14,7 @@ using osu.Framework.Localisation;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Graphics.Sprites; using osu.Game.Graphics.Sprites;
using osu.Game.Resources.Localisation.Web;
using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI;
using osu.Game.Scoring; using osu.Game.Scoring;
@ -61,9 +63,9 @@ namespace osu.Game.Overlays.BeatmapSet.Scores
Spacing = new Vector2(margin, 0), Spacing = new Vector2(margin, 0),
Children = new Drawable[] Children = new Drawable[]
{ {
totalScoreColumn = new TextColumn("total score", largeFont, top_columns_min_width), totalScoreColumn = new TextColumn(BeatmapsetsStrings.ShowScoreboardHeadersScoreTotal, largeFont, top_columns_min_width),
accuracyColumn = new TextColumn("accuracy", largeFont, top_columns_min_width), accuracyColumn = new TextColumn(BeatmapsetsStrings.ShowScoreboardHeadersAccuracy, largeFont, top_columns_min_width),
maxComboColumn = new TextColumn("max combo", largeFont, top_columns_min_width) maxComboColumn = new TextColumn(BeatmapsetsStrings.ShowScoreboardHeadersCombo, largeFont, top_columns_min_width)
} }
}, },
new FillFlowContainer new FillFlowContainer
@ -81,7 +83,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores
Direction = FillDirection.Horizontal, Direction = FillDirection.Horizontal,
Spacing = new Vector2(margin, 0), Spacing = new Vector2(margin, 0),
}, },
ppColumn = new TextColumn("pp", smallFont, bottom_columns_min_width), ppColumn = new TextColumn(BeatmapsetsStrings.ShowScoreboardHeaderspp, smallFont, bottom_columns_min_width),
modsColumn = new ModsInfoColumn(), modsColumn = new ModsInfoColumn(),
} }
}, },
@ -111,10 +113,10 @@ namespace osu.Game.Overlays.BeatmapSet.Scores
score = value; score = value;
accuracyColumn.Text = value.DisplayAccuracy; accuracyColumn.Text = value.DisplayAccuracy;
maxComboColumn.Text = $@"{value.MaxCombo:N0}x"; maxComboColumn.Text = value.MaxCombo.ToLocalisableString(@"0\x");
ppColumn.Alpha = value.Beatmap?.Status.GrantsPerformancePoints() == true ? 1 : 0; ppColumn.Alpha = value.Beatmap?.Status.GrantsPerformancePoints() == true ? 1 : 0;
ppColumn.Text = $@"{value.PP:N0}"; ppColumn.Text = value.PP.ToLocalisableString(@"N0");
statisticsColumns.ChildrenEnumerable = value.GetStatisticsForDisplay().Select(createStatisticsColumn); statisticsColumns.ChildrenEnumerable = value.GetStatisticsForDisplay().Select(createStatisticsColumn);
modsColumn.Mods = value.Mods; modsColumn.Mods = value.Mods;
@ -126,7 +128,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores
private TextColumn createStatisticsColumn(HitResultDisplayStatistic stat) => new TextColumn(stat.DisplayName, smallFont, bottom_columns_min_width) private TextColumn createStatisticsColumn(HitResultDisplayStatistic stat) => new TextColumn(stat.DisplayName, smallFont, bottom_columns_min_width)
{ {
Text = stat.MaxCount == null ? $"{stat.Count}" : $"{stat.Count}/{stat.MaxCount}" Text = stat.MaxCount == null ? stat.Count.ToLocalisableString(@"N0") : (LocalisableString)$"{stat.Count}/{stat.MaxCount}"
}; };
private class InfoColumn : CompositeDrawable private class InfoColumn : CompositeDrawable
@ -134,7 +136,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores
private readonly Box separator; private readonly Box separator;
private readonly OsuSpriteText text; private readonly OsuSpriteText text;
public InfoColumn(string title, Drawable content, float? minWidth = null) public InfoColumn(LocalisableString title, Drawable content, float? minWidth = null)
{ {
AutoSizeAxes = Axes.Both; AutoSizeAxes = Axes.Both;
Margin = new MarginPadding { Vertical = 5 }; Margin = new MarginPadding { Vertical = 5 };
@ -194,12 +196,12 @@ namespace osu.Game.Overlays.BeatmapSet.Scores
{ {
private readonly SpriteText text; private readonly SpriteText text;
public TextColumn(string title, FontUsage font, float? minWidth = null) public TextColumn(LocalisableString title, FontUsage font, float? minWidth = null)
: this(title, new OsuSpriteText { Font = font }, minWidth) : this(title, new OsuSpriteText { Font = font }, minWidth)
{ {
} }
private TextColumn(string title, SpriteText text, float? minWidth = null) private TextColumn(LocalisableString title, SpriteText text, float? minWidth = null)
: base(title, text, minWidth) : base(title, text, minWidth)
{ {
this.text = text; this.text = text;
@ -233,7 +235,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores
} }
private ModsInfoColumn(FillFlowContainer modsContainer) private ModsInfoColumn(FillFlowContainer modsContainer)
: base("mods", modsContainer) : base(BeatmapsetsStrings.ShowScoreboardHeadersMods, modsContainer)
{ {
this.modsContainer = modsContainer; this.modsContainer = modsContainer;
} }

View File

@ -7,6 +7,7 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Effects;
using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation;
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;
@ -126,7 +127,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores
public int? ScorePosition public int? ScorePosition
{ {
set => rankText.Text = value == null ? "-" : $"#{value}"; set => rankText.Text = value == null ? (LocalisableString)"-" : value.ToLocalisableString(@"\##");
} }
/// <summary> /// <summary>

View File

@ -4,10 +4,12 @@
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Localisation;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Graphics.Sprites; using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterface;
using osu.Game.Resources.Localisation.Web;
using osu.Game.Screens.Select.Details; using osu.Game.Screens.Select.Details;
namespace osu.Game.Overlays.BeatmapSet namespace osu.Game.Overlays.BeatmapSet
@ -42,7 +44,7 @@ namespace osu.Game.Overlays.BeatmapSet
int playCount = beatmap?.OnlineInfo?.PlayCount ?? 0; int playCount = beatmap?.OnlineInfo?.PlayCount ?? 0;
var rate = playCount != 0 ? (float)passCount / playCount : 0; var rate = playCount != 0 ? (float)passCount / playCount : 0;
successPercent.Text = rate.ToString("0.#%"); successPercent.Text = rate.ToLocalisableString(@"0.#%");
successRate.Length = rate; successRate.Length = rate;
percentContainer.ResizeWidthTo(successRate.Length, 250, Easing.InOutCubic); percentContainer.ResizeWidthTo(successRate.Length, 250, Easing.InOutCubic);
@ -64,7 +66,7 @@ namespace osu.Game.Overlays.BeatmapSet
{ {
Anchor = Anchor.TopCentre, Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre, Origin = Anchor.TopCentre,
Text = "Success Rate", Text = BeatmapsetsStrings.ShowInfoSuccessRate,
Font = OsuFont.GetFont(size: 12) Font = OsuFont.GetFont(size: 12)
}, },
successRate = new Bar successRate = new Bar
@ -89,7 +91,7 @@ namespace osu.Game.Overlays.BeatmapSet
{ {
Anchor = Anchor.TopCentre, Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre, Origin = Anchor.TopCentre,
Text = "Points of Failure", Text = BeatmapsetsStrings.ShowInfoPointsOfFailure,
Font = OsuFont.GetFont(size: 12), Font = OsuFont.GetFont(size: 12),
Margin = new MarginPadding { Vertical = 20 }, Margin = new MarginPadding { Vertical = 20 },
}, },

View File

@ -23,7 +23,10 @@ namespace osu.Game.Overlays
private IAPIProvider api { get; set; } private IAPIProvider api { get; set; }
[Resolved] [Resolved]
private Bindable<RulesetInfo> ruleset { get; set; } private IBindable<RulesetInfo> parentRuleset { get; set; }
[Cached]
private readonly Bindable<RulesetInfo> ruleset = new Bindable<RulesetInfo>();
public RankingsOverlay() public RankingsOverlay()
: base(OverlayColourScheme.Green) : base(OverlayColourScheme.Green)
@ -54,6 +57,19 @@ namespace osu.Game.Overlays
}); });
} }
private bool requiresRulesetUpdate = true;
protected override void PopIn()
{
if (requiresRulesetUpdate)
{
ruleset.Value = parentRuleset.Value;
requiresRulesetUpdate = false;
}
base.PopIn();
}
protected override void OnTabChanged(RankingsScope tab) protected override void OnTabChanged(RankingsScope tab)
{ {
// country filtering is only valid for performance scope. // country filtering is only valid for performance scope.

View File

@ -211,7 +211,7 @@ namespace osu.Game.Overlays
loading.Hide(); loading.Hide();
searchTextBox.Current.BindValueChanged(term => SectionsContainer.SearchContainer.SearchTerm = term.NewValue, true); searchTextBox.Current.BindValueChanged(term => SectionsContainer.SearchTerm = term.NewValue, true);
searchTextBox.TakeFocus(); searchTextBox.TakeFocus();
loadSidebarButtons(); loadSidebarButtons();

View File

@ -30,7 +30,12 @@ namespace osu.Game.Rulesets
// On android in release configuration assemblies are loaded from the apk directly into memory. // On android in release configuration assemblies are loaded from the apk directly into memory.
// We cannot read assemblies from cwd, so should check loaded assemblies instead. // We cannot read assemblies from cwd, so should check loaded assemblies instead.
loadFromAppDomain(); loadFromAppDomain();
loadFromDisk();
// This null check prevents Android from attempting to load the rulesets from disk,
// as the underlying path "AppContext.BaseDirectory", despite being non-nullable, it returns null on android.
// See https://github.com/xamarin/xamarin-android/issues/3489.
if (RuntimeInfo.StartupDirectory != null)
loadFromDisk();
// the event handler contains code for resolving dependency on the game assembly for rulesets located outside the base game directory. // the event handler contains code for resolving dependency on the game assembly for rulesets located outside the base game directory.
// It needs to be attached to the assembly lookup event before the actual call to loadUserRulesets() else rulesets located out of the base game directory will fail // It needs to be attached to the assembly lookup event before the actual call to loadUserRulesets() else rulesets located out of the base game directory will fail

View File

@ -11,6 +11,9 @@ namespace osu.Game.Screens
{ {
public abstract class BackgroundScreen : Screen, IEquatable<BackgroundScreen> public abstract class BackgroundScreen : Screen, IEquatable<BackgroundScreen>
{ {
protected const float TRANSITION_LENGTH = 500;
private const float x_movement_amount = 50;
private readonly bool animateOnEnter; private readonly bool animateOnEnter;
public override bool IsPresent => base.IsPresent || Scheduler.HasPendingTasks; public override bool IsPresent => base.IsPresent || Scheduler.HasPendingTasks;
@ -27,9 +30,6 @@ namespace osu.Game.Screens
return other?.GetType() == GetType(); return other?.GetType() == GetType();
} }
private const float transition_length = 500;
private const float x_movement_amount = 50;
protected override bool OnKeyDown(KeyDownEvent e) protected override bool OnKeyDown(KeyDownEvent e)
{ {
// we don't want to handle escape key. // we don't want to handle escape key.
@ -55,8 +55,8 @@ namespace osu.Game.Screens
this.FadeOut(); this.FadeOut();
this.MoveToX(x_movement_amount); this.MoveToX(x_movement_amount);
this.FadeIn(transition_length, Easing.InOutQuart); this.FadeIn(TRANSITION_LENGTH, Easing.InOutQuart);
this.MoveToX(0, transition_length, Easing.InOutQuart); this.MoveToX(0, TRANSITION_LENGTH, Easing.InOutQuart);
} }
base.OnEntering(last); base.OnEntering(last);
@ -64,7 +64,7 @@ namespace osu.Game.Screens
public override void OnSuspending(IScreen next) public override void OnSuspending(IScreen next)
{ {
this.MoveToX(-x_movement_amount, transition_length, Easing.InOutQuart); this.MoveToX(-x_movement_amount, TRANSITION_LENGTH, Easing.InOutQuart);
base.OnSuspending(next); base.OnSuspending(next);
} }
@ -72,8 +72,8 @@ namespace osu.Game.Screens
{ {
if (IsLoaded) if (IsLoaded)
{ {
this.FadeOut(transition_length, Easing.OutExpo); this.FadeOut(TRANSITION_LENGTH, Easing.OutExpo);
this.MoveToX(x_movement_amount, transition_length, Easing.OutExpo); this.MoveToX(x_movement_amount, TRANSITION_LENGTH, Easing.OutExpo);
} }
return base.OnExiting(next); return base.OnExiting(next);
@ -82,7 +82,7 @@ namespace osu.Game.Screens
public override void OnResuming(IScreen last) public override void OnResuming(IScreen last)
{ {
if (IsLoaded) if (IsLoaded)
this.MoveToX(0, transition_length, Easing.OutExpo); this.MoveToX(0, TRANSITION_LENGTH, Easing.OutExpo);
base.OnResuming(last); base.OnResuming(last);
} }
} }

View File

@ -4,7 +4,6 @@
using System.Collections.Generic; using System.Collections.Generic;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Screens; using osu.Framework.Screens;
using osuTK;
namespace osu.Game.Screens namespace osu.Game.Screens
{ {
@ -13,14 +12,11 @@ namespace osu.Game.Screens
public BackgroundScreenStack() public BackgroundScreenStack()
: base(false) : base(false)
{ {
Scale = new Vector2(1.06f);
RelativeSizeAxes = Axes.Both; RelativeSizeAxes = Axes.Both;
Anchor = Anchor.Centre; Anchor = Anchor.Centre;
Origin = Anchor.Centre; Origin = Anchor.Centre;
} }
//public float ParallaxAmount { set => parallax.ParallaxAmount = ParallaxContainer.DEFAULT_PARALLAX_AMOUNT * value; }
public void Push(BackgroundScreen screen) public void Push(BackgroundScreen screen)
{ {
if (screen == null) if (screen == null)

View File

@ -0,0 +1,60 @@
// 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.Localisation;
using osu.Game.Graphics.UserInterfaceV2;
namespace osu.Game.Screens.Edit.Setup
{
internal class DesignSection : SetupSection
{
private LabelledSwitchButton widescreenSupport;
private LabelledSwitchButton epilepsyWarning;
private LabelledSwitchButton letterboxDuringBreaks;
public override LocalisableString Title => "Design";
[BackgroundDependencyLoader]
private void load()
{
Children = new[]
{
widescreenSupport = new LabelledSwitchButton
{
Label = "Widescreen support",
Description = "Allows storyboards to use the full screen space, rather than be confined to a 4:3 area.",
Current = { Value = Beatmap.BeatmapInfo.WidescreenStoryboard }
},
epilepsyWarning = new LabelledSwitchButton
{
Label = "Epilepsy warning",
Description = "Recommended if the storyboard or video contain scenes with rapidly flashing colours.",
Current = { Value = Beatmap.BeatmapInfo.EpilepsyWarning }
},
letterboxDuringBreaks = new LabelledSwitchButton
{
Label = "Letterbox during breaks",
Description = "Adds horizontal letterboxing to give a cinematic look during breaks.",
Current = { Value = Beatmap.BeatmapInfo.LetterboxInBreaks }
}
};
}
protected override void LoadComplete()
{
base.LoadComplete();
widescreenSupport.Current.BindValueChanged(_ => updateBeatmap());
epilepsyWarning.Current.BindValueChanged(_ => updateBeatmap());
letterboxDuringBreaks.Current.BindValueChanged(_ => updateBeatmap());
}
private void updateBeatmap()
{
Beatmap.BeatmapInfo.WidescreenStoryboard = widescreenSupport.Current.Value;
Beatmap.BeatmapInfo.EpilepsyWarning = epilepsyWarning.Current.Value;
Beatmap.BeatmapInfo.LetterboxInBreaks = letterboxDuringBreaks.Current.Value;
}
}
}

View File

@ -8,28 +8,28 @@ using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Extensions;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Cursor;
using osu.Framework.Graphics.UserInterface;
using osu.Framework.Input.Events; using osu.Framework.Input.Events;
using osu.Game.Database; using osu.Game.Database;
using osu.Game.Graphics.Containers; using osu.Game.Graphics.Containers;
using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterface;
using osu.Game.Graphics.UserInterfaceV2; using osu.Game.Graphics.UserInterfaceV2;
using osuTK;
namespace osu.Game.Screens.Edit.Setup namespace osu.Game.Screens.Edit.Setup
{ {
/// <summary> /// <summary>
/// A labelled textbox which reveals an inline file chooser when clicked. /// A labelled textbox which reveals an inline file chooser when clicked.
/// </summary> /// </summary>
internal class FileChooserLabelledTextBox : LabelledTextBox, ICanAcceptFiles internal class FileChooserLabelledTextBox : LabelledTextBox, ICanAcceptFiles, IHasPopover
{ {
private readonly string[] handledExtensions; private readonly string[] handledExtensions;
public IEnumerable<string> HandledExtensions => handledExtensions;
/// <summary> public IEnumerable<string> HandledExtensions => handledExtensions;
/// The target container to display the file chooser in.
/// </summary>
public Container Target;
private readonly Bindable<FileInfo> currentFile = new Bindable<FileInfo>(); private readonly Bindable<FileInfo> currentFile = new Bindable<FileInfo>();
@ -51,23 +51,9 @@ namespace osu.Game.Screens.Edit.Setup
Origin = Anchor.Centre, Origin = Anchor.Centre,
RelativeSizeAxes = Axes.X, RelativeSizeAxes = Axes.X,
CornerRadius = CORNER_RADIUS, CornerRadius = CORNER_RADIUS,
OnFocused = DisplayFileChooser OnFocused = this.ShowPopover
}; };
public void DisplayFileChooser()
{
OsuFileSelector fileSelector;
Target.Child = fileSelector = new OsuFileSelector(currentFile.Value?.DirectoryName, handledExtensions)
{
RelativeSizeAxes = Axes.X,
Height = 400,
CurrentFile = { BindTarget = currentFile }
};
sectionsContainer.ScrollTo(fileSelector);
}
protected override void LoadComplete() protected override void LoadComplete()
{ {
base.LoadComplete(); base.LoadComplete();
@ -81,7 +67,7 @@ namespace osu.Game.Screens.Edit.Setup
if (file.NewValue == null) if (file.NewValue == null)
return; return;
Target.Clear(); this.HidePopover();
Current.Value = file.NewValue.FullName; Current.Value = file.NewValue.FullName;
} }
@ -111,5 +97,23 @@ namespace osu.Game.Screens.Edit.Setup
GetContainingInputManager().TriggerFocusContention(this); GetContainingInputManager().TriggerFocusContention(this);
} }
} }
public Popover GetPopover() => new FileChooserPopover(handledExtensions, currentFile);
private class FileChooserPopover : OsuPopover
{
public FileChooserPopover(string[] handledExtensions, Bindable<FileInfo> currentFile)
{
Child = new Container
{
Size = new Vector2(600, 400),
Child = new OsuFileSelector(currentFile.Value?.DirectoryName, handledExtensions)
{
RelativeSizeAxes = Axes.Both,
CurrentFile = { BindTarget = currentFile }
},
};
}
}
} }
} }

View File

@ -6,7 +6,6 @@ using System.Linq;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Localisation; using osu.Framework.Localisation;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Graphics.UserInterfaceV2; using osu.Game.Graphics.UserInterfaceV2;
@ -39,62 +38,30 @@ namespace osu.Game.Screens.Edit.Setup
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load() private void load()
{ {
Container audioTrackFileChooserContainer = createFileChooserContainer();
Container backgroundFileChooserContainer = createFileChooserContainer();
Children = new Drawable[] Children = new Drawable[]
{ {
new FillFlowContainer backgroundTextBox = new FileChooserLabelledTextBox(".jpg", ".jpeg", ".png")
{ {
RelativeSizeAxes = Axes.X, Label = "Background",
AutoSizeAxes = Axes.Y, FixedLabelWidth = LABEL_WIDTH,
Direction = FillDirection.Vertical, PlaceholderText = "Click to select a background image",
Children = new Drawable[] Current = { Value = working.Value.Metadata.BackgroundFile },
{ TabbableContentContainer = this
backgroundTextBox = new FileChooserLabelledTextBox(".jpg", ".jpeg", ".png")
{
Label = "Background",
FixedLabelWidth = LABEL_WIDTH,
PlaceholderText = "Click to select a background image",
Current = { Value = working.Value.Metadata.BackgroundFile },
Target = backgroundFileChooserContainer,
TabbableContentContainer = this
},
backgroundFileChooserContainer,
}
}, },
new FillFlowContainer audioTrackTextBox = new FileChooserLabelledTextBox(".mp3", ".ogg")
{ {
RelativeSizeAxes = Axes.X, Label = "Audio Track",
AutoSizeAxes = Axes.Y, FixedLabelWidth = LABEL_WIDTH,
Direction = FillDirection.Vertical, PlaceholderText = "Click to select a track",
Children = new Drawable[] Current = { Value = working.Value.Metadata.AudioFile },
{ TabbableContentContainer = this
audioTrackTextBox = new FileChooserLabelledTextBox(".mp3", ".ogg") },
{
Label = "Audio Track",
FixedLabelWidth = LABEL_WIDTH,
PlaceholderText = "Click to select a track",
Current = { Value = working.Value.Metadata.AudioFile },
Target = audioTrackFileChooserContainer,
TabbableContentContainer = this
},
audioTrackFileChooserContainer,
}
}
}; };
backgroundTextBox.Current.BindValueChanged(backgroundChanged); backgroundTextBox.Current.BindValueChanged(backgroundChanged);
audioTrackTextBox.Current.BindValueChanged(audioTrackChanged); audioTrackTextBox.Current.BindValueChanged(audioTrackChanged);
} }
private static Container createFileChooserContainer() =>
new Container
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
};
public bool ChangeBackgroundImage(string path) public bool ChangeBackgroundImage(string path)
{ {
var info = new FileInfo(path); var info = new FileInfo(path);

View File

@ -34,7 +34,8 @@ namespace osu.Game.Screens.Edit.Setup
new ResourcesSection(), new ResourcesSection(),
new MetadataSection(), new MetadataSection(),
new DifficultySection(), new DifficultySection(),
new ColoursSection() new ColoursSection(),
new DesignSection(),
} }
}, },
}); });

View File

@ -20,9 +20,6 @@ namespace osu.Game.Screens.OnlinePlay.Components
public readonly Bindable<FilterCriteria> Filter = new Bindable<FilterCriteria>(); public readonly Bindable<FilterCriteria> Filter = new Bindable<FilterCriteria>();
[Resolved]
private Bindable<Room> selectedRoom { get; set; }
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load() private void load()
{ {

View File

@ -0,0 +1,103 @@
// 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.Threading;
using osu.Framework.Allocation;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Colour;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Screens;
using osu.Game.Online.Rooms;
using osuTK;
using osuTK.Graphics;
#nullable enable
namespace osu.Game.Screens.OnlinePlay.Components
{
public abstract class OnlinePlayBackgroundScreen : BackgroundScreen
{
private CancellationTokenSource? cancellationSource;
private PlaylistItemBackground? background;
protected OnlinePlayBackgroundScreen()
: base(false)
{
AddInternal(new Box
{
RelativeSizeAxes = Axes.Both,
Depth = float.MinValue,
Colour = ColourInfo.GradientVertical(Color4.Black.Opacity(0.9f), Color4.Black.Opacity(0.6f))
});
}
[BackgroundDependencyLoader]
private void load()
{
switchBackground(new PlaylistItemBackground(playlistItem));
}
private PlaylistItem? playlistItem;
protected PlaylistItem? PlaylistItem
{
get => playlistItem;
set
{
if (playlistItem == value)
return;
playlistItem = value;
if (LoadState > LoadState.Ready)
updateBackground();
}
}
private void updateBackground()
{
Schedule(() =>
{
var beatmap = playlistItem?.Beatmap.Value;
if (background?.BeatmapInfo?.BeatmapSet?.OnlineInfo?.Covers?.Cover == beatmap?.BeatmapSet?.OnlineInfo?.Covers?.Cover)
return;
cancellationSource?.Cancel();
LoadComponentAsync(new PlaylistItemBackground(playlistItem), switchBackground, (cancellationSource = new CancellationTokenSource()).Token);
});
}
private void switchBackground(PlaylistItemBackground newBackground)
{
float newDepth = 0;
if (background != null)
{
newDepth = background.Depth + 1;
background.FinishTransforms();
background.FadeOut(250);
background.Expire();
}
newBackground.Depth = newDepth;
newBackground.BlurTo(new Vector2(10));
AddInternal(background = newBackground);
}
public override void OnSuspending(IScreen next)
{
base.OnSuspending(next);
this.MoveToX(0, TRANSITION_LENGTH);
}
public override bool OnExiting(IScreen next)
{
var result = base.OnExiting(next);
this.MoveToX(0);
return result;
}
}
}

View File

@ -0,0 +1,44 @@
// 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.
#nullable enable
using osu.Framework.Allocation;
using osu.Framework.Graphics.Textures;
using osu.Game.Beatmaps;
using osu.Game.Graphics.Backgrounds;
using osu.Game.Online.Rooms;
namespace osu.Game.Screens.OnlinePlay.Components
{
public class PlaylistItemBackground : Background
{
public readonly BeatmapInfo? BeatmapInfo;
public PlaylistItemBackground(PlaylistItem? playlistItem)
{
BeatmapInfo = playlistItem?.Beatmap.Value;
}
[BackgroundDependencyLoader]
private void load(BeatmapManager beatmaps, LargeTextureStore textures)
{
Texture? texture = null;
// prefer online cover where available.
if (BeatmapInfo?.BeatmapSet?.OnlineInfo?.Covers.Cover != null)
texture = textures.Get(BeatmapInfo.BeatmapSet.OnlineInfo.Covers.Cover);
Sprite.Texture = texture ?? beatmaps.DefaultBeatmap.Background;
}
public override bool Equals(Background? other)
{
if (ReferenceEquals(null, other)) return false;
if (ReferenceEquals(this, other)) return true;
return other.GetType() == GetType()
&& ((PlaylistItemBackground)other).BeatmapInfo == BeatmapInfo;
}
}
}

View File

@ -3,7 +3,6 @@
using System.Threading.Tasks; using System.Threading.Tasks;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Game.Online.Rooms; using osu.Game.Online.Rooms;
namespace osu.Game.Screens.OnlinePlay.Components namespace osu.Game.Screens.OnlinePlay.Components
@ -13,20 +12,14 @@ namespace osu.Game.Screens.OnlinePlay.Components
/// </summary> /// </summary>
public class SelectionPollingComponent : RoomPollingComponent public class SelectionPollingComponent : RoomPollingComponent
{ {
[Resolved]
private Bindable<Room> selectedRoom { get; set; }
[Resolved] [Resolved]
private IRoomManager roomManager { get; set; } private IRoomManager roomManager { get; set; }
[BackgroundDependencyLoader] private readonly Room room;
private void load()
public SelectionPollingComponent(Room room)
{ {
selectedRoom.BindValueChanged(_ => this.room = room;
{
if (IsLoaded)
PollImmediately();
});
} }
private GetRoomRequest pollReq; private GetRoomRequest pollReq;
@ -36,13 +29,13 @@ namespace osu.Game.Screens.OnlinePlay.Components
if (!API.IsLoggedIn) if (!API.IsLoggedIn)
return base.Poll(); return base.Poll();
if (selectedRoom.Value?.RoomID.Value == null) if (room.RoomID.Value == null)
return base.Poll(); return base.Poll();
var tcs = new TaskCompletionSource<bool>(); var tcs = new TaskCompletionSource<bool>();
pollReq?.Cancel(); pollReq?.Cancel();
pollReq = new GetRoomRequest(selectedRoom.Value.RoomID.Value.Value); pollReq = new GetRoomRequest(room.RoomID.Value.Value);
pollReq.Success += result => pollReq.Success += result =>
{ {

View File

@ -23,16 +23,13 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
{ {
public class RoomsContainer : CompositeDrawable, IKeyBindingHandler<GlobalAction> public class RoomsContainer : CompositeDrawable, IKeyBindingHandler<GlobalAction>
{ {
private readonly IBindableList<Room> rooms = new BindableList<Room>(); public readonly Bindable<Room> SelectedRoom = new Bindable<Room>();
public readonly Bindable<FilterCriteria> Filter = new Bindable<FilterCriteria>();
private readonly FillFlowContainer<DrawableLoungeRoom> roomFlow;
public IReadOnlyList<DrawableRoom> Rooms => roomFlow.FlowingChildren.Cast<DrawableRoom>().ToArray(); public IReadOnlyList<DrawableRoom> Rooms => roomFlow.FlowingChildren.Cast<DrawableRoom>().ToArray();
public readonly Bindable<FilterCriteria> Filter = new Bindable<FilterCriteria>(); private readonly IBindableList<Room> rooms = new BindableList<Room>();
private readonly FillFlowContainer<DrawableLoungeRoom> roomFlow;
[Resolved]
private Bindable<Room> selectedRoom { get; set; }
[Resolved] [Resolved]
private IRoomManager roomManager { get; set; } private IRoomManager roomManager { get; set; }
@ -112,9 +109,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
private void addRooms(IEnumerable<Room> rooms) private void addRooms(IEnumerable<Room> rooms)
{ {
foreach (var room in rooms) foreach (var room in rooms)
{ roomFlow.Add(new DrawableLoungeRoom(room) { SelectedRoom = { BindTarget = SelectedRoom } });
roomFlow.Add(new DrawableLoungeRoom(room));
}
applyFilterCriteria(Filter?.Value); applyFilterCriteria(Filter?.Value);
} }
@ -126,8 +121,8 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
roomFlow.RemoveAll(d => d.Room == r); roomFlow.RemoveAll(d => d.Room == r);
// selection may have a lease due to being in a sub screen. // selection may have a lease due to being in a sub screen.
if (!selectedRoom.Disabled) if (!SelectedRoom.Disabled)
selectedRoom.Value = null; SelectedRoom.Value = null;
} }
} }
@ -139,8 +134,8 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
protected override bool OnClick(ClickEvent e) protected override bool OnClick(ClickEvent e)
{ {
if (!selectedRoom.Disabled) if (!SelectedRoom.Disabled)
selectedRoom.Value = null; SelectedRoom.Value = null;
return base.OnClick(e); return base.OnClick(e);
} }
@ -202,26 +197,26 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
private void selectNext(int direction) private void selectNext(int direction)
{ {
if (selectedRoom.Disabled) if (SelectedRoom.Disabled)
return; return;
var visibleRooms = Rooms.AsEnumerable().Where(r => r.IsPresent); var visibleRooms = Rooms.AsEnumerable().Where(r => r.IsPresent);
Room room; Room room;
if (selectedRoom.Value == null) if (SelectedRoom.Value == null)
room = visibleRooms.FirstOrDefault()?.Room; room = visibleRooms.FirstOrDefault()?.Room;
else else
{ {
if (direction < 0) if (direction < 0)
visibleRooms = visibleRooms.Reverse(); visibleRooms = visibleRooms.Reverse();
room = visibleRooms.SkipWhile(r => r.Room != selectedRoom.Value).Skip(1).FirstOrDefault()?.Room; room = visibleRooms.SkipWhile(r => r.Room != SelectedRoom.Value).Skip(1).FirstOrDefault()?.Room;
} }
// we already have a valid selection only change selection if we still have a room to switch to. // we already have a valid selection only change selection if we still have a room to switch to.
if (room != null) if (room != null)
selectedRoom.Value = room; SelectedRoom.Value = room;
} }
#endregion #endregion

View File

@ -34,11 +34,10 @@ namespace osu.Game.Screens.OnlinePlay.Lounge
private const float transition_duration = 60; private const float transition_duration = 60;
private const float selection_border_width = 4; private const float selection_border_width = 4;
[Resolved(canBeNull: true)] public readonly Bindable<Room> SelectedRoom = new Bindable<Room>();
private LoungeSubScreen lounge { get; set; }
[Resolved(canBeNull: true)] [Resolved(canBeNull: true)]
private Bindable<Room> selectedRoom { get; set; } private LoungeSubScreen lounge { get; set; }
private Sample sampleSelect; private Sample sampleSelect;
private Sample sampleJoin; private Sample sampleJoin;
@ -89,7 +88,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge
else else
Alpha = 0; Alpha = 0;
selectedRoom.BindValueChanged(updateSelectedRoom, true); SelectedRoom.BindValueChanged(updateSelectedRoom, true);
} }
private void updateSelectedRoom(ValueChangedEvent<Room> selected) private void updateSelectedRoom(ValueChangedEvent<Room> selected)
@ -135,7 +134,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge
public bool OnPressed(GlobalAction action) public bool OnPressed(GlobalAction action)
{ {
if (selectedRoom.Value != Room) if (SelectedRoom.Value != Room)
return false; return false;
switch (action) switch (action)
@ -152,14 +151,14 @@ namespace osu.Game.Screens.OnlinePlay.Lounge
{ {
} }
protected override bool ShouldBeConsideredForInput(Drawable child) => selectedRoom.Value == Room || child is HoverSounds; protected override bool ShouldBeConsideredForInput(Drawable child) => SelectedRoom.Value == Room || child is HoverSounds;
protected override bool OnClick(ClickEvent e) protected override bool OnClick(ClickEvent e)
{ {
if (Room != selectedRoom.Value) if (Room != SelectedRoom.Value)
{ {
sampleSelect?.Play(); sampleSelect?.Play();
selectedRoom.Value = Room; SelectedRoom.Value = Room;
return true; return true;
} }

View File

@ -0,0 +1,43 @@
// 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.
#nullable enable
using System.Linq;
using osu.Framework.Bindables;
using osu.Framework.Screens;
using osu.Game.Online.Rooms;
using osu.Game.Screens.OnlinePlay.Components;
using PlaylistItem = osu.Game.Online.Rooms.PlaylistItem;
namespace osu.Game.Screens.OnlinePlay.Lounge
{
public class LoungeBackgroundScreen : OnlinePlayBackgroundScreen
{
public readonly Bindable<Room> SelectedRoom = new Bindable<Room>();
private readonly BindableList<PlaylistItem> playlist = new BindableList<PlaylistItem>();
public LoungeBackgroundScreen()
{
SelectedRoom.BindValueChanged(onSelectedRoomChanged);
playlist.BindCollectionChanged((_, __) => PlaylistItem = playlist.FirstOrDefault());
}
private void onSelectedRoomChanged(ValueChangedEvent<Room> room)
{
if (room.OldValue != null)
playlist.UnbindFrom(room.OldValue.Playlist);
if (room.NewValue != null)
playlist.BindTo(room.NewValue.Playlist);
else
playlist.Clear();
}
public override bool OnExiting(IScreen next)
{
// This screen never exits.
return true;
}
}
}

View File

@ -36,6 +36,11 @@ namespace osu.Game.Screens.OnlinePlay.Lounge
{ {
public override string Title => "Lounge"; public override string Title => "Lounge";
protected override BackgroundScreen CreateBackground() => new LoungeBackgroundScreen
{
SelectedRoom = { BindTarget = SelectedRoom }
};
protected override UserActivity InitialActivity => new UserActivity.SearchingForLobby(); protected override UserActivity InitialActivity => new UserActivity.SearchingForLobby();
protected Container<OsuButton> Buttons { get; } = new Container<OsuButton> protected Container<OsuButton> Buttons { get; } = new Container<OsuButton>
@ -47,8 +52,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge
protected ListingPollingComponent ListingPollingComponent { get; private set; } protected ListingPollingComponent ListingPollingComponent { get; private set; }
[Resolved] protected readonly Bindable<Room> SelectedRoom = new Bindable<Room>();
private Bindable<Room> selectedRoom { get; set; }
[Resolved] [Resolved]
private MusicController music { get; set; } private MusicController music { get; set; }
@ -101,7 +105,8 @@ namespace osu.Game.Screens.OnlinePlay.Lounge
ScrollbarOverlapsContent = false, ScrollbarOverlapsContent = false,
Child = roomsContainer = new RoomsContainer Child = roomsContainer = new RoomsContainer
{ {
Filter = { BindTarget = filter } Filter = { BindTarget = filter },
SelectedRoom = { BindTarget = SelectedRoom }
} }
}, },
}, },
@ -160,7 +165,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge
}; };
// scroll selected room into view on selection. // scroll selected room into view on selection.
selectedRoom.BindValueChanged(val => SelectedRoom.BindValueChanged(val =>
{ {
var drawable = roomsContainer.Rooms.FirstOrDefault(r => r.Room == val.NewValue); var drawable = roomsContainer.Rooms.FirstOrDefault(r => r.Room == val.NewValue);
if (drawable != null) if (drawable != null)
@ -243,8 +248,8 @@ namespace osu.Game.Screens.OnlinePlay.Lounge
selectionLease.Return(); selectionLease.Return();
selectionLease = null; selectionLease = null;
if (selectedRoom.Value?.RoomID.Value == null) if (SelectedRoom.Value?.RoomID.Value == null)
selectedRoom.Value = new Room(); SelectedRoom.Value = new Room();
music?.EnsurePlayingSomething(); music?.EnsurePlayingSomething();
@ -317,7 +322,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge
protected virtual void OpenNewRoom(Room room) protected virtual void OpenNewRoom(Room room)
{ {
selectionLease = selectedRoom.BeginLease(false); selectionLease = SelectedRoom.BeginLease(false);
Debug.Assert(selectionLease != null); Debug.Assert(selectionLease != null);
selectionLease.Value = room; selectionLease.Value = room;

View File

@ -9,6 +9,7 @@ using osu.Game.Graphics;
using osu.Game.Graphics.Sprites; using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterface;
using osu.Game.Input.Bindings; using osu.Game.Input.Bindings;
using osu.Game.Online.Rooms;
using osuTK; using osuTK;
using osuTK.Graphics; using osuTK.Graphics;
@ -27,8 +28,12 @@ namespace osu.Game.Screens.OnlinePlay.Match.Components
protected abstract bool IsLoading { get; } protected abstract bool IsLoading { get; }
protected RoomSettingsOverlay() private readonly Room room;
protected RoomSettingsOverlay(Room room)
{ {
this.room = room;
RelativeSizeAxes = Axes.Both; RelativeSizeAxes = Axes.Both;
Masking = true; Masking = true;
CornerRadius = 10; CornerRadius = 10;
@ -37,12 +42,12 @@ namespace osu.Game.Screens.OnlinePlay.Match.Components
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load() private void load()
{ {
Add(Settings = CreateSettings()); Add(Settings = CreateSettings(room));
} }
protected abstract void SelectBeatmap(); protected abstract void SelectBeatmap();
protected abstract OnlinePlayComposite CreateSettings(); protected abstract OnlinePlayComposite CreateSettings(Room room);
protected override void PopIn() protected override void PopIn()
{ {

View File

@ -6,6 +6,7 @@ using JetBrains.Annotations;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Game.Beatmaps.Drawables;
using osu.Game.Online.API; using osu.Game.Online.API;
using osu.Game.Online.Rooms; using osu.Game.Online.Rooms;
using osu.Game.Screens.OnlinePlay.Lounge.Components; using osu.Game.Screens.OnlinePlay.Lounge.Components;
@ -17,6 +18,7 @@ namespace osu.Game.Screens.OnlinePlay.Match
{ {
public class DrawableMatchRoom : DrawableRoom public class DrawableMatchRoom : DrawableRoom
{ {
public readonly IBindable<PlaylistItem> SelectedItem = new Bindable<PlaylistItem>();
public Action OnEdit; public Action OnEdit;
[Resolved] [Resolved]
@ -28,6 +30,8 @@ namespace osu.Game.Screens.OnlinePlay.Match
[CanBeNull] [CanBeNull]
private Drawable editButton; private Drawable editButton;
private BackgroundSprite background;
public DrawableMatchRoom(Room room, bool allowEdit = true) public DrawableMatchRoom(Room room, bool allowEdit = true)
: base(room) : base(room)
{ {
@ -57,8 +61,15 @@ namespace osu.Game.Screens.OnlinePlay.Match
if (editButton != null) if (editButton != null)
host.BindValueChanged(h => editButton.Alpha = h.NewValue?.Equals(api.LocalUser.Value) == true ? 1 : 0, true); host.BindValueChanged(h => editButton.Alpha = h.NewValue?.Equals(api.LocalUser.Value) == true ? 1 : 0, true);
SelectedItem.BindValueChanged(item => background.Beatmap.Value = item.NewValue?.Beatmap.Value, true);
} }
protected override Drawable CreateBackground() => new RoomBackgroundSprite(); protected override Drawable CreateBackground() => background = new BackgroundSprite();
private class BackgroundSprite : UpdateableBeatmapBackgroundSprite
{
protected override double LoadDelay => 0;
}
} }
} }

View File

@ -0,0 +1,20 @@
// 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.Game.Online.Rooms;
using osu.Game.Screens.OnlinePlay.Components;
namespace osu.Game.Screens.OnlinePlay.Match
{
public class RoomBackgroundScreen : OnlinePlayBackgroundScreen
{
public readonly Bindable<PlaylistItem> SelectedItem = new Bindable<PlaylistItem>();
public RoomBackgroundScreen(PlaylistItem initialPlaylistItem)
{
PlaylistItem = initialPlaylistItem;
SelectedItem.BindValueChanged(item => PlaylistItem = item.NewValue);
}
}
}

View File

@ -1,33 +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 osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Game.Beatmaps.Drawables;
namespace osu.Game.Screens.OnlinePlay.Match
{
public class RoomBackgroundSprite : RoomSubScreenComposite
{
protected readonly BeatmapSetCoverType BeatmapSetCoverType;
private UpdateableBeatmapBackgroundSprite sprite;
public RoomBackgroundSprite(BeatmapSetCoverType beatmapSetCoverType = BeatmapSetCoverType.Cover)
{
BeatmapSetCoverType = beatmapSetCoverType;
}
[BackgroundDependencyLoader]
private void load()
{
InternalChild = sprite = new UpdateableBeatmapBackgroundSprite(BeatmapSetCoverType) { RelativeSizeAxes = Axes.Both };
}
protected override void LoadComplete()
{
base.LoadComplete();
SelectedItem.BindValueChanged(item => sprite.Beatmap.Value = item.NewValue?.Beatmap.Value, true);
}
}
}

View File

@ -29,6 +29,11 @@ namespace osu.Game.Screens.OnlinePlay.Match
[Cached(typeof(IBindable<PlaylistItem>))] [Cached(typeof(IBindable<PlaylistItem>))]
protected readonly Bindable<PlaylistItem> SelectedItem = new Bindable<PlaylistItem>(); protected readonly Bindable<PlaylistItem> SelectedItem = new Bindable<PlaylistItem>();
protected override BackgroundScreen CreateBackground() => new RoomBackgroundScreen(Room.Playlist.FirstOrDefault())
{
SelectedItem = { BindTarget = SelectedItem }
};
public override bool DisallowExternalBeatmapRulesetChanges => true; public override bool DisallowExternalBeatmapRulesetChanges => true;
/// <summary> /// <summary>
@ -134,7 +139,8 @@ namespace osu.Game.Screens.OnlinePlay.Match
{ {
new DrawableMatchRoom(Room, allowEdit) new DrawableMatchRoom(Room, allowEdit)
{ {
OnEdit = () => settingsOverlay.Show() OnEdit = () => settingsOverlay.Show(),
SelectedItem = { BindTarget = SelectedItem }
} }
}, },
null, null,
@ -184,7 +190,7 @@ namespace osu.Game.Screens.OnlinePlay.Match
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
// Resolves 1px masking errors between the settings overlay and the room panel. // Resolves 1px masking errors between the settings overlay and the room panel.
Padding = new MarginPadding(-1), Padding = new MarginPadding(-1),
Child = settingsOverlay = CreateRoomSettingsOverlay() Child = settingsOverlay = CreateRoomSettingsOverlay(Room)
} }
}, },
}, },
@ -244,6 +250,14 @@ namespace osu.Game.Screens.OnlinePlay.Match
UserMods.BindValueChanged(_ => Scheduler.AddOnce(UpdateMods)); UserMods.BindValueChanged(_ => Scheduler.AddOnce(UpdateMods));
} }
protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent)
{
return new CachedModelDependencyContainer<Room>(base.CreateChildDependencies(parent))
{
Model = { Value = Room }
};
}
public override bool OnBackButton() public override bool OnBackButton()
{ {
if (Room.RoomID.Value == null) if (Room.RoomID.Value == null)
@ -412,7 +426,8 @@ namespace osu.Game.Screens.OnlinePlay.Match
/// <summary> /// <summary>
/// Creates the room settings overlay. /// Creates the room settings overlay.
/// </summary> /// </summary>
protected abstract RoomSettingsOverlay CreateRoomSettingsOverlay(); /// <param name="room">The room to change the settings of.</param>
protected abstract RoomSettingsOverlay CreateRoomSettingsOverlay(Room room);
private class UserModSelectOverlay : LocalPlayerModSelectOverlay private class UserModSelectOverlay : LocalPlayerModSelectOverlay
{ {

View File

@ -37,15 +37,19 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
protected override bool IsLoading => ongoingOperationTracker.InProgress.Value; protected override bool IsLoading => ongoingOperationTracker.InProgress.Value;
public MultiplayerMatchSettingsOverlay(Room room)
: base(room)
{
}
protected override void SelectBeatmap() => settings.SelectBeatmap(); protected override void SelectBeatmap() => settings.SelectBeatmap();
protected override OnlinePlayComposite CreateSettings() protected override OnlinePlayComposite CreateSettings(Room room) => settings = new MatchSettings(room)
=> settings = new MatchSettings {
{ RelativeSizeAxes = Axes.Both,
RelativeSizeAxes = Axes.Both, RelativePositionAxes = Axes.Y,
RelativePositionAxes = Axes.Y, SettingsApplied = Hide
SettingsApplied = Hide };
};
protected class MatchSettings : OnlinePlayComposite protected class MatchSettings : OnlinePlayComposite
{ {
@ -73,9 +77,6 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
[Resolved] [Resolved]
private MultiplayerClient client { get; set; } private MultiplayerClient client { get; set; }
[Resolved]
private Bindable<Room> currentRoom { get; set; }
[Resolved] [Resolved]
private Bindable<WorkingBeatmap> beatmap { get; set; } private Bindable<WorkingBeatmap> beatmap { get; set; }
@ -90,6 +91,13 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
[CanBeNull] [CanBeNull]
private IDisposable applyingSettingsOperation; private IDisposable applyingSettingsOperation;
private readonly Room room;
public MatchSettings(Room room)
{
this.room = room;
}
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(OsuColour colours) private void load(OsuColour colours)
{ {
@ -321,24 +329,24 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
client.ChangeSettings(name: NameField.Text, password: PasswordTextBox.Text, matchType: TypePicker.Current.Value).ContinueWith(t => Schedule(() => client.ChangeSettings(name: NameField.Text, password: PasswordTextBox.Text, matchType: TypePicker.Current.Value).ContinueWith(t => Schedule(() =>
{ {
if (t.IsCompletedSuccessfully) if (t.IsCompletedSuccessfully)
onSuccess(currentRoom.Value); onSuccess(room);
else else
onError(t.Exception?.AsSingular().Message ?? "Error changing settings."); onError(t.Exception?.AsSingular().Message ?? "Error changing settings.");
})); }));
} }
else else
{ {
currentRoom.Value.Name.Value = NameField.Text; room.Name.Value = NameField.Text;
currentRoom.Value.Availability.Value = AvailabilityPicker.Current.Value; room.Availability.Value = AvailabilityPicker.Current.Value;
currentRoom.Value.Type.Value = TypePicker.Current.Value; room.Type.Value = TypePicker.Current.Value;
currentRoom.Value.Password.Value = PasswordTextBox.Current.Value; room.Password.Value = PasswordTextBox.Current.Value;
if (int.TryParse(MaxParticipantsField.Text, out int max)) if (int.TryParse(MaxParticipantsField.Text, out int max))
currentRoom.Value.MaxParticipants.Value = max; room.MaxParticipants.Value = max;
else else
currentRoom.Value.MaxParticipants.Value = null; room.MaxParticipants.Value = null;
manager?.CreateRoom(currentRoom.Value, onSuccess, onError); manager?.CreateRoom(room, onSuccess, onError);
} }
} }

View File

@ -48,9 +48,6 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
[Resolved] [Resolved]
private OngoingOperationTracker ongoingOperationTracker { get; set; } private OngoingOperationTracker ongoingOperationTracker { get; set; }
[Resolved]
private Bindable<Room> currentRoom { get; set; } // Todo: This should not exist.
private readonly IBindable<bool> isConnected = new Bindable<bool>(); private readonly IBindable<bool> isConnected = new Bindable<bool>();
[CanBeNull] [CanBeNull]
@ -81,17 +78,6 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
if (!connected.NewValue) if (!connected.NewValue)
handleRoomLost(); handleRoomLost();
}, true); }, true);
currentRoom.BindValueChanged(room =>
{
if (room.NewValue == null)
{
// the room has gone away.
// this could mean something happened during the join process, or an external connection issue occurred.
// one specific scenario is where the underlying room is created, but the signalr server returns an error during the join process. this triggers a PartRoom operation (see https://github.com/ppy/osu/blob/7654df94f6f37b8382be7dfcb4f674e03bd35427/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerRoomManager.cs#L97)
handleRoomLost();
}
}, true);
} }
protected override Drawable CreateMainContent() => new GridContainer protected override Drawable CreateMainContent() => new GridContainer
@ -212,7 +198,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
OnSpectateClick = onSpectateClick OnSpectateClick = onSpectateClick
}; };
protected override RoomSettingsOverlay CreateRoomSettingsOverlay() => new MultiplayerMatchSettingsOverlay(); protected override RoomSettingsOverlay CreateRoomSettingsOverlay(Room room) => new MultiplayerMatchSettingsOverlay(room);
protected override void UpdateMods() protected override void UpdateMods()
{ {

View File

@ -6,23 +6,15 @@ using osu.Framework.Allocation;
using osu.Framework.Bindables; 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.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Logging; using osu.Framework.Logging;
using osu.Framework.Screens; using osu.Framework.Screens;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.Drawables;
using osu.Game.Graphics.Containers; using osu.Game.Graphics.Containers;
using osu.Game.Online.API; using osu.Game.Online.API;
using osu.Game.Online.Rooms;
using osu.Game.Overlays; using osu.Game.Overlays;
using osu.Game.Screens.Menu; using osu.Game.Screens.Menu;
using osu.Game.Screens.OnlinePlay.Components; using osu.Game.Screens.OnlinePlay.Components;
using osu.Game.Screens.OnlinePlay.Lounge; using osu.Game.Screens.OnlinePlay.Lounge;
using osu.Game.Users; using osu.Game.Users;
using osuTK;
using osuTK.Graphics;
namespace osu.Game.Screens.OnlinePlay namespace osu.Game.Screens.OnlinePlay
{ {
@ -45,9 +37,6 @@ namespace osu.Game.Screens.OnlinePlay
[Cached(Type = typeof(IRoomManager))] [Cached(Type = typeof(IRoomManager))]
protected RoomManager RoomManager { get; private set; } protected RoomManager RoomManager { get; private set; }
[Cached]
private readonly Bindable<Room> selectedRoom = new Bindable<Room>();
[Cached] [Cached]
private readonly OngoingOperationTracker ongoingOperationTracker = new OngoingOperationTracker(); private readonly OngoingOperationTracker ongoingOperationTracker = new OngoingOperationTracker();
@ -78,38 +67,12 @@ namespace osu.Game.Screens.OnlinePlay
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load() private void load()
{ {
var backgroundColour = Color4Extensions.FromHex(@"3e3a44");
InternalChild = waves = new MultiplayerWaveContainer InternalChild = waves = new MultiplayerWaveContainer
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
Children = new Drawable[] Children = new Drawable[]
{ {
new Box screenStack = new OnlinePlaySubScreenStack { RelativeSizeAxes = Axes.Both },
{
RelativeSizeAxes = Axes.Both,
Colour = backgroundColour,
},
new Container
{
RelativeSizeAxes = Axes.Both,
Children = new Drawable[]
{
new BeatmapBackgroundSprite
{
RelativeSizeAxes = Axes.Both
},
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = ColourInfo.GradientVertical(Color4.Black.Opacity(0.9f), Color4.Black.Opacity(0.6f))
},
screenStack = new OnlinePlaySubScreenStack
{
RelativeSizeAxes = Axes.Both
}
}
},
new Header(ScreenTitle, screenStack), new Header(ScreenTitle, screenStack),
RoomManager, RoomManager,
ongoingOperationTracker ongoingOperationTracker
@ -139,13 +102,6 @@ namespace osu.Game.Screens.OnlinePlay
apiState.BindValueChanged(onlineStateChanged, true); apiState.BindValueChanged(onlineStateChanged, true);
} }
protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent)
{
var dependencies = new CachedModelDependencyContainer<Room>(base.CreateChildDependencies(parent));
dependencies.Model.BindTo(selectedRoom);
return dependencies;
}
private void forcefullyExit() private void forcefullyExit()
{ {
// This is temporary since we don't currently have a way to force screens to be exited // This is temporary since we don't currently have a way to force screens to be exited
@ -276,51 +232,6 @@ namespace osu.Game.Screens.OnlinePlay
} }
} }
private class BeatmapBackgroundSprite : OnlinePlayBackgroundSprite
{
protected override UpdateableBeatmapBackgroundSprite CreateBackgroundSprite() => new BlurredBackgroundSprite(BeatmapSetCoverType) { RelativeSizeAxes = Axes.Both };
public class BlurredBackgroundSprite : UpdateableBeatmapBackgroundSprite
{
public BlurredBackgroundSprite(BeatmapSetCoverType type)
: base(type)
{
}
protected override double LoadDelay => 200;
protected override Drawable CreateDrawable(BeatmapInfo model) =>
new BufferedLoader(base.CreateDrawable(model));
}
// This class is an unfortunate requirement due to `LongRunningLoad` requiring direct async loading.
// It means that if the web request fetching the beatmap background takes too long, it will suddenly appear.
internal class BufferedLoader : BufferedContainer
{
private readonly Drawable drawable;
public BufferedLoader(Drawable drawable)
{
this.drawable = drawable;
RelativeSizeAxes = Axes.Both;
BlurSigma = new Vector2(10);
FrameBufferScale = new Vector2(0.5f);
CacheDrawnFrameBuffer = true;
}
[BackgroundDependencyLoader]
private void load()
{
LoadComponentAsync(drawable, d =>
{
Add(d);
ForceRedraw();
});
}
}
}
ScreenStack IHasSubScreenStack.SubScreenStack => screenStack; ScreenStack IHasSubScreenStack.SubScreenStack => screenStack;
} }
} }

View File

@ -29,23 +29,26 @@ namespace osu.Game.Screens.OnlinePlay
public override void OnEntering(IScreen last) public override void OnEntering(IScreen last)
{ {
base.OnEntering(last);
this.FadeInFromZero(APPEAR_DURATION, Easing.OutQuint); this.FadeInFromZero(APPEAR_DURATION, Easing.OutQuint);
} }
public override bool OnExiting(IScreen next) public override bool OnExiting(IScreen next)
{ {
base.OnExiting(next);
this.FadeOut(DISAPPEAR_DURATION, Easing.OutQuint); this.FadeOut(DISAPPEAR_DURATION, Easing.OutQuint);
return false; return false;
} }
public override void OnResuming(IScreen last) public override void OnResuming(IScreen last)
{ {
base.OnResuming(last);
this.FadeIn(APPEAR_DURATION, Easing.OutQuint); this.FadeIn(APPEAR_DURATION, Easing.OutQuint);
} }
public override void OnSuspending(IScreen next) public override void OnSuspending(IScreen next)
{ {
base.OnSuspending(next);
this.FadeOut(DISAPPEAR_DURATION, Easing.OutQuint); this.FadeOut(DISAPPEAR_DURATION, Easing.OutQuint);
} }

View File

@ -5,7 +5,6 @@ using System;
using System.Collections.Specialized; using System.Collections.Specialized;
using Humanizer; using Humanizer;
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.Containers; using osu.Framework.Graphics.Containers;
@ -32,15 +31,19 @@ namespace osu.Game.Screens.OnlinePlay.Playlists
protected override bool IsLoading => settings.IsLoading; // should probably be replaced with an OngoingOperationTracker. protected override bool IsLoading => settings.IsLoading; // should probably be replaced with an OngoingOperationTracker.
public PlaylistsRoomSettingsOverlay(Room room)
: base(room)
{
}
protected override void SelectBeatmap() => settings.SelectBeatmap(); protected override void SelectBeatmap() => settings.SelectBeatmap();
protected override OnlinePlayComposite CreateSettings() protected override OnlinePlayComposite CreateSettings(Room room) => settings = new MatchSettings(room)
=> settings = new MatchSettings {
{ RelativeSizeAxes = Axes.Both,
RelativeSizeAxes = Axes.Both, RelativePositionAxes = Axes.Y,
RelativePositionAxes = Axes.Y, EditPlaylist = () => EditPlaylist?.Invoke()
EditPlaylist = () => EditPlaylist?.Invoke() };
};
protected class MatchSettings : OnlinePlayComposite protected class MatchSettings : OnlinePlayComposite
{ {
@ -66,8 +69,12 @@ namespace osu.Game.Screens.OnlinePlay.Playlists
[Resolved(CanBeNull = true)] [Resolved(CanBeNull = true)]
private IRoomManager manager { get; set; } private IRoomManager manager { get; set; }
[Resolved] private readonly Room room;
private Bindable<Room> currentRoom { get; set; }
public MatchSettings(Room room)
{
this.room = room;
}
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(OsuColour colours) private void load(OsuColour colours)
@ -333,7 +340,7 @@ namespace osu.Game.Screens.OnlinePlay.Playlists
Duration.Value = DurationField.Current.Value; Duration.Value = DurationField.Current.Value;
manager?.CreateRoom(currentRoom.Value, onSuccess, onError); manager?.CreateRoom(room, onSuccess, onError);
loadingLayer.Show(); loadingLayer.Show();
} }

View File

@ -50,7 +50,7 @@ namespace osu.Game.Screens.OnlinePlay.Playlists
if (idleTracker != null) if (idleTracker != null)
isIdle.BindTo(idleTracker.IsIdle); isIdle.BindTo(idleTracker.IsIdle);
AddInternal(selectionPollingComponent = new SelectionPollingComponent()); AddInternal(selectionPollingComponent = new SelectionPollingComponent(Room));
} }
protected override void LoadComplete() protected override void LoadComplete()
@ -184,7 +184,7 @@ namespace osu.Game.Screens.OnlinePlay.Playlists
OnStart = StartPlay OnStart = StartPlay
}; };
protected override RoomSettingsOverlay CreateRoomSettingsOverlay() => new PlaylistsRoomSettingsOverlay protected override RoomSettingsOverlay CreateRoomSettingsOverlay(Room room) => new PlaylistsRoomSettingsOverlay(room)
{ {
EditPlaylist = () => EditPlaylist = () =>
{ {

View File

@ -71,6 +71,7 @@ namespace osu.Game.Screens.Select
private void load() private void load()
{ {
ruleset.BindValueChanged(_ => updateDisplay()); ruleset.BindValueChanged(_ => updateDisplay());
mods.BindValueChanged(_ => updateDisplay());
} }
private const double animation_duration = 800; private const double animation_duration = 800;
@ -449,8 +450,11 @@ namespace osu.Game.Screens.Select
{ {
public LocalisableString TooltipText { get; } public LocalisableString TooltipText { get; }
internal BeatmapStatistic Statistic { get; }
public InfoLabel(BeatmapStatistic statistic) public InfoLabel(BeatmapStatistic statistic)
{ {
Statistic = statistic;
TooltipText = statistic.Name; TooltipText = statistic.Name;
AutoSizeAxes = Axes.Both; AutoSizeAxes = Axes.Both;

View File

@ -9,6 +9,8 @@ using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterface;
using System.Linq; using System.Linq;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Framework.Localisation;
using osu.Game.Resources.Localisation.Web;
namespace osu.Game.Screens.Select.Details namespace osu.Game.Screens.Select.Details
{ {
@ -35,8 +37,8 @@ namespace osu.Game.Screens.Select.Details
if (metrics == null) if (metrics == null)
{ {
negativeRatings.Text = "0"; negativeRatings.Text = 0.ToLocalisableString(@"N0");
positiveRatings.Text = "0"; positiveRatings.Text = 0.ToLocalisableString(@"N0");
ratingsBar.Length = 0; ratingsBar.Length = 0;
graph.Values = new float[rating_range]; graph.Values = new float[rating_range];
} }
@ -47,8 +49,8 @@ namespace osu.Game.Screens.Select.Details
var negativeCount = ratings.Take(rating_range / 2).Sum(); var negativeCount = ratings.Take(rating_range / 2).Sum();
var totalCount = ratings.Sum(); var totalCount = ratings.Sum();
negativeRatings.Text = negativeCount.ToString(); negativeRatings.Text = negativeCount.ToLocalisableString(@"N0");
positiveRatings.Text = (totalCount - negativeCount).ToString(); positiveRatings.Text = (totalCount - negativeCount).ToLocalisableString(@"N0");
ratingsBar.Length = totalCount == 0 ? 0 : (float)negativeCount / totalCount; ratingsBar.Length = totalCount == 0 ? 0 : (float)negativeCount / totalCount;
graph.Values = ratings.Take(rating_range).Select(r => (float)r); graph.Values = ratings.Take(rating_range).Select(r => (float)r);
} }
@ -70,7 +72,7 @@ namespace osu.Game.Screens.Select.Details
{ {
Anchor = Anchor.TopCentre, Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre, Origin = Anchor.TopCentre,
Text = "User Rating", Text = BeatmapsetsStrings.ShowStatsUserRating,
Font = OsuFont.GetFont(size: 12), Font = OsuFont.GetFont(size: 12),
Margin = new MarginPadding { Bottom = 5 }, Margin = new MarginPadding { Bottom = 5 },
}, },
@ -88,14 +90,14 @@ namespace osu.Game.Screens.Select.Details
{ {
negativeRatings = new OsuSpriteText negativeRatings = new OsuSpriteText
{ {
Text = "0", Text = 0.ToLocalisableString(@"N0"),
Font = OsuFont.GetFont(size: 12) Font = OsuFont.GetFont(size: 12)
}, },
positiveRatings = new OsuSpriteText positiveRatings = new OsuSpriteText
{ {
Anchor = Anchor.TopRight, Anchor = Anchor.TopRight,
Origin = Anchor.TopRight, Origin = Anchor.TopRight,
Text = @"0", Text = 0.ToLocalisableString(@"N0"),
Font = OsuFont.GetFont(size: 12) Font = OsuFont.GetFont(size: 12)
}, },
}, },
@ -104,7 +106,7 @@ namespace osu.Game.Screens.Select.Details
{ {
Anchor = Anchor.TopCentre, Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre, Origin = Anchor.TopCentre,
Text = "Rating Spread", Text = BeatmapsetsStrings.ShowStatsRatingSpread,
Font = OsuFont.GetFont(size: 12), Font = OsuFont.GetFont(size: 12),
Margin = new MarginPadding { Bottom = 5 }, Margin = new MarginPadding { Bottom = 5 },
}, },

View File

@ -2,6 +2,8 @@
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using System.ComponentModel; using System.ComponentModel;
using osu.Framework.Localisation;
using osu.Game.Resources.Localisation.Web;
namespace osu.Game.Screens.Select.Leaderboards namespace osu.Game.Screens.Select.Leaderboards
{ {
@ -10,13 +12,13 @@ namespace osu.Game.Screens.Select.Leaderboards
[Description("Local Ranking")] [Description("Local Ranking")]
Local, Local,
[Description("Country Ranking")] [LocalisableDescription(typeof(BeatmapsetsStrings), nameof(BeatmapsetsStrings.ShowScoreboardCountry))]
Country, Country,
[Description("Global Ranking")] [LocalisableDescription(typeof(BeatmapsetsStrings), nameof(BeatmapsetsStrings.ShowScoreboardGlobal))]
Global, Global,
[Description("Friend Ranking")] [LocalisableDescription(typeof(BeatmapsetsStrings), nameof(BeatmapsetsStrings.ShowScoreboardFriend))]
Friend, Friend,
} }
} }

View File

@ -37,7 +37,7 @@
</PackageReference> </PackageReference>
<PackageReference Include="Realm" Version="10.3.0" /> <PackageReference Include="Realm" Version="10.3.0" />
<PackageReference Include="ppy.osu.Framework" Version="2021.819.0" /> <PackageReference Include="ppy.osu.Framework" Version="2021.819.0" />
<PackageReference Include="ppy.osu.Game.Resources" Version="2021.820.0" /> <PackageReference Include="ppy.osu.Game.Resources" Version="2021.822.0" />
<PackageReference Include="Sentry" Version="3.8.3" /> <PackageReference Include="Sentry" Version="3.8.3" />
<PackageReference Include="SharpCompress" Version="0.28.3" /> <PackageReference Include="SharpCompress" Version="0.28.3" />
<PackageReference Include="NUnit" Version="3.13.2" /> <PackageReference Include="NUnit" Version="3.13.2" />

View File

@ -71,7 +71,7 @@
</ItemGroup> </ItemGroup>
<ItemGroup Label="Package References"> <ItemGroup Label="Package References">
<PackageReference Include="ppy.osu.Framework.iOS" Version="2021.819.0" /> <PackageReference Include="ppy.osu.Framework.iOS" Version="2021.819.0" />
<PackageReference Include="ppy.osu.Game.Resources" Version="2021.820.0" /> <PackageReference Include="ppy.osu.Game.Resources" Version="2021.822.0" />
</ItemGroup> </ItemGroup>
<!-- See https://github.com/dotnet/runtime/issues/35988 (can be removed after Xamarin uses net5.0 / net6.0) --> <!-- See https://github.com/dotnet/runtime/issues/35988 (can be removed after Xamarin uses net5.0 / net6.0) -->
<PropertyGroup> <PropertyGroup>