1
0
mirror of https://github.com/ppy/osu.git synced 2025-03-22 22:29:28 +08:00

Merge branch 'master' into move-difficulty-graph-toggle

This commit is contained in:
Dean Herbert 2022-04-29 13:44:47 +09:00
commit c4495dafb6
56 changed files with 1276 additions and 241 deletions

View File

@ -191,4 +191,5 @@ dotnet_diagnostic.CA2225.severity = none
# Banned APIs
dotnet_diagnostic.RS0030.severity = error
dotnet_diagnostic.OLOC001.words_in_name = 5
dotnet_diagnostic.OLOC001.license_header = // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.\n// See the LICENCE file in the repository root for full licence text.

View File

@ -52,7 +52,7 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="ppy.osu.Game.Resources" Version="2022.422.0" />
<PackageReference Include="ppy.osu.Framework.Android" Version="2022.423.0" />
<PackageReference Include="ppy.osu.Framework.Android" Version="2022.428.0" />
</ItemGroup>
<ItemGroup Label="Transitive Dependencies">
<!-- Realm needs to be directly referenced in all Xamarin projects, as it will not pull in its transitive dependencies otherwise. -->

View File

@ -5,10 +5,10 @@ using System;
using System.Collections.Generic;
using System.Linq;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Catch.MathUtils;
using osu.Game.Rulesets.Catch.Objects;
using osu.Game.Rulesets.Catch.UI;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Utils;
namespace osu.Game.Rulesets.Catch.Beatmaps
{
@ -46,7 +46,7 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
public void ApplyPositionOffsets(IBeatmap beatmap)
{
var rng = new FastRandom(RNG_SEED);
var rng = new LegacyRandom(RNG_SEED);
float? lastPosition = null;
double lastStartTime = 0;
@ -98,7 +98,7 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
initialiseHyperDash(beatmap);
}
private static void applyHardRockOffset(CatchHitObject hitObject, ref float? lastPosition, ref double lastStartTime, FastRandom rng)
private static void applyHardRockOffset(CatchHitObject hitObject, ref float? lastPosition, ref double lastStartTime, LegacyRandom rng)
{
float offsetPosition = hitObject.OriginalX;
double startTime = hitObject.StartTime;
@ -146,7 +146,7 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
/// <param name="position">The position which the offset should be applied to.</param>
/// <param name="maxOffset">The maximum offset, cannot exceed 20px.</param>
/// <param name="rng">The random number generator.</param>
private static void applyRandomOffset(ref float position, double maxOffset, FastRandom rng)
private static void applyRandomOffset(ref float position, double maxOffset, LegacyRandom rng)
{
bool right = rng.NextBool();
float rand = Math.Min(20, (float)rng.Next(0, Math.Max(0, maxOffset)));

View File

@ -11,8 +11,8 @@ using osu.Game.Beatmaps;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Mania.Beatmaps.Patterns;
using osu.Game.Rulesets.Mania.MathUtils;
using osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy;
using osu.Game.Utils;
using osuTK;
namespace osu.Game.Rulesets.Mania.Beatmaps
@ -31,7 +31,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
private readonly int originalTargetColumns;
// Internal for testing purposes
internal FastRandom Random { get; private set; }
internal LegacyRandom Random { get; private set; }
private Pattern lastPattern = new Pattern();
@ -84,7 +84,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
IBeatmapDifficultyInfo difficulty = original.Difficulty;
int seed = (int)MathF.Round(difficulty.DrainRate + difficulty.CircleSize) * 20 + (int)(difficulty.OverallDifficulty * 41.2) + (int)MathF.Round(difficulty.ApproachRate);
Random = new FastRandom(seed);
Random = new LegacyRandom(seed);
return base.ConvertBeatmap(original, cancellationToken);
}
@ -227,7 +227,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
/// </summary>
private class SpecificBeatmapPatternGenerator : Patterns.Legacy.PatternGenerator
{
public SpecificBeatmapPatternGenerator(FastRandom random, HitObject hitObject, ManiaBeatmap beatmap, Pattern previousPattern, IBeatmap originalBeatmap)
public SpecificBeatmapPatternGenerator(LegacyRandom random, HitObject hitObject, ManiaBeatmap beatmap, Pattern previousPattern, IBeatmap originalBeatmap)
: base(random, hitObject, beatmap, previousPattern, originalBeatmap)
{
}

View File

@ -8,12 +8,12 @@ using System.Linq;
using osu.Framework.Extensions.EnumExtensions;
using osu.Game.Audio;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Mania.MathUtils;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Beatmaps.Formats;
using osu.Game.Utils;
namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
{
@ -34,7 +34,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
private PatternType convertType;
public DistanceObjectPatternGenerator(FastRandom random, HitObject hitObject, ManiaBeatmap beatmap, Pattern previousPattern, IBeatmap originalBeatmap)
public DistanceObjectPatternGenerator(LegacyRandom random, HitObject hitObject, ManiaBeatmap beatmap, Pattern previousPattern, IBeatmap originalBeatmap)
: base(random, hitObject, beatmap, previousPattern, originalBeatmap)
{
convertType = PatternType.None;

View File

@ -2,13 +2,13 @@
// See the LICENCE file in the repository root for full licence text.
using System.Collections.Generic;
using osu.Game.Rulesets.Mania.MathUtils;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types;
using System.Linq;
using osu.Game.Audio;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Utils;
namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
{
@ -17,7 +17,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
private readonly int endTime;
private readonly PatternType convertType;
public EndTimeObjectPatternGenerator(FastRandom random, HitObject hitObject, ManiaBeatmap beatmap, Pattern previousPattern, IBeatmap originalBeatmap)
public EndTimeObjectPatternGenerator(LegacyRandom random, HitObject hitObject, ManiaBeatmap beatmap, Pattern previousPattern, IBeatmap originalBeatmap)
: base(random, hitObject, beatmap, previousPattern, originalBeatmap)
{
endTime = (int)((HitObject as IHasDuration)?.EndTime ?? 0);

View File

@ -9,11 +9,11 @@ using osuTK;
using osu.Game.Audio;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Mania.MathUtils;
using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types;
using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Game.Utils;
namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
{
@ -23,7 +23,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
private readonly PatternType convertType;
public HitObjectPatternGenerator(FastRandom random, HitObject hitObject, ManiaBeatmap beatmap, Pattern previousPattern, double previousTime, Vector2 previousPosition, double density,
public HitObjectPatternGenerator(LegacyRandom random, HitObject hitObject, ManiaBeatmap beatmap, Pattern previousPattern, double previousTime, Vector2 previousPosition, double density,
PatternType lastStair, IBeatmap originalBeatmap)
: base(random, hitObject, beatmap, previousPattern, originalBeatmap)
{

View File

@ -5,8 +5,8 @@ using System;
using System.Linq;
using JetBrains.Annotations;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Mania.MathUtils;
using osu.Game.Rulesets.Objects;
using osu.Game.Utils;
namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
{
@ -23,14 +23,14 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
/// <summary>
/// The random number generator to use.
/// </summary>
protected readonly FastRandom Random;
protected readonly LegacyRandom Random;
/// <summary>
/// The beatmap which <see cref="HitObject"/> is being converted from.
/// </summary>
protected readonly IBeatmap OriginalBeatmap;
protected PatternGenerator(FastRandom random, HitObject hitObject, ManiaBeatmap beatmap, Pattern previousPattern, IBeatmap originalBeatmap)
protected PatternGenerator(LegacyRandom random, HitObject hitObject, ManiaBeatmap beatmap, Pattern previousPattern, IBeatmap originalBeatmap)
: base(hitObject, beatmap, previousPattern)
{
if (random == null) throw new ArgumentNullException(nameof(random));

View File

@ -1,95 +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 System;
namespace osu.Game.Rulesets.Mania.MathUtils
{
/// <summary>
/// A PRNG specified in http://heliosphan.org/fastrandom.html.
/// </summary>
internal class FastRandom
{
private const double int_to_real = 1.0 / (int.MaxValue + 1.0);
private const uint int_mask = 0x7FFFFFFF;
private const uint y = 842502087;
private const uint z = 3579807591;
private const uint w = 273326509;
internal uint X { get; private set; }
internal uint Y { get; private set; } = y;
internal uint Z { get; private set; } = z;
internal uint W { get; private set; } = w;
public FastRandom(int seed)
{
X = (uint)seed;
}
public FastRandom()
: this(Environment.TickCount)
{
}
/// <summary>
/// Generates a random unsigned integer within the range [<see cref="uint.MinValue"/>, <see cref="uint.MaxValue"/>).
/// </summary>
/// <returns>The random value.</returns>
public uint NextUInt()
{
uint t = X ^ (X << 11);
X = Y;
Y = Z;
Z = W;
return W = W ^ (W >> 19) ^ t ^ (t >> 8);
}
/// <summary>
/// Generates a random integer value within the range [0, <see cref="int.MaxValue"/>).
/// </summary>
/// <returns>The random value.</returns>
public int Next() => (int)(int_mask & NextUInt());
/// <summary>
/// Generates a random integer value within the range [0, <paramref name="upperBound"/>).
/// </summary>
/// <param name="upperBound">The upper bound.</param>
/// <returns>The random value.</returns>
public int Next(int upperBound) => (int)(NextDouble() * upperBound);
/// <summary>
/// Generates a random integer value within the range [<paramref name="lowerBound"/>, <paramref name="upperBound"/>).
/// </summary>
/// <param name="lowerBound">The lower bound of the range.</param>
/// <param name="upperBound">The upper bound of the range.</param>
/// <returns>The random value.</returns>
public int Next(int lowerBound, int upperBound) => (int)(lowerBound + NextDouble() * (upperBound - lowerBound));
/// <summary>
/// Generates a random double value within the range [0, 1).
/// </summary>
/// <returns>The random value.</returns>
public double NextDouble() => int_to_real * Next();
private uint bitBuffer;
private int bitIndex = 32;
/// <summary>
/// Generates a reandom boolean value. Cached such that a random value is only generated once in every 32 calls.
/// </summary>
/// <returns>The random value.</returns>
public bool NextBool()
{
if (bitIndex == 32)
{
bitBuffer = NextUInt();
bitIndex = 1;
return (bitBuffer & 1) == 1;
}
bitIndex++;
return ((bitBuffer >>= 1) & 1) == 1;
}
}
}

View File

@ -33,6 +33,7 @@ namespace osu.Game.Rulesets.Osu.UI
},
new SettingsCheckbox
{
ClassicDefault = false,
LabelText = "Snaking out sliders",
Current = config.GetBindable<bool>(OsuRulesetSetting.SnakingOutSliders)
},

View File

@ -0,0 +1,24 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using NUnit.Framework;
using osu.Game.Beatmaps.Drawables;
namespace osu.Game.Tests.Visual.Online
{
[Ignore("Only for visual testing")]
public class TestSceneBundledBeatmapDownloader : OsuTestScene
{
private BundledBeatmapDownloader downloader;
[Test]
public void TestDownloader()
{
AddStep("Create downloader", () =>
{
downloader?.Expire();
Add(downloader = new BundledBeatmapDownloader(false));
});
}
}
}

View File

@ -0,0 +1,24 @@
// 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.Screens;
using osu.Game.Overlays;
using osu.Game.Overlays.FirstRunSetup;
namespace osu.Game.Tests.Visual.UserInterface
{
public class TestSceneFirstRunScreenBehaviour : OsuManualInputManagerTestScene
{
[Cached]
private OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Purple);
public TestSceneFirstRunScreenBehaviour()
{
AddStep("load screen", () =>
{
Child = new ScreenStack(new ScreenBehaviour());
});
}
}
}

View File

@ -0,0 +1,24 @@
// 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.Screens;
using osu.Game.Overlays;
using osu.Game.Overlays.FirstRunSetup;
namespace osu.Game.Tests.Visual.UserInterface
{
public class TestSceneFirstRunScreenBundledBeatmaps : OsuManualInputManagerTestScene
{
[Cached]
private OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Purple);
public TestSceneFirstRunScreenBundledBeatmaps()
{
AddStep("load screen", () =>
{
Child = new ScreenStack(new ScreenBeatmaps());
});
}
}
}

View File

@ -1,13 +1,18 @@
// 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.Screens;
using osu.Game.Overlays;
using osu.Game.Overlays.FirstRunSetup;
namespace osu.Game.Tests.Visual.UserInterface
{
public class TestSceneFirstRunScreenUIScale : OsuManualInputManagerTestScene
{
[Cached]
private OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Purple);
public TestSceneFirstRunScreenUIScale()
{
AddStep("load screen", () =>

View File

@ -178,7 +178,7 @@ namespace osu.Game.Tests.Visual.UserInterface
{
AddStep("step to next", () => overlay.NextButton.TriggerClick());
AddAssert("is at known screen", () => overlay.CurrentScreen is ScreenUIScale);
AddAssert("is at known screen", () => overlay.CurrentScreen is ScreenBeatmaps);
AddStep("hide", () => overlay.Hide());
AddAssert("overlay hidden", () => overlay.State.Value == Visibility.Hidden);
@ -188,7 +188,7 @@ namespace osu.Game.Tests.Visual.UserInterface
AddStep("run notification action", () => lastNotification.Activated());
AddAssert("overlay shown", () => overlay.State.Value == Visibility.Visible);
AddAssert("is resumed", () => overlay.CurrentScreen is ScreenUIScale);
AddAssert("is resumed", () => overlay.CurrentScreen is ScreenBeatmaps);
}
}
}

View File

@ -111,6 +111,33 @@ namespace osu.Game.Tests.Visual.UserInterface
&& SelectedMods.Value.Any(mod => mod.GetType() == typeof(OsuModMirror)));
}
[Test]
public void TestDimmedState()
{
createScreen();
changeRuleset(0);
AddUntilStep("any column dimmed", () => this.ChildrenOfType<ModColumn>().Any(column => !column.Active.Value));
ModColumn lastColumn = null;
AddAssert("last column dimmed", () => !this.ChildrenOfType<ModColumn>().Last().Active.Value);
AddStep("request scroll to last column", () =>
{
var lastDimContainer = this.ChildrenOfType<ModSelectScreen.ColumnDimContainer>().Last();
lastColumn = lastDimContainer.Column;
lastDimContainer.RequestScroll?.Invoke(lastDimContainer);
});
AddUntilStep("column undimmed", () => lastColumn.Active.Value);
AddStep("click panel", () =>
{
InputManager.MoveMouseTo(lastColumn.ChildrenOfType<ModPanel>().First());
InputManager.Click(MouseButton.Left);
});
AddUntilStep("panel selected", () => lastColumn.ChildrenOfType<ModPanel>().First().Active.Value);
}
[Test]
public void TestCustomisationToggleState()
{

View File

@ -1,3 +1,4 @@
[*.cs]
dotnet_diagnostic.OLOC001.words_in_name = 5
dotnet_diagnostic.OLOC001.prefix_namespace = osu.Game.Resources.Localisation
dotnet_diagnostic.OLOC001.license_header = // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.\n// See the LICENCE file in the repository root for full licence text.
dotnet_diagnostic.OLOC001.license_header = // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.\n// See the LICENCE file in the repository root for full licence text.

View File

@ -0,0 +1,339 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using osu.Framework.Allocation;
using osu.Framework.Graphics.Containers;
using osu.Game.Database;
using osu.Game.Online;
using osu.Game.Online.API;
using osu.Game.Online.API.Requests;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Overlays;
using osu.Game.Utils;
namespace osu.Game.Beatmaps.Drawables
{
public class BundledBeatmapDownloader : CompositeDrawable
{
private readonly bool shouldPostNotifications;
public IEnumerable<BeatmapDownloadTracker> DownloadTrackers => downloadTrackers;
private readonly List<BeatmapDownloadTracker> downloadTrackers = new List<BeatmapDownloadTracker>();
private readonly List<string> downloadableFilenames = new List<string>();
private BundledBeatmapModelDownloader beatmapDownloader;
/// <summary>
/// Construct a new beatmap downloader.
/// </summary>
/// <param name="onlyTutorial">Whether only the tutorial should be downloaded, instead of bundled beatmaps.</param>
/// <param name="shouldPostNotifications">Whether downloads should create tracking notifications.</param>
public BundledBeatmapDownloader(bool onlyTutorial, bool shouldPostNotifications = false)
{
this.shouldPostNotifications = shouldPostNotifications;
if (onlyTutorial)
{
queueDownloads(new[] { tutorial_filename });
}
else
{
queueDownloads(always_bundled_beatmaps);
queueDownloads(bundled_osu, 8);
queueDownloads(bundled_taiko, 3);
queueDownloads(bundled_catch, 3);
queueDownloads(bundled_mania, 3);
}
}
protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent)
{
var localDependencies = new DependencyContainer(base.CreateChildDependencies(parent));
localDependencies.CacheAs<BeatmapModelDownloader>(beatmapDownloader = new BundledBeatmapModelDownloader(parent.Get<BeatmapManager>(), parent.Get<IAPIProvider>()));
if (shouldPostNotifications && parent.Get<INotificationOverlay>() is INotificationOverlay notifications)
beatmapDownloader.PostNotification = notifications.Post;
return localDependencies;
}
[BackgroundDependencyLoader]
private void load()
{
foreach (string filename in downloadableFilenames)
{
var match = Regex.Match(filename, @"([0-9]*) (.*) - (.*)\.osz");
var beatmapSet = new APIBeatmapSet
{
OnlineID = int.Parse(match.Groups[1].Value),
Artist = match.Groups[2].Value,
Title = match.Groups[3].Value,
};
var beatmapDownloadTracker = new BeatmapDownloadTracker(beatmapSet);
downloadTrackers.Add(beatmapDownloadTracker);
AddInternal(beatmapDownloadTracker);
beatmapDownloader.Download(beatmapSet);
}
}
private void queueDownloads(string[] sourceFilenames, int? limit = null)
{
try
{
// Matches osu-stable, in order to provide new users with roughly the same randomised selection of bundled beatmaps.
var random = new LegacyRandom(DateTime.UtcNow.Year * 1000 + (DateTime.UtcNow.DayOfYear / 7));
downloadableFilenames.AddRange(sourceFilenames.OrderBy(x => random.NextDouble()).Take(limit ?? int.MaxValue));
}
catch { }
}
private class BundledBeatmapModelDownloader : BeatmapModelDownloader
{
public BundledBeatmapModelDownloader(IModelImporter<BeatmapSetInfo> beatmapImporter, IAPIProvider api)
: base(beatmapImporter, api)
{
}
protected override ArchiveDownloadRequest<IBeatmapSetInfo> CreateDownloadRequest(IBeatmapSetInfo set, bool minimiseDownloadSize)
=> new BundledBeatmapDownloadRequest(set, minimiseDownloadSize);
public class BundledBeatmapDownloadRequest : DownloadBeatmapSetRequest
{
protected override string Uri => $"https://assets.ppy.sh/client-resources/bundled/{Model.OnlineID}.osz";
public BundledBeatmapDownloadRequest(IBeatmapSetInfo beatmapSetInfo, bool minimiseDownloadSize)
: base(beatmapSetInfo, minimiseDownloadSize)
{
}
}
}
private const string tutorial_filename = "1011011 nekodex - new beginnings.osz";
/// <summary>
/// Contest winners or other special cases.
/// </summary>
private static readonly string[] always_bundled_beatmaps =
{
// This thing is 40mb, I'm not sure we want it here...
@"1388906 Raphlesia & BilliumMoto - My Love.osz"
};
private static readonly string[] bundled_osu =
{
"682286 Yuyoyuppe - Emerald Galaxy.osz",
"682287 baker - For a Dead Girl+.osz",
"682289 Hige Driver - I Wanna Feel Your Love (feat. shully).osz",
"682290 Hige Driver - Miracle Sugite Yabai (feat. shully).osz",
"682416 Hige Driver - Palette.osz",
"682595 baker - Kimi ga Kimi ga -vocanico remix-.osz",
"716211 yuki. - Spring Signal.osz",
"716213 dark cat - BUBBLE TEA (feat. juu & cinders).osz",
"716215 LukHash - CLONED.osz",
"716219 IAHN - Snowdrop.osz",
"716249 *namirin - Senaka Awase no Kuukyo (with Kakichoco).osz",
"716390 sakuraburst - SHA.osz",
"716441 Fractal Dreamers - Paradigm Shift.osz",
"729808 Thaehan - Leprechaun.osz",
"751771 Cranky - Hanaarashi.osz",
"751772 Cranky - Ran.osz",
"751773 Cranky - Feline, the White....osz",
"751774 Function Phantom - Variable.osz",
"751779 Rin - Daishibyo set 14 ~ Sado no Futatsuiwa.osz",
"751782 Fractal Dreamers - Fata Morgana.osz",
"751785 Cranky - Chandelier - King.osz",
"751846 Fractal Dreamers - Celestial Horizon.osz",
"751866 Rin - Moriya set 08 ReEdit ~ Youkai no Yama.osz",
"751894 Fractal Dreamers - Blue Haven.osz",
"751896 Cranky - Rave 2 Rave.osz",
"751932 Cranky - La fuite des jours.osz",
"751972 Cranky - CHASER.osz",
"779173 Thaehan - Superpower.osz",
"780932 VINXIS - A Centralized View.osz",
"785572 S3RL - I'll See You Again (feat. Chi Chi).osz",
"785650 yuki. feat. setsunan - Hello! World.osz",
"785677 Dictate - Militant.osz",
"785731 S3RL - Catchit (Radio Edit).osz",
"785774 LukHash - GLITCH.osz",
"786498 Trial & Error - Tokoyami no keiyaku KEGARETA-SHOUJO feat. GUMI.osz",
"789374 Pulse - LP.osz",
"789528 James Portland - Sky.osz",
"789529 Lexurus - Gravity.osz",
"789544 Andromedik - Invasion.osz",
"789905 Gourski x Himmes - Silence.osz",
"791667 cYsmix - Babaroque (Short Ver.).osz",
"791798 cYsmix - Behind the Walls.osz",
"791845 cYsmix - Little Knight.osz",
"792241 cYsmix - Eden.osz",
"792396 cYsmix - The Ballad of a Mindless Girl.osz",
"795432 Phonetic - Journey.osz",
"831322 DJ'TEKINA//SOMETHING - Hidamari no Uta.osz",
"847764 Cranky - Crocus.osz",
"847776 Culprate & Joe Ford - Gaucho.osz",
"847812 J. Pachelbel - Canon (Cranky Remix).osz",
"847900 Cranky - Time Alter.osz",
"847930 LukHash - 8BIT FAIRY TALE.osz",
"848003 Culprate - Aurora.osz",
"848068 nanobii - popsicle beach.osz",
"848090 Trial & Error - DAI*TAN SENSATION feat. Nanahira, Mii, Aitsuki Nakuru (Short Ver.).osz",
"848259 Culprate & Skorpion - Jester.osz",
"848976 Dictate - Treason.osz",
"851543 Culprate - Florn.osz",
"864748 Thaehan - Angry Birds Epic (Remix).osz",
"873667 OISHII - ONIGIRI FREEWAY.osz",
"876227 Culprate, Keota & Sophie Meiers - Mechanic Heartbeat.osz",
"880487 cYsmix - Peer Gynt.osz",
"883088 Wisp X - Somewhere I'd Rather Be.osz",
"891333 HyuN - White Aura.osz",
"891334 HyuN - Wild Card.osz",
"891337 HyuN feat. LyuU - Cross Over.osz",
"891338 HyuN & Ritoru - Apocalypse in Love.osz",
"891339 HyuN feat. Ato - Asu wa Ame ga Yamukara.osz",
"891345 HyuN - Infinity Heaven.osz",
"891348 HyuN - Guitian.osz",
"891356 HyuN - Legend of Genesis.osz",
"891366 HyuN - Illusion of Inflict.osz",
"891417 HyuN feat. Yu-A - My life is for you.osz",
"891441 HyuN - You'Re aRleAdY dEAd.osz",
"891632 HyuN feat. YURI - Disorder.osz",
"891712 HyuN - Tokyo's Starlight.osz",
"901091 *namirin - Ciel etoile.osz",
"916990 *namirin - Koishiteiku Planet.osz",
"929284 tieff - Sense of Nostalgia.osz",
"933940 Ben Briggs - Yes (Maybe).osz",
"934415 Ben Briggs - Fearless Living.osz",
"934627 Ben Briggs - New Game Plus.osz",
"934666 Ben Briggs - Wave Island.osz",
"936126 siromaru + cranky - conflict.osz",
"940377 onumi - ARROGANCE.osz",
"940597 tieff - Take Your Swimsuit.osz",
"941085 tieff - Our Story.osz",
"949297 tieff - Sunflower.osz",
"952380 Ben Briggs - Why Are We Yelling.osz",
"954272 *namirin - Kanzen Shouri*Esper Girl.osz",
"955866 KIRA & Heartbreaker - B.B.F (feat. Hatsune Miku & Kagamine Rin).osz",
"961320 Kuba Oms - All In All.osz",
"964553 The Flashbulb - You Take the World's Weight Away.osz",
"965651 Fractal Dreamers - Ad Astra.osz",
"966225 The Flashbulb - Passage D.osz",
"966324 DJ'TEKINA//SOMETHING - Hidamari no Uta.osz",
"972810 James Landino & Kabuki - Birdsong.osz",
"972932 James Landino - Hide And Seek.osz",
"977276 The Flashbulb - Mellann.osz",
"981616 *namirin - Mizutamari Tobikoete (with Nanahira).osz",
"985788 Loki - Wizard's Tower.osz",
"996628 OISHII - ONIGIRI FREEWAY.osz",
"996898 HyuN - White Aura.osz",
"1003554 yuki. - Nadeshiko Sensation.osz",
"1014936 Thaehan - Bwa !.osz",
"1019827 UNDEAD CORPORATION - Sad Dream.osz",
"1020213 Creo - Idolize.osz",
"1021450 Thaehan - Chiptune & Baroque.osz",
};
private static readonly string[] bundled_taiko =
{
"707824 Fractal Dreamers - Fortuna Redux.osz",
"789553 Cranky - Ran.osz",
"827822 Function Phantom - Neuronecia.osz",
"847323 Nakanojojo - Bittersweet (feat. Kuishinboakachan a.k.a Kiato).osz",
"847433 Trial & Error - Tokoyami no keiyaku KEGARETA-SHOUJO feat. GUMI.osz",
"847576 dark cat - hot chocolate.osz",
"847957 Wisp X - Final Moments.osz",
"876282 VINXIS - Greetings.osz",
"876648 Thaehan - Angry Birds Epic (Remix).osz",
"877069 IAHN - Transform (Original Mix).osz",
"877496 Thaehan - Leprechaun.osz",
"877935 Thaehan - Overpowered.osz",
"878344 yuki. - Be Your Light.osz",
"918446 VINXIS - Facade.osz",
"918903 LukHash - Ghosts.osz",
"919251 *namirin - Hitokoto no Kyori.osz",
"919704 S3RL - I Will Pick You Up (feat. Tamika).osz",
"921535 SOOOO - Raven Haven.osz",
"927206 *namirin - Kanzen Shouri*Esper Girl.osz",
"927544 Camellia feat. Nanahira - Kansoku Eisei.osz",
"930806 Nakanojojo - Pararara (feat. Amekoya).osz",
"931741 Camellia - Quaoar.osz",
"935699 Rin - Mythic set ~ Heart-Stirring Urban Legends.osz",
"935732 Thaehan - Yuujou.osz",
"941145 Function Phantom - Euclid.osz",
"942334 Dictate - Cauldron.osz",
"946540 nanobii - astral blast.osz",
"948844 Rin - Kishinjou set 01 ~ Mist Lake.osz",
"949122 Wisp X - Petal.osz",
"951618 Rin - Kishinjou set 02 ~ Mermaid from the Uncharted Land.osz",
"957412 Rin - Lunatic set 16 ~ The Space Shrine Maiden Returns Home.osz",
"961335 Thaehan - Insert Coin.osz",
"965178 The Flashbulb - DIDJ PVC.osz",
"966087 The Flashbulb - Creep.osz",
"966277 The Flashbulb - Amen Iraq.osz",
"966407 LukHash - ROOM 12.osz",
"966451 The Flashbulb - Six Acid Strings.osz",
"972301 BilliumMoto - four veiled stars.osz",
"973173 nanobii - popsicle beach.osz",
"973954 BilliumMoto - Rocky Buinne (Short Ver.).osz",
"975435 BilliumMoto - life flashes before weeb eyes.osz",
"978759 L. V. Beethoven - Moonlight Sonata (Cranky Remix).osz",
"982559 BilliumMoto - HDHR.osz",
"984361 The Flashbulb - Ninedump.osz",
"1023681 Inferi - The Ruin of Mankind.osz",
"1034358 ALEPH - The Evil Spirit.osz",
"1037567 ALEPH - Scintillations.osz",
};
private static readonly string[] bundled_catch =
{
"554256 Helblinde - When Time Sleeps.osz",
"693123 yuki. - Nadeshiko Sensation.osz",
"767009 OISHII - PIZZA PLAZA.osz",
"767346 Thaehan - Bwa !.osz",
"815162 VINXIS - Greetings.osz",
"840964 cYsmix - Breeze.osz",
"932657 Wisp X - Eventide.osz",
"933700 onumi - CONFUSION PART ONE.osz",
"933984 onumi - PERSONALITY.osz",
"934785 onumi - FAKE.osz",
"936545 onumi - REGRET PART ONE.osz",
"943803 Fractal Dreamers - Everything for a Dream.osz",
"943876 S3RL - I Will Pick You Up (feat. Tamika).osz",
"946773 Trial & Error - DREAMING COLOR (Short Ver.).osz",
"955808 Trial & Error - Tokoyami no keiyaku KEGARETA-SHOUJO feat. GUMI (Short Ver.).osz",
"957808 Fractal Dreamers - Module_410.osz",
"957842 antiPLUR - One Life Left to Live.osz",
"965730 The Flashbulb - Lawn Wake IV (Black).osz",
"966240 Creo - Challenger.osz",
"968232 Rin - Lunatic set 15 ~ The Moon as Seen from the Shrine.osz",
"972302 VINXIS - A Centralized View.osz",
"972887 HyuN - Illusion of Inflict.osz",
"1008600 LukHash - WHEN AN ANGEL DIES.osz",
"1032103 LukHash - H8 U.osz",
};
private static readonly string[] bundled_mania =
{
"943516 antiPLUR - Clockwork Spooks.osz",
"946394 VINXIS - Three Times The Original Charm.osz",
"966408 antiPLUR - One Life Left to Live.osz",
"971561 antiPLUR - Runengon.osz",
"983864 James Landino - Shiba Island.osz",
"989512 BilliumMoto - 1xMISS.osz",
"994104 James Landino - Reaction feat. Slyleaf.osz",
"1003217 nekodex - circles!.osz",
"1009907 James Landino & Kabuki - Birdsong.osz",
"1015169 Thaehan - Insert Coin.osz",
};
}
}

View File

@ -28,11 +28,6 @@ namespace osu.Game.Beatmaps.Drawables
},
downloadTracker = new BeatmapDownloadTracker(beatmapSet),
};
AddInternal(progressBar = new ProgressBar(false)
{
Height = 0,
Alpha = 0,
});
AutoSizeAxes = Axes.Y;
RelativeSizeAxes = Axes.X;

View File

@ -3,6 +3,7 @@
using System;
using Humanizer;
using osu.Framework.Extensions.LocalisationExtensions;
using osu.Framework.Localisation;
using osu.Game.Resources.Localisation.Web;
@ -42,12 +43,12 @@ namespace osu.Game.Extensions
public static LocalisableString ToFormattedDuration(this TimeSpan timeSpan)
{
if (timeSpan.TotalDays >= 1)
return new LocalisableFormattableString(timeSpan, @"dd\:hh\:mm\:ss");
return timeSpan.ToLocalisableString(@"dd\:hh\:mm\:ss");
if (timeSpan.TotalHours >= 1)
return new LocalisableFormattableString(timeSpan, @"hh\:mm\:ss");
return timeSpan.ToLocalisableString(@"hh\:mm\:ss");
return new LocalisableFormattableString(timeSpan, @"mm\:ss");
return timeSpan.ToLocalisableString(@"mm\:ss");
}
/// <summary>

View File

@ -3,6 +3,7 @@
#nullable enable
using System;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
@ -57,6 +58,26 @@ namespace osu.Game.Graphics.Containers
{
}
/// <summary>
/// Scrolls a <see cref="Drawable"/> into view.
/// </summary>
/// <param name="d">The <see cref="Drawable"/> to scroll into view.</param>
/// <param name="animated">Whether to animate the movement.</param>
/// <param name="extraScroll">An added amount to scroll beyond the requirement to bring the target into view.</param>
public void ScrollIntoView(Drawable d, bool animated = true, float extraScroll = 0)
{
float childPos0 = GetChildPosInContent(d);
float childPos1 = GetChildPosInContent(d, d.DrawSize);
float minPos = Math.Min(childPos0, childPos1);
float maxPos = Math.Max(childPos0, childPos1);
if (minPos < Current || (minPos > Current && d.DrawSize[ScrollDim] > DisplayableContent))
ScrollTo(minPos - extraScroll, animated);
else if (maxPos > Current + DisplayableContent)
ScrollTo(maxPos - DisplayableContent + extraScroll, animated);
}
protected override bool OnMouseDown(MouseDownEvent e)
{
if (shouldPerformRightMouseScroll(e))

View File

@ -12,7 +12,6 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.UserInterface;
using osu.Framework.Input.Events;
using osu.Framework.Localisation;
using osu.Game.Graphics.Containers;
using osu.Game.Graphics.Sprites;
@ -131,22 +130,7 @@ namespace osu.Game.Graphics.UserInterface
BackgroundColourSelected = SelectionColour
};
protected override ScrollContainer<Drawable> CreateScrollContainer(Direction direction) => new DropdownScrollContainer(direction);
// Hotfix for https://github.com/ppy/osu/issues/17961
public class DropdownScrollContainer : OsuScrollContainer
{
public DropdownScrollContainer(Direction direction)
: base(direction)
{
}
protected override bool OnMouseDown(MouseDownEvent e)
{
base.OnMouseDown(e);
return true;
}
}
protected override ScrollContainer<Drawable> CreateScrollContainer(Direction direction) => new OsuScrollContainer(direction);
#region DrawableOsuDropdownMenuItem

View File

@ -9,7 +9,6 @@ using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.UserInterface;
using osu.Framework.Input.Events;
using osu.Game.Graphics.Containers;
using osuTK;
@ -82,22 +81,7 @@ namespace osu.Game.Graphics.UserInterface
return new DrawableOsuMenuItem(item);
}
protected override ScrollContainer<Drawable> CreateScrollContainer(Direction direction) => new OsuMenuScrollContainer(direction);
// Hotfix for https://github.com/ppy/osu/issues/17961
public class OsuMenuScrollContainer : OsuScrollContainer
{
public OsuMenuScrollContainer(Direction direction)
: base(direction)
{
}
protected override bool OnMouseDown(MouseDownEvent e)
{
base.OnMouseDown(e);
return true;
}
}
protected override ScrollContainer<Drawable> CreateScrollContainer(Direction direction) => new OsuScrollContainer(direction);
protected override Menu CreateSubMenu() => new OsuMenu(Direction.Vertical)
{

View File

@ -8,6 +8,7 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Graphics.UserInterface;
using osu.Game.Overlays;
using osuTK.Graphics;
namespace osu.Game.Graphics.UserInterfaceV2
{
@ -28,7 +29,8 @@ namespace osu.Game.Graphics.UserInterfaceV2
[BackgroundDependencyLoader(true)]
private void load([CanBeNull] OverlayColourProvider overlayColourProvider, OsuColour colours)
{
BackgroundColour = overlayColourProvider?.Highlight1 ?? colours.Blue3;
if (BackgroundColour == Color4.White)
BackgroundColour = overlayColourProvider?.Highlight1 ?? colours.Blue3;
}
protected override void LoadComplete()

View File

@ -4,6 +4,8 @@
#nullable enable
using osu.Framework.Allocation;
using osu.Framework.Audio;
using osu.Framework.Audio.Sample;
using osu.Framework.Bindables;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
@ -31,6 +33,9 @@ namespace osu.Game.Graphics.UserInterfaceV2
private Color4 enabledColour;
private Color4 disabledColour;
private Sample? sampleChecked;
private Sample? sampleUnchecked;
public SwitchButton()
{
Size = new Vector2(45, 20);
@ -70,13 +75,16 @@ namespace osu.Game.Graphics.UserInterfaceV2
}
[BackgroundDependencyLoader(true)]
private void load(OverlayColourProvider? colourProvider, OsuColour colours)
private void load(OverlayColourProvider? colourProvider, OsuColour colours, AudioManager audio)
{
enabledColour = colourProvider?.Highlight1 ?? colours.BlueDark;
disabledColour = colourProvider?.Background3 ?? colours.Gray3;
switchContainer.Colour = enabledColour;
fill.Colour = disabledColour;
sampleChecked = audio.Samples.Get(@"UI/check-on");
sampleUnchecked = audio.Samples.Get(@"UI/check-off");
}
protected override void LoadComplete()
@ -107,6 +115,16 @@ namespace osu.Game.Graphics.UserInterfaceV2
base.OnHoverLost(e);
}
protected override void OnUserChange(bool value)
{
base.OnUserChange(value);
if (value)
sampleChecked?.Play();
else
sampleUnchecked?.Play();
}
private void updateBorder()
{
circularContainer.TransformBorderTo((Current.Value ? enabledColour : disabledColour).Lighten(IsHovered ? 0.3f : 0));

View File

@ -0,0 +1,54 @@
// 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.Localisation;
namespace osu.Game.Localisation
{
public static class FirstRunSetupBeatmapScreenStrings
{
private const string prefix = @"osu.Game.Resources.Localisation.FirstRunSetupBeatmapScreen";
/// <summary>
/// "Obtaining Beatmaps"
/// </summary>
public static LocalisableString Header => new TranslatableString(getKey(@"header"), @"Obtaining Beatmaps");
/// <summary>
/// "&quot;Beatmaps&quot; are what we call playable levels. osu! doesn&#39;t come with any beatmaps pre-loaded. This step will help you get started on your beatmap collection."
/// </summary>
public static LocalisableString Description => new TranslatableString(getKey(@"description"), @"""Beatmaps"" are what we call playable levels. osu! doesn't come with any beatmaps pre-loaded. This step will help you get started on your beatmap collection.");
/// <summary>
/// "If you are a new player, we recommend playing through the tutorial to get accustomed to the gameplay."
/// </summary>
public static LocalisableString TutorialDescription => new TranslatableString(getKey(@"tutorial_description"), @"If you are a new player, we recommend playing through the tutorial to get accustomed to the gameplay.");
/// <summary>
/// "Get the osu! tutorial"
/// </summary>
public static LocalisableString TutorialButton => new TranslatableString(getKey(@"tutorial_button"), @"Get the osu! tutorial");
/// <summary>
/// "To get you started, we have some recommended beatmaps."
/// </summary>
public static LocalisableString BundledDescription => new TranslatableString(getKey(@"bundled_description"), @"To get you started, we have some recommended beatmaps.");
/// <summary>
/// "Get recommended beatmaps"
/// </summary>
public static LocalisableString BundledButton => new TranslatableString(getKey(@"bundled_button"), @"Get recommended beatmaps");
/// <summary>
/// "You can also obtain more beatmaps from the main menu &quot;browse&quot; button at any time."
/// </summary>
public static LocalisableString ObtainMoreBeatmaps => new TranslatableString(getKey(@"obtain_more_beatmaps"), @"You can also obtain more beatmaps from the main menu ""browse"" button at any time.");
/// <summary>
/// "You currently have {0} beatmap(s) loaded!"
/// </summary>
public static LocalisableString CurrentlyLoadedBeatmaps(int beatmaps) => new TranslatableString(getKey(@"currently_loaded_beatmaps"), @"You currently have {0} beatmap(s) loaded!", beatmaps);
private static string getKey(string key) => $@"{prefix}:{key}";
}
}

View File

@ -17,7 +17,8 @@ namespace osu.Game.Localisation
/// <summary>
/// "Click to resume first-run setup at any point"
/// </summary>
public static LocalisableString ClickToResumeFirstRunSetupAtAnyPoint => new TranslatableString(getKey(@"click_to_resume_first_run_setup_at_any_point"), @"Click to resume first-run setup at any point");
public static LocalisableString ClickToResumeFirstRunSetupAtAnyPoint =>
new TranslatableString(getKey(@"click_to_resume_first_run_setup_at_any_point"), @"Click to resume first-run setup at any point");
/// <summary>
/// "First-run setup"
@ -48,6 +49,31 @@ osu! is a very configurable game, and diving straight into the settings can some
/// </summary>
public static LocalisableString UIScaleDescription => new TranslatableString(getKey(@"ui_scale_description"), @"The size of the osu! user interface can be adjusted to your liking.");
/// <summary>
/// "Behaviour"
/// </summary>
public static LocalisableString Behaviour => new TranslatableString(getKey(@"behaviour"), @"Behaviour");
/// <summary>
/// "Some new defaults for game behaviours have been implemented, with the aim of improving the game experience and making it more accessible to everyone.
///
/// We recommend you give the new defaults a try, but if you&#39;d like to have things feel more like classic versions of osu!, you can easily apply some sane defaults below."
/// </summary>
public static LocalisableString BehaviourDescription => new TranslatableString(getKey(@"behaviour_description"),
@"Some new defaults for game behaviours have been implemented, with the aim of improving the game experience and making it more accessible to everyone.
We recommend you give the new defaults a try, but if you'd like to have things feel more like classic versions of osu!, you can easily apply some sane defaults below.");
/// <summary>
/// "New defaults"
/// </summary>
public static LocalisableString NewDefaults => new TranslatableString(getKey(@"new_defaults"), @"New defaults");
/// <summary>
/// "Classic defaults"
/// </summary>
public static LocalisableString ClassicDefaults => new TranslatableString(getKey(@"classic_defaults"), @"Classic defaults");
private static string getKey(string key) => $@"{prefix}:{key}";
}
}

View File

@ -72,7 +72,8 @@ namespace osu.Game.Overlays.BeatmapListing
Size = new Vector2(12),
Icon = getIconForCardSize(Value)
}
}
},
new HoverClickSounds(HoverSampleSet.TabSelect)
};
}

View File

@ -119,7 +119,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores
maxComboColumn.Text = value.MaxCombo.ToLocalisableString(@"0\x");
ppColumn.Alpha = value.BeatmapInfo.Status.GrantsPerformancePoints() ? 1 : 0;
ppColumn.Text = value.PP?.ToLocalisableString(@"N0");
ppColumn.Text = value.PP?.ToLocalisableString(@"N0") ?? default(LocalisableString);
statisticsColumns.ChildrenEnumerable = value.GetStatisticsForDisplay().Select(createStatisticsColumn);
modsColumn.Mods = value.Mods;

View File

@ -1,10 +1,14 @@
// 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.Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Screens;
using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
using osu.Game.Graphics.Sprites;
using osuTK;
namespace osu.Game.Overlays.FirstRunSetup
@ -15,19 +19,37 @@ namespace osu.Game.Overlays.FirstRunSetup
protected FillFlowContainer Content { get; private set; }
protected FirstRunSetupScreen()
[Resolved]
protected OverlayColourProvider OverlayColourProvider { get; private set; }
[BackgroundDependencyLoader]
private void load()
{
const float header_size = 40;
const float spacing = 20;
InternalChildren = new Drawable[]
{
new OsuScrollContainer(Direction.Vertical)
{
RelativeSizeAxes = Axes.Both,
Child = Content = new FillFlowContainer
ScrollbarOverlapsContent = false,
Children = new Drawable[]
{
Spacing = new Vector2(20),
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Direction = FillDirection.Vertical,
new OsuSpriteText
{
Text = this.GetLocalisableDescription(),
Font = OsuFont.Default.With(size: header_size),
Colour = OverlayColourProvider.Light1,
},
Content = new FillFlowContainer
{
Y = header_size + spacing,
Spacing = new Vector2(spacing),
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Direction = FillDirection.Vertical,
}
},
}
};

View File

@ -0,0 +1,258 @@
// 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;
using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Localisation;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.Drawables;
using osu.Game.Database;
using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
using osu.Game.Graphics.UserInterface;
using osu.Game.Graphics.UserInterfaceV2;
using osu.Game.Localisation;
using osu.Game.Online;
using osuTK;
using Realms;
namespace osu.Game.Overlays.FirstRunSetup
{
[LocalisableDescription(typeof(FirstRunSetupBeatmapScreenStrings), nameof(FirstRunSetupBeatmapScreenStrings.Header))]
public class ScreenBeatmaps : FirstRunSetupScreen
{
private ProgressRoundedButton downloadBundledButton = null!;
private ProgressRoundedButton importBeatmapsButton = null!;
private ProgressRoundedButton downloadTutorialButton = null!;
private OsuTextFlowContainer currentlyLoadedBeatmaps = null!;
private BundledBeatmapDownloader? tutorialDownloader;
private BundledBeatmapDownloader? bundledDownloader;
[Resolved]
private OsuColour colours { get; set; } = null!;
[Resolved]
private RealmAccess realmAccess { get; set; } = null!;
private IDisposable? beatmapSubscription;
[BackgroundDependencyLoader(permitNulls: true)]
private void load(LegacyImportManager? legacyImportManager)
{
Vector2 buttonSize = new Vector2(500, 60);
Content.Children = new Drawable[]
{
new OsuTextFlowContainer(cp => cp.Font = OsuFont.Default.With(size: 20))
{
Colour = OverlayColourProvider.Content1,
Text = FirstRunSetupBeatmapScreenStrings.Description,
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y
},
new Container
{
RelativeSizeAxes = Axes.X,
Height = 30,
Children = new Drawable[]
{
currentlyLoadedBeatmaps = new OsuTextFlowContainer(cp => cp.Font = OsuFont.Default.With(size: 24, weight: FontWeight.SemiBold))
{
Colour = OverlayColourProvider.Content2,
TextAnchor = Anchor.Centre,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
AutoSizeAxes = Axes.Both,
},
}
},
new OsuTextFlowContainer(cp => cp.Font = OsuFont.Default.With(size: 20))
{
Colour = OverlayColourProvider.Content1,
Text = FirstRunSetupBeatmapScreenStrings.TutorialDescription,
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y
},
downloadTutorialButton = new ProgressRoundedButton
{
Size = buttonSize,
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
BackgroundColour = colours.Pink3,
Text = FirstRunSetupBeatmapScreenStrings.TutorialButton,
Action = downloadTutorial
},
new OsuTextFlowContainer(cp => cp.Font = OsuFont.Default.With(size: 20))
{
Colour = OverlayColourProvider.Content1,
Text = FirstRunSetupBeatmapScreenStrings.BundledDescription,
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y
},
downloadBundledButton = new ProgressRoundedButton
{
Size = buttonSize,
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
BackgroundColour = colours.Blue3,
Text = FirstRunSetupBeatmapScreenStrings.BundledButton,
Action = downloadBundled
},
new OsuTextFlowContainer(cp => cp.Font = OsuFont.Default.With(size: 20))
{
Colour = OverlayColourProvider.Content1,
Text = "If you have an existing osu! install, you can also choose to import your existing beatmap collection.",
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y
},
importBeatmapsButton = new ProgressRoundedButton
{
Size = buttonSize,
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
BackgroundColour = colours.Blue3,
Text = MaintenanceSettingsStrings.ImportBeatmapsFromStable,
Action = () =>
{
importBeatmapsButton.Enabled.Value = false;
legacyImportManager?.ImportFromStableAsync(StableContent.Beatmaps).ContinueWith(t => Schedule(() =>
{
if (t.IsCompletedSuccessfully)
importBeatmapsButton.Complete();
else
importBeatmapsButton.Enabled.Value = true;
}));
}
},
new OsuTextFlowContainer(cp => cp.Font = OsuFont.Default.With(size: 20))
{
Colour = OverlayColourProvider.Content1,
Text = FirstRunSetupBeatmapScreenStrings.ObtainMoreBeatmaps,
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y
},
};
}
protected override void LoadComplete()
{
base.LoadComplete();
beatmapSubscription = realmAccess.RegisterForNotifications(r => r.All<BeatmapSetInfo>().Where(s => !s.DeletePending && !s.Protected), beatmapsChanged);
}
protected override void Dispose(bool isDisposing)
{
base.Dispose(isDisposing);
beatmapSubscription?.Dispose();
}
private void beatmapsChanged(IRealmCollection<BeatmapSetInfo> sender, ChangeSet? changes, Exception error)
{
currentlyLoadedBeatmaps.Text = FirstRunSetupBeatmapScreenStrings.CurrentlyLoadedBeatmaps(sender.Count);
if (sender.Count == 0)
{
currentlyLoadedBeatmaps.FadeColour(colours.Red1, 500, Easing.OutQuint);
}
else if (changes != null && (changes.DeletedIndices.Any() || changes.InsertedIndices.Any()))
{
currentlyLoadedBeatmaps.FadeColour(colours.Yellow)
.FadeColour(OverlayColourProvider.Content2, 1500, Easing.OutQuint);
currentlyLoadedBeatmaps.ScaleTo(1.1f)
.ScaleTo(1, 1500, Easing.OutQuint);
}
}
private void downloadTutorial()
{
if (tutorialDownloader != null)
return;
tutorialDownloader = new BundledBeatmapDownloader(true);
AddInternal(tutorialDownloader);
var downloadTracker = tutorialDownloader.DownloadTrackers.First();
downloadTracker.Progress.BindValueChanged(progress =>
{
downloadTutorialButton.SetProgress(progress.NewValue, false);
if (progress.NewValue == 1)
downloadTutorialButton.Complete();
}, true);
}
private void downloadBundled()
{
if (bundledDownloader != null)
return;
bundledDownloader = new BundledBeatmapDownloader(false);
AddInternal(bundledDownloader);
foreach (var tracker in bundledDownloader.DownloadTrackers)
tracker.State.BindValueChanged(_ => updateProgress(), true);
void updateProgress()
{
double progress = (double)bundledDownloader.DownloadTrackers.Count(t => t.State.Value == DownloadState.LocallyAvailable) / bundledDownloader.DownloadTrackers.Count();
if (progress == 1)
downloadBundledButton.Complete();
else
downloadBundledButton.SetProgress(progress, true);
}
}
private class ProgressRoundedButton : RoundedButton
{
[Resolved]
private OsuColour colours { get; set; } = null!;
private ProgressBar progressBar = null!;
protected override void LoadComplete()
{
base.LoadComplete();
Add(progressBar = new ProgressBar(false)
{
RelativeSizeAxes = Axes.Both,
Blending = BlendingParameters.Additive,
FillColour = BackgroundColour,
Alpha = 0.5f,
Depth = float.MinValue
});
}
public void Complete()
{
Enabled.Value = false;
Background.FadeColour(colours.Green, 500, Easing.OutQuint);
progressBar.FillColour = colours.Green;
this.TransformBindableTo(progressBar.Current, 1, 500, Easing.OutQuint);
}
public void SetProgress(double progress, bool animated)
{
if (!Enabled.Value)
return;
this.TransformBindableTo(progressBar.Current, progress, animated ? 500 : 0, Easing.OutQuint);
}
}
}
}

View File

@ -0,0 +1,109 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Localisation;
using osu.Framework.Testing;
using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
using osu.Game.Graphics.UserInterface;
using osu.Game.Localisation;
using osu.Game.Overlays.Settings;
using osu.Game.Overlays.Settings.Sections;
namespace osu.Game.Overlays.FirstRunSetup
{
[LocalisableDescription(typeof(FirstRunSetupOverlayStrings), nameof(FirstRunSetupOverlayStrings.Behaviour))]
public class ScreenBehaviour : FirstRunSetupScreen
{
private SearchContainer<SettingsSection> searchContainer;
[BackgroundDependencyLoader]
private void load()
{
Content.Children = new Drawable[]
{
new OsuTextFlowContainer(cp => cp.Font = OsuFont.Default.With(size: 24))
{
Text = FirstRunSetupOverlayStrings.BehaviourDescription,
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y
},
new GridContainer
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
ColumnDimensions = new[]
{
new Dimension(),
new Dimension(GridSizeMode.Absolute, 10),
new Dimension(),
},
RowDimensions = new[]
{
new Dimension(GridSizeMode.AutoSize),
},
Content = new[]
{
new[]
{
new TriangleButton
{
Anchor = Anchor.TopLeft,
Origin = Anchor.TopLeft,
Text = FirstRunSetupOverlayStrings.NewDefaults,
RelativeSizeAxes = Axes.X,
Action = applyStandard,
},
Empty(),
new DangerousTriangleButton
{
Anchor = Anchor.TopRight,
Origin = Anchor.TopRight,
Text = FirstRunSetupOverlayStrings.ClassicDefaults,
RelativeSizeAxes = Axes.X,
Action = applyClassic
}
},
},
},
searchContainer = new SearchContainer<SettingsSection>
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Children = new SettingsSection[]
{
// This list should be kept in sync with SettingsOverlay.
new GeneralSection(),
new SkinSection(),
// InputSection is intentionally omitted for now due to its sub-panel being a pain to set up.
new UserInterfaceSection(),
new GameplaySection(),
new RulesetSection(),
new AudioSection(),
new GraphicsSection(),
new OnlineSection(),
new MaintenanceSection(),
new DebugSection(),
},
SearchTerm = SettingsItem<bool>.CLASSIC_DEFAULT_SEARCH_TERM,
}
};
}
private void applyClassic()
{
foreach (var i in searchContainer.ChildrenOfType<ISettingsItem>().Where(s => s.HasClassicDefault))
i.ApplyClassicDefault();
}
private void applyStandard()
{
foreach (var i in searchContainer.ChildrenOfType<ISettingsItem>().Where(s => s.HasClassicDefault))
i.ApplyDefault();
}
}
}

View File

@ -27,6 +27,7 @@ using osuTK;
namespace osu.Game.Overlays.FirstRunSetup
{
[LocalisableDescription(typeof(GraphicsSettingsStrings), nameof(GraphicsSettingsStrings.UIScaling))]
public class ScreenUIScale : FirstRunSetupScreen
{
[BackgroundDependencyLoader]

View File

@ -1,16 +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.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Localisation;
using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
using osu.Game.Localisation;
namespace osu.Game.Overlays.FirstRunSetup
{
[LocalisableDescription(typeof(FirstRunSetupOverlayStrings), nameof(FirstRunSetupOverlayStrings.WelcomeTitle))]
public class ScreenWelcome : FirstRunSetupScreen
{
public ScreenWelcome()
[BackgroundDependencyLoader]
private void load()
{
Content.Children = new Drawable[]
{

View File

@ -7,6 +7,7 @@ using System;
using System.Diagnostics;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
@ -56,10 +57,12 @@ namespace osu.Game.Overlays
/// </summary>
public FirstRunSetupScreen? CurrentScreen => (FirstRunSetupScreen?)stack?.CurrentScreen;
private readonly FirstRunStep[] steps =
private readonly Type[] steps =
{
new FirstRunStep(typeof(ScreenWelcome), FirstRunSetupOverlayStrings.WelcomeTitle),
new FirstRunStep(typeof(ScreenUIScale), GraphicsSettingsStrings.UIScaling),
typeof(ScreenWelcome),
typeof(ScreenBeatmaps),
typeof(ScreenUIScale),
typeof(ScreenBehaviour),
};
private Container stackContainer = null!;
@ -81,7 +84,7 @@ namespace osu.Game.Overlays
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding { Horizontal = 50 },
Padding = new MarginPadding { Horizontal = 70 * 1.2f },
Child = new InputBlockingContainer
{
Masking = true,
@ -102,7 +105,7 @@ namespace osu.Game.Overlays
Padding = new MarginPadding
{
Vertical = 20,
Horizontal = 20,
Horizontal = 70,
},
}
},
@ -286,7 +289,7 @@ namespace osu.Game.Overlays
if (currentStepIndex < steps.Length)
{
stack.Push((Screen)Activator.CreateInstance(steps[currentStepIndex.Value].ScreenType));
stack.Push((Screen)Activator.CreateInstance(steps[currentStepIndex.Value]));
}
else
{
@ -317,23 +320,11 @@ namespace osu.Game.Overlays
}
else
{
BackButton.Text = new TranslatableString(@"_", @"{0} ({1})", CommonStrings.Back, steps[currentStepIndex.Value - 1].Description);
BackButton.Text = LocalisableString.Interpolate($@"{CommonStrings.Back} ({steps[currentStepIndex.Value - 1].GetLocalisableDescription()})");
NextButton.Text = isLastStep
? CommonStrings.Finish
: new TranslatableString(@"_", @"{0} ({1})", CommonStrings.Next, steps[currentStepIndex.Value + 1].Description);
}
}
private class FirstRunStep
{
public readonly Type ScreenType;
public readonly LocalisableString Description;
public FirstRunStep(Type screenType, LocalisableString description)
{
ScreenType = screenType;
Description = description;
: LocalisableString.Interpolate($@"{CommonStrings.Next} ({steps[currentStepIndex.Value + 1].GetLocalisableDescription()})");
}
}
}

View File

@ -53,6 +53,9 @@ namespace osu.Game.Overlays.Mods
}
public Bindable<IReadOnlyList<Mod>> SelectedMods = new Bindable<IReadOnlyList<Mod>>(Array.Empty<Mod>());
public Bindable<bool> Active = new BindableBool(true);
protected override bool ReceivePositionalInputAtSubTree(Vector2 screenSpacePos) => base.ReceivePositionalInputAtSubTree(screenSpacePos) && Active.Value;
protected virtual ModPanel CreateModPanel(Mod mod) => new ModPanel(mod);
@ -441,7 +444,7 @@ namespace osu.Game.Overlays.Mods
protected override bool OnKeyDown(KeyDownEvent e)
{
if (e.ControlPressed || e.AltPressed) return false;
if (e.ControlPressed || e.AltPressed || e.SuperPressed) return false;
if (toggleKeys == null) return false;
int index = Array.IndexOf(toggleKeys, e.Key);

View File

@ -13,7 +13,9 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Input.Events;
using osu.Framework.Layout;
using osu.Framework.Utils;
using osu.Game.Configuration;
using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
using osu.Game.Graphics.UserInterface;
using osu.Game.Rulesets.Mods;
@ -59,7 +61,8 @@ namespace osu.Game.Overlays.Mods
private DifficultyMultiplierDisplay? multiplierDisplay;
private ModSettingsArea modSettingsArea = null!;
private FillFlowContainer<ModColumn> columnFlow = null!;
private ColumnScrollContainer columnScroll = null!;
private ColumnFlowContainer columnFlow = null!;
[BackgroundDependencyLoader]
private void load()
@ -95,27 +98,27 @@ namespace osu.Game.Overlays.Mods
RelativePositionAxes = Axes.Both,
Children = new Drawable[]
{
new OsuScrollContainer(Direction.Horizontal)
columnScroll = new ColumnScrollContainer
{
RelativeSizeAxes = Axes.Both,
Masking = false,
ClampExtension = 100,
ScrollbarOverlapsContent = false,
Child = columnFlow = new ModColumnContainer
Child = columnFlow = new ColumnFlowContainer
{
Direction = FillDirection.Horizontal,
Shear = new Vector2(SHEAR, 0),
RelativeSizeAxes = Axes.Y,
AutoSizeAxes = Axes.X,
Spacing = new Vector2(10, 0),
Margin = new MarginPadding { Right = 70 },
Margin = new MarginPadding { Horizontal = 70 },
Children = new[]
{
CreateModColumn(ModType.DifficultyReduction, new[] { Key.Q, Key.W, Key.E, Key.R, Key.T, Key.Y, Key.U, Key.I, Key.O, Key.P }),
CreateModColumn(ModType.DifficultyIncrease, new[] { Key.A, Key.S, Key.D, Key.F, Key.G, Key.H, Key.J, Key.K, Key.L }),
CreateModColumn(ModType.Automation, new[] { Key.Z, Key.X, Key.C, Key.V, Key.B, Key.N, Key.M }),
CreateModColumn(ModType.Conversion),
CreateModColumn(ModType.Fun)
createModColumnContent(ModType.DifficultyReduction, new[] { Key.Q, Key.W, Key.E, Key.R, Key.T, Key.Y, Key.U, Key.I, Key.O, Key.P }),
createModColumnContent(ModType.DifficultyIncrease, new[] { Key.A, Key.S, Key.D, Key.F, Key.G, Key.H, Key.J, Key.K, Key.L }),
createModColumnContent(ModType.Automation, new[] { Key.Z, Key.X, Key.C, Key.V, Key.B, Key.N, Key.M }),
createModColumnContent(ModType.Conversion),
createModColumnContent(ModType.Fun)
}
}
}
@ -153,6 +156,14 @@ namespace osu.Game.Overlays.Mods
}
}
private ColumnDimContainer createModColumnContent(ModType modType, Key[]? toggleKeys = null)
=> new ColumnDimContainer(CreateModColumn(modType, toggleKeys))
{
AutoSizeAxes = Axes.X,
RelativeSizeAxes = Axes.Y,
RequestScroll = column => columnScroll.ScrollIntoView(column, extraScroll: 140)
};
protected override void LoadComplete()
{
base.LoadComplete();
@ -166,7 +177,7 @@ namespace osu.Game.Overlays.Mods
updateSelectionFromBindable();
}, true);
foreach (var column in columnFlow)
foreach (var column in columnFlow.Columns)
{
column.SelectedMods.BindValueChanged(updateBindableFromSelection);
}
@ -191,7 +202,7 @@ namespace osu.Game.Overlays.Mods
private void updateAvailableMods()
{
foreach (var column in columnFlow)
foreach (var column in columnFlow.Columns)
column.Filter = isValidMod;
}
@ -244,7 +255,7 @@ namespace osu.Game.Overlays.Mods
// to synchronise state correctly, updateBindableFromSelection() computes the final mods (including incompatibility rules) and updates SelectedMods,
// and this method then runs unconditionally again to make sure the new visual selection accurately reflects the final set of selected mods.
// selectionBindableSyncInProgress ensures that mutual infinite recursion does not happen after that unconditional call.
foreach (var column in columnFlow)
foreach (var column in columnFlow.Columns)
column.SelectedMods.Value = SelectedMods.Value.Where(mod => mod.Type == column.ModType).ToArray();
}
@ -265,7 +276,7 @@ namespace osu.Game.Overlays.Mods
}
protected virtual IReadOnlyList<Mod> ComputeNewModsFromSelection(IEnumerable<Mod> addedMods, IEnumerable<Mod> removedMods)
=> columnFlow.SelectMany(column => column.SelectedMods.Value).ToArray();
=> columnFlow.Columns.SelectMany(column => column.SelectedMods.Value).ToArray();
protected override void PopIn()
{
@ -280,7 +291,8 @@ namespace osu.Game.Overlays.Mods
for (int i = 0; i < columnFlow.Count; i++)
{
columnFlow[i].TopLevelContent
columnFlow[i].Column
.TopLevelContent
.Delay(i * 30)
.MoveToY(0, fade_in_duration, Easing.OutQuint)
.FadeIn(fade_in_duration, Easing.OutQuint);
@ -301,27 +313,68 @@ namespace osu.Game.Overlays.Mods
{
const float distance = 700;
columnFlow[i].TopLevelContent
columnFlow[i].Column
.TopLevelContent
.MoveToY(i % 2 == 0 ? -distance : distance, fade_out_duration, Easing.OutQuint)
.FadeOut(fade_out_duration, Easing.OutQuint);
}
}
private class ModColumnContainer : FillFlowContainer<ModColumn>
internal class ColumnScrollContainer : OsuScrollContainer<ColumnFlowContainer>
{
public ColumnScrollContainer()
: base(Direction.Horizontal)
{
}
protected override void Update()
{
base.Update();
// the bounds below represent the horizontal range of scroll items to be considered fully visible/active, in the scroll's internal coordinate space.
// note that clamping is applied to the left scroll bound to ensure scrolling past extents does not change the set of active columns.
float leftVisibleBound = Math.Clamp(Current, 0, ScrollableExtent);
float rightVisibleBound = leftVisibleBound + DrawWidth;
// if a movement is occurring at this time, the bounds below represent the full range of columns that the scroll movement will encompass.
// this will be used to ensure that columns do not change state from active to inactive back and forth until they are fully scrolled past.
float leftMovementBound = Math.Min(Current, Target);
float rightMovementBound = Math.Max(Current, Target) + DrawWidth;
foreach (var column in Child)
{
// DrawWidth/DrawPosition do not include shear effects, and we want to know the full extents of the columns post-shear,
// so we have to manually compensate.
var topLeft = column.ToSpaceOfOtherDrawable(Vector2.Zero, ScrollContent);
var bottomRight = column.ToSpaceOfOtherDrawable(new Vector2(column.DrawWidth - column.DrawHeight * SHEAR, 0), ScrollContent);
bool isCurrentlyVisible = Precision.AlmostBigger(topLeft.X, leftVisibleBound)
&& Precision.DefinitelyBigger(rightVisibleBound, bottomRight.X);
bool isBeingScrolledToward = Precision.AlmostBigger(topLeft.X, leftMovementBound)
&& Precision.DefinitelyBigger(rightMovementBound, bottomRight.X);
column.Active.Value = isCurrentlyVisible || isBeingScrolledToward;
}
}
}
internal class ColumnFlowContainer : FillFlowContainer<ColumnDimContainer>
{
public IEnumerable<ModColumn> Columns => Children.Select(dimWrapper => dimWrapper.Column);
private readonly LayoutValue drawSizeLayout = new LayoutValue(Invalidation.DrawSize);
public ModColumnContainer()
public ColumnFlowContainer()
{
AddLayout(drawSizeLayout);
}
public override void Add(ModColumn column)
public override void Add(ColumnDimContainer dimContainer)
{
base.Add(column);
base.Add(dimContainer);
Debug.Assert(column != null);
column.Shear = Vector2.Zero;
Debug.Assert(dimContainer != null);
dimContainer.Column.Shear = Vector2.Zero;
}
protected override void Update()
@ -341,6 +394,63 @@ namespace osu.Game.Overlays.Mods
}
}
internal class ColumnDimContainer : Container
{
public ModColumn Column { get; }
public readonly Bindable<bool> Active = new BindableBool();
public Action<ColumnDimContainer>? RequestScroll { get; set; }
[Resolved]
private OsuColour colours { get; set; } = null!;
public ColumnDimContainer(ModColumn column)
{
Child = Column = column;
column.Active.BindTo(Active);
}
protected override void LoadComplete()
{
base.LoadComplete();
Active.BindValueChanged(_ => updateDim(), true);
FinishTransforms();
}
private void updateDim()
{
Colour4 targetColour;
if (Active.Value)
targetColour = Colour4.White;
else
targetColour = IsHovered ? colours.GrayC : colours.Gray8;
this.FadeColour(targetColour, 800, Easing.OutQuint);
}
protected override bool OnClick(ClickEvent e)
{
if (!Active.Value)
RequestScroll?.Invoke(this);
return true;
}
protected override bool OnHover(HoverEvent e)
{
base.OnHover(e);
updateDim();
return Active.Value;
}
protected override void OnHoverLost(HoverLostEvent e)
{
base.OnHoverLost(e);
updateDim();
}
}
private class ClickToReturnContainer : Container
{
public BindableBool HandleMouse { get; } = new BindableBool();

View File

@ -14,13 +14,13 @@ namespace osu.Game.Overlays.Mods
/// </summary>
public class NestedVerticalScrollContainer : OsuScrollContainer
{
private OsuScrollContainer? parentScrollContainer;
private ModSelectScreen.ColumnScrollContainer? parentScrollContainer;
protected override void LoadComplete()
{
base.LoadComplete();
parentScrollContainer = this.FindClosestParent<OsuScrollContainer>();
parentScrollContainer = this.FindClosestParent<ModSelectScreen.ColumnScrollContainer>();
}
protected override bool OnScroll(ScrollEvent e)

View File

@ -149,7 +149,7 @@ namespace osu.Game.Overlays
}
});
AddInternal(new HoverClickSounds());
AddInternal(new HoverClickSounds(HoverSampleSet.TabSelect));
}
protected override void LoadComplete()

View File

@ -62,7 +62,7 @@ namespace osu.Game.Overlays.Profile.Header.Components
private void updateProgress(APIUser user)
{
levelProgressBar.Length = user?.Statistics?.Level.Progress / 100f ?? 0;
levelProgressText.Text = user?.Statistics?.Level.Progress.ToLocalisableString("0'%'");
levelProgressText.Text = user?.Statistics?.Level.Progress.ToLocalisableString("0'%'") ?? default(LocalisableString);
}
}
}

View File

@ -126,7 +126,7 @@ namespace osu.Game.Overlays.Rankings
startDateColumn.Value = dateToString(response.Spotlight.StartDate);
endDateColumn.Value = dateToString(response.Spotlight.EndDate);
mapCountColumn.Value = response.BeatmapSets.Count.ToLocalisableString(@"N0");
participantsColumn.Value = response.Spotlight.Participants?.ToLocalisableString(@"N0");
participantsColumn.Value = response.Spotlight.Participants?.ToLocalisableString(@"N0") ?? default(LocalisableString);
}
private LocalisableString dateToString(DateTimeOffset date) => date.ToLocalisableString(@"yyyy-MM-dd");

View File

@ -5,6 +5,7 @@ using System.Collections.Generic;
using osu.Framework.Extensions.LocalisationExtensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Localisation;
using osu.Game.Resources.Localisation.Web;
using osu.Game.Users;
@ -24,7 +25,7 @@ namespace osu.Game.Overlays.Rankings.Tables
protected override Drawable[] CreateUniqueContent(UserStatistics item) => new Drawable[]
{
new RowText { Text = item.PP?.ToLocalisableString(@"N0"), }
new RowText { Text = item.PP?.ToLocalisableString(@"N0") ?? default(LocalisableString), }
};
}
}

View File

@ -9,5 +9,20 @@ namespace osu.Game.Overlays.Settings
public interface ISettingsItem : IDrawable, IDisposable
{
event Action SettingChanged;
/// <summary>
/// Whether this setting has a classic default (ie. a different default which better aligns with osu-stable expectations).
/// </summary>
bool HasClassicDefault { get; }
/// <summary>
/// Apply the classic default value of the associated setting. Will throw if <see cref="HasClassicDefault"/> is <c>false</c>.
/// </summary>
void ApplyClassicDefault();
/// <summary>
/// Apply the default value of the associated setting.
/// </summary>
void ApplyDefault();
}
}

View File

@ -28,6 +28,7 @@ namespace osu.Game.Overlays.Settings.Sections.Gameplay
},
new SettingsCheckbox
{
ClassicDefault = false,
LabelText = GameplaySettingsStrings.AlwaysPlayFirstComboBreak,
Current = config.GetBindable<bool>(OsuSetting.AlwaysPlayFirstComboBreak)
}

View File

@ -21,6 +21,7 @@ namespace osu.Game.Overlays.Settings.Sections.Gameplay
{
new SettingsEnumDropdown<ScoringMode>
{
ClassicDefault = ScoringMode.Classic,
LabelText = GameplaySettingsStrings.ScoreDisplayMode,
Current = config.GetBindable<ScoringMode>(OsuSetting.ScoreDisplayMode),
Keywords = new[] { "scoring" }

View File

@ -25,6 +25,7 @@ namespace osu.Game.Overlays.Settings.Sections.Gameplay
},
new SettingsCheckbox
{
ClassicDefault = false,
LabelText = GameplaySettingsStrings.ShowHealthDisplayWhenCantFail,
Current = config.GetBindable<bool>(OsuSetting.ShowHealthDisplayWhenCantFail),
Keywords = new[] { "hp", "bar" }

View File

@ -37,6 +37,7 @@ namespace osu.Game.Overlays.Settings.Sections.UserInterface
},
new SettingsSlider<double, TimeSlider>
{
ClassicDefault = 0,
LabelText = UserInterfaceStrings.HoldToConfirmActivationTime,
Current = config.GetBindable<double>(OsuSetting.UIHoldActivationDelay),
Keywords = new[] { @"delay" },

View File

@ -32,6 +32,7 @@ namespace osu.Game.Overlays.Settings.Sections.UserInterface
{
new SettingsCheckbox
{
ClassicDefault = true,
LabelText = UserInterfaceStrings.RightMouseScroll,
Current = config.GetBindable<bool>(OsuSetting.SongSelectRightMouseScroll),
},

View File

@ -30,6 +30,8 @@ namespace osu.Game.Overlays.Settings
/// </summary>
public object SettingSourceObject { get; internal set; }
public const string CLASSIC_DEFAULT_SEARCH_TERM = @"has-classic-default";
private IHasCurrentValue<T> controlWithCurrent => Control as IHasCurrentValue<T>;
protected override Container<Drawable> Content => FlowContent;
@ -96,7 +98,21 @@ namespace osu.Game.Overlays.Settings
set => controlWithCurrent.Current = value;
}
public virtual IEnumerable<string> FilterTerms => Keywords == null ? new[] { LabelText.ToString() } : new List<string>(Keywords) { LabelText.ToString() }.ToArray();
public virtual IEnumerable<string> FilterTerms
{
get
{
var keywords = new List<string>(Keywords ?? Array.Empty<string>())
{
LabelText.ToString()
};
if (HasClassicDefault)
keywords.Add(CLASSIC_DEFAULT_SEARCH_TERM);
return keywords;
}
}
public IEnumerable<string> Keywords { get; set; }
@ -122,6 +138,32 @@ namespace osu.Game.Overlays.Settings
public event Action SettingChanged;
private T classicDefault;
public bool HasClassicDefault { get; private set; }
/// <summary>
/// A "classic" default value for this setting.
/// </summary>
public T ClassicDefault
{
set
{
classicDefault = value;
HasClassicDefault = true;
}
}
public void ApplyClassicDefault()
{
if (!HasClassicDefault)
throw new InvalidOperationException($"Cannot apply a classic default to a setting which doesn't have one defined via {nameof(ClassicDefault)}.");
Current.Value = classicDefault;
}
public void ApplyDefault() => Current.SetDefault();
protected SettingsItem()
{
RelativeSizeAxes = Axes.X;

View File

@ -2,6 +2,7 @@
// See the LICENCE file in the repository root for full licence text.
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
@ -58,7 +59,7 @@ namespace osu.Game.Overlays.Settings
public bool FilteringActive { get; set; }
[Resolved]
[Resolved(canBeNull: true)]
private SettingsPanel settingsPanel { get; set; }
protected SettingsSection()
@ -131,7 +132,7 @@ namespace osu.Game.Overlays.Settings
},
});
selectedSection = settingsPanel.CurrentSection.GetBoundCopy();
selectedSection = settingsPanel?.CurrentSection.GetBoundCopy() ?? new Bindable<SettingsSection>(this);
selectedSection.BindValueChanged(_ => updateContentFade(), true);
}
@ -152,7 +153,10 @@ namespace osu.Game.Overlays.Settings
protected override bool OnClick(ClickEvent e)
{
if (!isCurrentSection)
{
Debug.Assert(settingsPanel != null);
settingsPanel.SectionsContainer.ScrollTo(this);
}
return base.OnClick(e);
}

View File

@ -23,6 +23,7 @@ namespace osu.Game.Overlays
protected override IEnumerable<SettingsSection> CreateSections() => new SettingsSection[]
{
// This list should be kept in sync with ScreenBehaviour.
new GeneralSection(),
new SkinSection(),
new InputSection(createSubPanel(new KeyBindingPanel())),

View File

@ -109,7 +109,7 @@ namespace osu.Game.Screens.Menu
AutoSizeAxes = Axes.Both,
Children = new Drawable[]
{
logoBounceContainer = new Container
logoBounceContainer = new DragContainer
{
AutoSizeAxes = Axes.Both,
Children = new Drawable[]
@ -402,5 +402,28 @@ namespace osu.Game.Screens.Menu
impactContainer.ScaleTo(0.96f);
impactContainer.ScaleTo(1.12f, 250);
}
private class DragContainer : Container
{
public override bool DragBlocksClick => false;
protected override bool OnDragStart(DragStartEvent e) => true;
protected override void OnDrag(DragEvent e)
{
Vector2 change = e.MousePosition - e.MouseDownPosition;
// Diminish the drag distance as we go further to simulate "rubber band" feeling.
change *= change.Length <= 0 ? 0 : MathF.Pow(change.Length, 0.6f) / change.Length;
this.MoveTo(change);
}
protected override void OnDragEnd(DragEndEvent e)
{
this.MoveTo(Vector2.Zero, 800, Easing.OutElastic);
base.OnDragEnd(e);
}
}
}
}

View File

@ -99,8 +99,8 @@ namespace osu.Game.Screens.Play.PlayerSettings
{
public override LocalisableString TooltipText =>
Current.Value == 0
? new TranslatableString("_", @"{0} ms", base.TooltipText)
: new TranslatableString("_", @"{0} ms {1}", base.TooltipText, getEarlyLateText(Current.Value));
? LocalisableString.Interpolate($@"{base.TooltipText} ms")
: LocalisableString.Interpolate($@"{base.TooltipText} ms {getEarlyLateText(Current.Value)}");
private LocalisableString getEarlyLateText(double value)
{

View File

@ -2,27 +2,36 @@
// See the LICENCE file in the repository root for full licence text.
using System;
using osu.Framework.Utils;
namespace osu.Game.Rulesets.Catch.MathUtils
namespace osu.Game.Utils
{
/// <summary>
/// A PRNG specified in http://heliosphan.org/fastrandom.html.
/// Should only be used to match legacy behaviour. See <see cref="RNG"/> for a newer alternative.
/// </summary>
public class FastRandom
/// <remarks>
/// Known in osu-stable code as `FastRandom`.
/// </remarks>
public class LegacyRandom
{
private const double int_to_real = 1.0 / (int.MaxValue + 1.0);
private const uint int_mask = 0x7FFFFFFF;
private const uint y_initial = 842502087;
private const uint z_initial = 3579807591;
private const uint w_initial = 273326509;
private uint x, y = y_initial, z = z_initial, w = w_initial;
private const uint y = 842502087;
private const uint z = 3579807591;
private const uint w = 273326509;
public FastRandom(int seed)
public uint X { get; private set; }
public uint Y { get; private set; } = y;
public uint Z { get; private set; } = z;
public uint W { get; private set; } = w;
public LegacyRandom(int seed)
{
x = (uint)seed;
X = (uint)seed;
}
public FastRandom()
public LegacyRandom()
: this(Environment.TickCount)
{
}
@ -33,11 +42,11 @@ namespace osu.Game.Rulesets.Catch.MathUtils
/// <returns>The random value.</returns>
public uint NextUInt()
{
uint t = x ^ (x << 11);
x = y;
y = z;
z = w;
return w = w ^ (w >> 19) ^ t ^ (t >> 8);
uint t = X ^ (X << 11);
X = Y;
Y = Z;
Z = W;
return W = W ^ (W >> 19) ^ t ^ (t >> 8);
}
/// <summary>

View File

@ -35,7 +35,7 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Realm" Version="10.10.0" />
<PackageReference Include="ppy.osu.Framework" Version="2022.423.0" />
<PackageReference Include="ppy.osu.Framework" Version="2022.428.0" />
<PackageReference Include="ppy.osu.Game.Resources" Version="2022.422.0" />
<PackageReference Include="Sentry" Version="3.14.1" />
<PackageReference Include="SharpCompress" Version="0.30.1" />

View File

@ -61,7 +61,7 @@
<Reference Include="System.Net.Http" />
</ItemGroup>
<ItemGroup Label="Package References">
<PackageReference Include="ppy.osu.Framework.iOS" Version="2022.423.0" />
<PackageReference Include="ppy.osu.Framework.iOS" Version="2022.428.0" />
<PackageReference Include="ppy.osu.Game.Resources" Version="2022.422.0" />
</ItemGroup>
<!-- See https://github.com/dotnet/runtime/issues/35988 (can be removed after Xamarin uses net6.0) -->
@ -84,7 +84,7 @@
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="5.0.14" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="5.0.14" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
<PackageReference Include="ppy.osu.Framework" Version="2022.423.0" />
<PackageReference Include="ppy.osu.Framework" Version="2022.428.0" />
<PackageReference Include="SharpCompress" Version="0.30.1" />
<PackageReference Include="NUnit" Version="3.13.2" />
<PackageReference Include="System.ComponentModel.Annotations" Version="5.0.0" />