diff --git a/README.md b/README.md
index c4d676f4be..56491a4be4 100644
--- a/README.md
+++ b/README.md
@@ -19,8 +19,9 @@ Detailed changelogs are published on the [official osu! site](https://osu.ppy.sh
## Requirements
- A desktop platform with the [.NET Core SDK 2.2](https://www.microsoft.com/net/learn/get-started) or higher installed.
+- When running on linux, please have a system-wide ffmpeg installation available to support video decoding.
+- When running on Windows 7 or 8.1, **[additional prerequisites](https://docs.microsoft.com/en-us/dotnet/core/windows-prerequisites?tabs=netcore2x)** may be required to correctly run .NET Core applications if your operating system is not up-to-date with the latest service packs.
- When working with the codebase, we recommend using an IDE with intellisense and syntax highlighting, such as [Visual Studio 2017+](https://visualstudio.microsoft.com/vs/), [Jetbrains Rider](https://www.jetbrains.com/rider/) or [Visual Studio Code](https://code.visualstudio.com/).
-- Note that there are **[additional requirements for Windows 7 and Windows 8.1](https://docs.microsoft.com/en-us/dotnet/core/windows-prerequisites?tabs=netcore2x)** which you may need to manually install if your operating system is not up-to-date.
## Running osu!
diff --git a/osu.Android.props b/osu.Android.props
index 3b2e6574ac..721d341c08 100644
--- a/osu.Android.props
+++ b/osu.Android.props
@@ -62,7 +62,7 @@
-
+
diff --git a/osu.Game.Tests/Visual/Menus/TestSceneIntroTriangles.cs b/osu.Game.Tests/Visual/Menus/TestSceneIntroTriangles.cs
new file mode 100644
index 0000000000..df79584167
--- /dev/null
+++ b/osu.Game.Tests/Visual/Menus/TestSceneIntroTriangles.cs
@@ -0,0 +1,15 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using NUnit.Framework;
+using osu.Framework.Screens;
+using osu.Game.Screens.Menu;
+
+namespace osu.Game.Tests.Visual.Menus
+{
+ [TestFixture]
+ public class TestSceneIntroTriangles : IntroTestScene
+ {
+ protected override IScreen CreateScreen() => new IntroTriangles();
+ }
+}
diff --git a/osu.Game.Tests/Visual/Online/TestSceneLeaderboardScopeSelector.cs b/osu.Game.Tests/Visual/Online/TestSceneLeaderboardScopeSelector.cs
new file mode 100644
index 0000000000..cc3b2ac68b
--- /dev/null
+++ b/osu.Game.Tests/Visual/Online/TestSceneLeaderboardScopeSelector.cs
@@ -0,0 +1,36 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Game.Overlays.BeatmapSet;
+using System;
+using System.Collections.Generic;
+using osu.Framework.Graphics;
+using osu.Framework.Bindables;
+using osu.Game.Screens.Select.Leaderboards;
+
+namespace osu.Game.Tests.Visual.Online
+{
+ public class TestSceneLeaderboardScopeSelector : OsuTestScene
+ {
+ public override IReadOnlyList RequiredTypes => new[]
+ {
+ typeof(LeaderboardScopeSelector),
+ };
+
+ public TestSceneLeaderboardScopeSelector()
+ {
+ Bindable scope = new Bindable();
+
+ Add(new LeaderboardScopeSelector
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Current = { BindTarget = scope }
+ });
+
+ AddStep(@"Select global", () => scope.Value = BeatmapLeaderboardScope.Global);
+ AddStep(@"Select country", () => scope.Value = BeatmapLeaderboardScope.Country);
+ AddStep(@"Select friend", () => scope.Value = BeatmapLeaderboardScope.Friend);
+ }
+ }
+}
diff --git a/osu.Game.Tests/Visual/Online/TestSceneUserProfilePreviousUsernames.cs b/osu.Game.Tests/Visual/Online/TestSceneUserProfilePreviousUsernames.cs
new file mode 100644
index 0000000000..d09a50b12c
--- /dev/null
+++ b/osu.Game.Tests/Visual/Online/TestSceneUserProfilePreviousUsernames.cs
@@ -0,0 +1,69 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using System.Collections.Generic;
+using NUnit.Framework;
+using osu.Framework.Allocation;
+using osu.Framework.Bindables;
+using osu.Framework.Graphics;
+using osu.Game.Online.API;
+using osu.Game.Online.API.Requests;
+using osu.Game.Overlays.Profile.Header.Components;
+using osu.Game.Users;
+
+namespace osu.Game.Tests.Visual.Online
+{
+ [TestFixture]
+ public class TestSceneUserProfilePreviousUsernames : OsuTestScene
+ {
+ public override IReadOnlyList RequiredTypes => new[]
+ {
+ typeof(PreviousUsernames)
+ };
+
+ [Resolved]
+ private IAPIProvider api { get; set; }
+
+ private readonly Bindable user = new Bindable();
+
+ public TestSceneUserProfilePreviousUsernames()
+ {
+ Child = new PreviousUsernames
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ User = { BindTarget = user },
+ };
+
+ User[] users =
+ {
+ new User { PreviousUsernames = new[] { "username1" } },
+ new User { PreviousUsernames = new[] { "longusername", "longerusername" } },
+ new User { PreviousUsernames = new[] { "test", "angelsim", "verylongusername" } },
+ new User { PreviousUsernames = new[] { "ihavenoidea", "howcani", "makethistext", "anylonger" } },
+ new User { PreviousUsernames = new string[0] },
+ null
+ };
+
+ AddStep("single username", () => user.Value = users[0]);
+ AddStep("two usernames", () => user.Value = users[1]);
+ AddStep("three usernames", () => user.Value = users[2]);
+ AddStep("four usernames", () => user.Value = users[3]);
+ AddStep("no username", () => user.Value = users[4]);
+ AddStep("null user", () => user.Value = users[5]);
+ }
+
+ protected override void LoadComplete()
+ {
+ base.LoadComplete();
+
+ AddStep("online user (Angelsim)", () =>
+ {
+ var request = new GetUserRequest(1777162);
+ request.Success += user => this.user.Value = user;
+ api.Queue(request);
+ });
+ }
+ }
+}
diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneOnScreenDisplay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneOnScreenDisplay.cs
index d900526c07..45720548c8 100644
--- a/osu.Game.Tests/Visual/UserInterface/TestSceneOnScreenDisplay.cs
+++ b/osu.Game.Tests/Visual/UserInterface/TestSceneOnScreenDisplay.cs
@@ -7,6 +7,7 @@ using osu.Framework.Configuration;
using osu.Framework.Configuration.Tracking;
using osu.Framework.Graphics;
using osu.Game.Overlays;
+using osu.Game.Overlays.OSD;
namespace osu.Game.Tests.Visual.UserInterface
{
@@ -22,6 +23,12 @@ namespace osu.Game.Tests.Visual.UserInterface
osd.BeginTracking(this, config);
Add(osd);
+ AddStep("Display empty osd toast", () => osd.Display(new EmptyToast()));
+ AddAssert("Toast width is 240", () => osd.Child.Width == 240);
+
+ AddStep("Display toast with lengthy text", () => osd.Display(new LengthyToast()));
+ AddAssert("Toast width is greater than 240", () => osd.Child.Width > 240);
+
AddRepeatStep("Change toggle (no bind)", () => config.ToggleSetting(TestConfigSetting.ToggleSettingNoKeybind), 2);
AddRepeatStep("Change toggle (with bind)", () => config.ToggleSetting(TestConfigSetting.ToggleSettingWithKeybind), 2);
AddRepeatStep("Change enum (no bind)", () => config.IncrementEnumSetting(TestConfigSetting.EnumSettingNoKeybind), 3);
@@ -86,6 +93,22 @@ namespace osu.Game.Tests.Visual.UserInterface
Setting4
}
+ private class EmptyToast : Toast
+ {
+ public EmptyToast()
+ : base("", "", "")
+ {
+ }
+ }
+
+ private class LengthyToast : Toast
+ {
+ public LengthyToast()
+ : base("Toast with a very very very long text", "A very very very very very very long text also", "A very very very very very long shortcut")
+ {
+ }
+ }
+
private class TestOnScreenDisplay : OnScreenDisplay
{
protected override void DisplayTemporarily(Drawable toDisplay) => toDisplay.FadeIn().ResizeHeightTo(110);
diff --git a/osu.Game/Configuration/IntroSequence.cs b/osu.Game/Configuration/IntroSequence.cs
new file mode 100644
index 0000000000..1eb953be36
--- /dev/null
+++ b/osu.Game/Configuration/IntroSequence.cs
@@ -0,0 +1,11 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+namespace osu.Game.Configuration
+{
+ public enum IntroSequence
+ {
+ Circles,
+ Triangles
+ }
+}
diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs
index 1da7c7ec1d..19f46c1d6a 100644
--- a/osu.Game/Configuration/OsuConfigManager.cs
+++ b/osu.Game/Configuration/OsuConfigManager.cs
@@ -105,6 +105,8 @@ namespace osu.Game.Configuration
Set(OsuSetting.ScalingPositionY, 0.5f, 0f, 1f);
Set(OsuSetting.UIScale, 1f, 0.8f, 1.6f, 0.01f);
+
+ Set(OsuSetting.IntroSequence, IntroSequence.Triangles);
}
public OsuConfigManager(Storage storage)
@@ -167,6 +169,7 @@ namespace osu.Game.Configuration
ScalingPositionY,
ScalingSizeX,
ScalingSizeY,
- UIScale
+ UIScale,
+ IntroSequence
}
}
diff --git a/osu.Game/Graphics/UserInterface/OsuTabControl.cs b/osu.Game/Graphics/UserInterface/OsuTabControl.cs
index 11f41b1a48..c55d14456b 100644
--- a/osu.Game/Graphics/UserInterface/OsuTabControl.cs
+++ b/osu.Game/Graphics/UserInterface/OsuTabControl.cs
@@ -31,6 +31,11 @@ namespace osu.Game.Graphics.UserInterface
protected virtual float StripWidth() => TabContainer.Children.Sum(c => c.IsPresent ? c.DrawWidth + TabContainer.Spacing.X : 0) - TabContainer.Spacing.X;
protected virtual float StripHeight() => 1;
+ ///
+ /// Whether entries should be automatically populated if is an type.
+ ///
+ protected virtual bool AddEnumEntriesAutomatically => true;
+
private static bool isEnumType => typeof(T).IsEnum;
public OsuTabControl()
@@ -45,7 +50,7 @@ namespace osu.Game.Graphics.UserInterface
Colour = Color4.White.Opacity(0),
});
- if (isEnumType)
+ if (isEnumType && AddEnumEntriesAutomatically)
foreach (var val in (T[])Enum.GetValues(typeof(T)))
AddItem(val);
}
diff --git a/osu.Game/Graphics/UserInterface/PageTabControl.cs b/osu.Game/Graphics/UserInterface/PageTabControl.cs
index 156a556b5e..a0d3745180 100644
--- a/osu.Game/Graphics/UserInterface/PageTabControl.cs
+++ b/osu.Game/Graphics/UserInterface/PageTabControl.cs
@@ -24,7 +24,13 @@ namespace osu.Game.Graphics.UserInterface
Height = 30;
}
- public class PageTabItem : TabItem
+ [BackgroundDependencyLoader]
+ private void load(OsuColour colours)
+ {
+ AccentColour = colours.Yellow;
+ }
+
+ public class PageTabItem : TabItem, IHasAccentColour
{
private const float transition_duration = 100;
@@ -32,6 +38,18 @@ namespace osu.Game.Graphics.UserInterface
protected readonly SpriteText Text;
+ private Color4 accentColour;
+
+ public Color4 AccentColour
+ {
+ get => accentColour;
+ set
+ {
+ accentColour = value;
+ box.Colour = accentColour;
+ }
+ }
+
public PageTabItem(T value)
: base(value)
{
@@ -63,12 +81,6 @@ namespace osu.Game.Graphics.UserInterface
Active.BindValueChanged(active => Text.Font = Text.Font.With(Typeface.Exo, weight: active.NewValue ? FontWeight.Bold : FontWeight.Medium), true);
}
- [BackgroundDependencyLoader]
- private void load(OsuColour colours)
- {
- box.Colour = colours.Yellow;
- }
-
protected override bool OnHover(HoverEvent e)
{
if (!Active.Value)
diff --git a/osu.Game/Overlays/BeatmapSet/LeaderboardScopeSelector.cs b/osu.Game/Overlays/BeatmapSet/LeaderboardScopeSelector.cs
new file mode 100644
index 0000000000..dcd58db427
--- /dev/null
+++ b/osu.Game/Overlays/BeatmapSet/LeaderboardScopeSelector.cs
@@ -0,0 +1,119 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Framework.Graphics.UserInterface;
+using osu.Game.Screens.Select.Leaderboards;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osuTK;
+using osu.Game.Graphics.UserInterface;
+using osu.Framework.Graphics.Shapes;
+using osu.Game.Graphics;
+using osu.Framework.Allocation;
+using osuTK.Graphics;
+using osu.Framework.Graphics.Colour;
+using osu.Framework.Input.Events;
+
+namespace osu.Game.Overlays.BeatmapSet
+{
+ public class LeaderboardScopeSelector : PageTabControl
+ {
+ protected override bool AddEnumEntriesAutomatically => false;
+
+ protected override Dropdown CreateDropdown() => null;
+
+ protected override TabItem CreateTabItem(BeatmapLeaderboardScope value) => new ScopeSelectorTabItem(value);
+
+ public LeaderboardScopeSelector()
+ {
+ RelativeSizeAxes = Axes.X;
+
+ AddItem(BeatmapLeaderboardScope.Global);
+ AddItem(BeatmapLeaderboardScope.Country);
+ AddItem(BeatmapLeaderboardScope.Friend);
+
+ AddInternal(new GradientLine
+ {
+ Anchor = Anchor.BottomCentre,
+ Origin = Anchor.BottomCentre,
+ });
+ }
+
+ [BackgroundDependencyLoader]
+ private void load(OsuColour colours)
+ {
+ AccentColour = colours.Blue;
+ }
+
+ protected override TabFillFlowContainer CreateTabFlow() => new TabFillFlowContainer
+ {
+ Anchor = Anchor.BottomCentre,
+ Origin = Anchor.BottomCentre,
+ AutoSizeAxes = Axes.X,
+ RelativeSizeAxes = Axes.Y,
+ Direction = FillDirection.Horizontal,
+ Spacing = new Vector2(20, 0),
+ };
+
+ private class ScopeSelectorTabItem : PageTabItem
+ {
+ public ScopeSelectorTabItem(BeatmapLeaderboardScope value)
+ : base(value)
+ {
+ Text.Font = OsuFont.GetFont(size: 16);
+ }
+
+ protected override bool OnHover(HoverEvent e)
+ {
+ Text.FadeColour(AccentColour);
+
+ return base.OnHover(e);
+ }
+
+ protected override void OnHoverLost(HoverLostEvent e)
+ {
+ base.OnHoverLost(e);
+
+ Text.FadeColour(Color4.White);
+ }
+ }
+
+ private class GradientLine : GridContainer
+ {
+ public GradientLine()
+ {
+ RelativeSizeAxes = Axes.X;
+ Size = new Vector2(0.8f, 1.5f);
+
+ ColumnDimensions = new[]
+ {
+ new Dimension(),
+ new Dimension(mode: GridSizeMode.Relative, size: 0.4f),
+ new Dimension(),
+ };
+
+ Content = new[]
+ {
+ new Drawable[]
+ {
+ new Box
+ {
+ RelativeSizeAxes = Axes.Both,
+ Colour = ColourInfo.GradientHorizontal(Color4.Transparent, Color4.Gray),
+ },
+ new Box
+ {
+ RelativeSizeAxes = Axes.Both,
+ Colour = Color4.Gray,
+ },
+ new Box
+ {
+ RelativeSizeAxes = Axes.Both,
+ Colour = ColourInfo.GradientHorizontal(Color4.Gray, Color4.Transparent),
+ },
+ }
+ };
+ }
+ }
+ }
+}
diff --git a/osu.Game/Overlays/OSD/Toast.cs b/osu.Game/Overlays/OSD/Toast.cs
new file mode 100644
index 0000000000..46c53ec409
--- /dev/null
+++ b/osu.Game/Overlays/OSD/Toast.cs
@@ -0,0 +1,84 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Shapes;
+using osu.Game.Graphics;
+using osu.Game.Graphics.Sprites;
+using osuTK;
+using osuTK.Graphics;
+
+namespace osu.Game.Overlays.OSD
+{
+ public abstract class Toast : Container
+ {
+ private const int toast_minimum_width = 240;
+
+ private readonly Container content;
+ protected override Container Content => content;
+
+ protected readonly OsuSpriteText ValueText;
+
+ protected Toast(string description, string value, string shortcut)
+ {
+ Anchor = Anchor.Centre;
+ Origin = Anchor.Centre;
+
+ // A toast's height is decided (and transformed) by the containing OnScreenDisplay.
+ RelativeSizeAxes = Axes.Y;
+ AutoSizeAxes = Axes.X;
+
+ InternalChildren = new Drawable[]
+ {
+ new Container //this container exists just to set a minimum width for the toast
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Width = toast_minimum_width
+ },
+ new Box
+ {
+ RelativeSizeAxes = Axes.Both,
+ Colour = Color4.Black,
+ Alpha = 0.7f
+ },
+ content = new Container
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ RelativeSizeAxes = Axes.Both,
+ },
+ new OsuSpriteText
+ {
+ Padding = new MarginPadding(10),
+ Name = "Description",
+ Font = OsuFont.GetFont(size: 14, weight: FontWeight.Black),
+ Spacing = new Vector2(1, 0),
+ Anchor = Anchor.TopCentre,
+ Origin = Anchor.TopCentre,
+ Text = description.ToUpperInvariant()
+ },
+ ValueText = new OsuSpriteText
+ {
+ Font = OsuFont.GetFont(size: 24, weight: FontWeight.Light),
+ Padding = new MarginPadding { Left = 10, Right = 10 },
+ Name = "Value",
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Text = value
+ },
+ new OsuSpriteText
+ {
+ Anchor = Anchor.BottomCentre,
+ Origin = Anchor.BottomCentre,
+ Name = "Shortcut",
+ Alpha = 0.3f,
+ Margin = new MarginPadding { Bottom = 15 },
+ Font = OsuFont.GetFont(size: 12, weight: FontWeight.Bold),
+ Text = string.IsNullOrEmpty(shortcut) ? "NO KEY BOUND" : shortcut.ToUpperInvariant()
+ },
+ };
+ }
+ }
+}
diff --git a/osu.Game/Overlays/OSD/TrackedSettingToast.cs b/osu.Game/Overlays/OSD/TrackedSettingToast.cs
new file mode 100644
index 0000000000..8e8a99a0a7
--- /dev/null
+++ b/osu.Game/Overlays/OSD/TrackedSettingToast.cs
@@ -0,0 +1,147 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using osu.Framework.Allocation;
+using osu.Framework.Configuration.Tracking;
+using osu.Framework.Extensions.Color4Extensions;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Effects;
+using osu.Framework.Graphics.Shapes;
+using osu.Game.Graphics;
+using osuTK;
+using osuTK.Graphics;
+
+namespace osu.Game.Overlays.OSD
+{
+ public class TrackedSettingToast : Toast
+ {
+ private const int lights_bottom_margin = 40;
+
+ public TrackedSettingToast(SettingDescription description)
+ : base(description.Name, description.Value, description.Shortcut)
+ {
+ FillFlowContainer optionLights;
+
+ Children = new Drawable[]
+ {
+ new Container
+ {
+ Anchor = Anchor.BottomCentre,
+ Origin = Anchor.BottomCentre,
+ Margin = new MarginPadding { Bottom = lights_bottom_margin },
+ Children = new Drawable[]
+ {
+ optionLights = new FillFlowContainer
+ {
+ Margin = new MarginPadding { Bottom = 5 },
+ Spacing = new Vector2(5, 0),
+ Direction = FillDirection.Horizontal,
+ Anchor = Anchor.TopCentre,
+ Origin = Anchor.TopCentre,
+ AutoSizeAxes = Axes.Both
+ },
+ }
+ }
+ };
+
+ int optionCount = 0;
+ int selectedOption = -1;
+
+ switch (description.RawValue)
+ {
+ case bool val:
+ optionCount = 1;
+ if (val) selectedOption = 0;
+ break;
+
+ case Enum _:
+ var values = Enum.GetValues(description.RawValue.GetType());
+ optionCount = values.Length;
+ selectedOption = Convert.ToInt32(description.RawValue);
+ break;
+ }
+
+ ValueText.Origin = optionCount > 0 ? Anchor.BottomCentre : Anchor.Centre;
+
+ for (int i = 0; i < optionCount; i++)
+ optionLights.Add(new OptionLight { Glowing = i == selectedOption });
+ }
+
+ private class OptionLight : Container
+ {
+ private Color4 glowingColour, idleColour;
+
+ private const float transition_speed = 300;
+
+ private const float glow_strength = 0.4f;
+
+ private readonly Box fill;
+
+ public OptionLight()
+ {
+ Children = new[]
+ {
+ fill = new Box
+ {
+ RelativeSizeAxes = Axes.Both,
+ Alpha = 1,
+ },
+ };
+ }
+
+ private bool glowing;
+
+ public bool Glowing
+ {
+ set
+ {
+ glowing = value;
+ if (!IsLoaded) return;
+
+ updateGlow();
+ }
+ }
+
+ [BackgroundDependencyLoader]
+ private void load(OsuColour colours)
+ {
+ fill.Colour = idleColour = Color4.White.Opacity(0.4f);
+ glowingColour = Color4.White;
+
+ Size = new Vector2(25, 5);
+
+ Masking = true;
+ CornerRadius = 3;
+
+ EdgeEffect = new EdgeEffectParameters
+ {
+ Colour = colours.BlueDark.Opacity(glow_strength),
+ Type = EdgeEffectType.Glow,
+ Radius = 8,
+ };
+ }
+
+ protected override void LoadComplete()
+ {
+ updateGlow();
+ FinishTransforms(true);
+ }
+
+ private void updateGlow()
+ {
+ if (glowing)
+ {
+ fill.FadeColour(glowingColour, transition_speed, Easing.OutQuint);
+ FadeEdgeEffectTo(glow_strength, transition_speed, Easing.OutQuint);
+ }
+ else
+ {
+ FadeEdgeEffectTo(0, transition_speed, Easing.OutQuint);
+ fill.FadeColour(idleColour, transition_speed, Easing.OutQuint);
+ }
+ }
+ }
+ }
+}
diff --git a/osu.Game/Overlays/OnScreenDisplay.cs b/osu.Game/Overlays/OnScreenDisplay.cs
index 88a1edddc5..a92320945e 100644
--- a/osu.Game/Overlays/OnScreenDisplay.cs
+++ b/osu.Game/Overlays/OnScreenDisplay.cs
@@ -8,34 +8,25 @@ using osu.Framework.Configuration;
using osu.Framework.Configuration.Tracking;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
-using osu.Framework.Graphics.Shapes;
-using osu.Framework.Graphics.Sprites;
-using osu.Game.Graphics;
using osuTK;
-using osuTK.Graphics;
-using osu.Framework.Extensions.Color4Extensions;
-using osu.Framework.Graphics.Effects;
using osu.Framework.Graphics.Transforms;
using osu.Framework.Threading;
using osu.Game.Configuration;
-using osu.Game.Graphics.Sprites;
+using osu.Game.Overlays.OSD;
namespace osu.Game.Overlays
{
+ ///
+ /// An on-screen display which automatically tracks and displays toast notifications for .
+ /// Can also display custom content via
+ ///
public class OnScreenDisplay : Container
{
private readonly Container box;
- private readonly SpriteText textLine1;
- private readonly SpriteText textLine2;
- private readonly SpriteText textLine3;
-
private const float height = 110;
- private const float height_notext = 98;
private const float height_contracted = height * 0.9f;
- private readonly FillFlowContainer optionLights;
-
public OnScreenDisplay()
{
RelativeSizeAxes = Axes.Both;
@@ -52,64 +43,6 @@ namespace osu.Game.Overlays
Height = height_contracted,
Alpha = 0,
CornerRadius = 20,
- Children = new Drawable[]
- {
- new Box
- {
- RelativeSizeAxes = Axes.Both,
- Colour = Color4.Black,
- Alpha = 0.7f,
- },
- new Container // purely to add a minimum width
- {
- Anchor = Anchor.Centre,
- Origin = Anchor.Centre,
- Width = 240,
- RelativeSizeAxes = Axes.Y,
- },
- textLine1 = new OsuSpriteText
- {
- Padding = new MarginPadding(10),
- Font = OsuFont.GetFont(size: 14, weight: FontWeight.Black),
- Spacing = new Vector2(1, 0),
- Anchor = Anchor.TopCentre,
- Origin = Anchor.TopCentre,
- },
- textLine2 = new OsuSpriteText
- {
- Font = OsuFont.GetFont(size: 24, weight: FontWeight.Light),
- Padding = new MarginPadding { Left = 10, Right = 10 },
- Anchor = Anchor.Centre,
- Origin = Anchor.BottomCentre,
- },
- new FillFlowContainer
- {
- Anchor = Anchor.BottomCentre,
- Origin = Anchor.BottomCentre,
- AutoSizeAxes = Axes.Both,
- Direction = FillDirection.Vertical,
- Children = new Drawable[]
- {
- optionLights = new FillFlowContainer
- {
- Padding = new MarginPadding { Top = 20, Bottom = 5 },
- Spacing = new Vector2(5, 0),
- Direction = FillDirection.Horizontal,
- Anchor = Anchor.TopCentre,
- Origin = Anchor.TopCentre,
- AutoSizeAxes = Axes.Both
- },
- textLine3 = new OsuSpriteText
- {
- Anchor = Anchor.TopCentre,
- Origin = Anchor.TopCentre,
- Margin = new MarginPadding { Bottom = 15 },
- Font = OsuFont.GetFont(size: 12, weight: FontWeight.Bold),
- Alpha = 0.3f,
- },
- }
- }
- }
},
};
}
@@ -142,7 +75,7 @@ namespace osu.Game.Overlays
return;
configManager.LoadInto(trackedSettings);
- trackedSettings.SettingChanged += display;
+ trackedSettings.SettingChanged += displayTrackedSettingChange;
trackedConfigManagers.Add((source, configManager), trackedSettings);
}
@@ -162,56 +95,23 @@ namespace osu.Game.Overlays
return;
existing.Unload();
- existing.SettingChanged -= display;
+ existing.SettingChanged -= displayTrackedSettingChange;
trackedConfigManagers.Remove((source, configManager));
}
- private void display(SettingDescription description)
+ ///
+ /// Displays the provided temporarily.
+ ///
+ ///
+ public void Display(Toast toast)
{
- Schedule(() =>
- {
- textLine1.Text = description.Name.ToUpperInvariant();
- textLine2.Text = description.Value;
- textLine3.Text = description.Shortcut.ToUpperInvariant();
-
- if (string.IsNullOrEmpty(textLine3.Text))
- textLine3.Text = "NO KEY BOUND";
-
- DisplayTemporarily(box);
-
- int optionCount = 0;
- int selectedOption = -1;
-
- switch (description.RawValue)
- {
- case bool val:
- optionCount = 1;
- if (val) selectedOption = 0;
- break;
-
- case Enum _:
- var values = Enum.GetValues(description.RawValue.GetType());
- optionCount = values.Length;
- selectedOption = Convert.ToInt32(description.RawValue);
- break;
- }
-
- textLine2.Origin = optionCount > 0 ? Anchor.BottomCentre : Anchor.Centre;
- textLine2.Y = optionCount > 0 ? 0 : 5;
-
- if (optionLights.Children.Count != optionCount)
- {
- optionLights.Clear();
- for (int i = 0; i < optionCount; i++)
- optionLights.Add(new OptionLight());
- }
-
- for (int i = 0; i < optionCount; i++)
- optionLights.Children[i].Glowing = i == selectedOption;
- });
+ box.Child = toast;
+ DisplayTemporarily(box);
}
+ private void displayTrackedSettingChange(SettingDescription description) => Schedule(() => Display(new TrackedSettingToast(description)));
+
private TransformSequence fadeIn;
private ScheduledDelegate fadeOut;
@@ -236,80 +136,5 @@ namespace osu.Game.Overlays
b => b.ResizeHeightTo(height_contracted, 1500, Easing.InQuint));
}, 500);
}
-
- private class OptionLight : Container
- {
- private Color4 glowingColour, idleColour;
-
- private const float transition_speed = 300;
-
- private const float glow_strength = 0.4f;
-
- private readonly Box fill;
-
- public OptionLight()
- {
- Children = new[]
- {
- fill = new Box
- {
- RelativeSizeAxes = Axes.Both,
- Alpha = 1,
- },
- };
- }
-
- private bool glowing;
-
- public bool Glowing
- {
- set
- {
- glowing = value;
- if (!IsLoaded) return;
-
- updateGlow();
- }
- }
-
- private void updateGlow()
- {
- if (glowing)
- {
- fill.FadeColour(glowingColour, transition_speed, Easing.OutQuint);
- FadeEdgeEffectTo(glow_strength, transition_speed, Easing.OutQuint);
- }
- else
- {
- FadeEdgeEffectTo(0, transition_speed, Easing.OutQuint);
- fill.FadeColour(idleColour, transition_speed, Easing.OutQuint);
- }
- }
-
- [BackgroundDependencyLoader]
- private void load(OsuColour colours)
- {
- fill.Colour = idleColour = Color4.White.Opacity(0.4f);
- glowingColour = Color4.White;
-
- Size = new Vector2(25, 5);
-
- Masking = true;
- CornerRadius = 3;
-
- EdgeEffect = new EdgeEffectParameters
- {
- Colour = colours.BlueDark.Opacity(glow_strength),
- Type = EdgeEffectType.Glow,
- Radius = 8,
- };
- }
-
- protected override void LoadComplete()
- {
- updateGlow();
- FinishTransforms(true);
- }
- }
}
}
diff --git a/osu.Game/Overlays/Profile/Header/Components/PreviousUsernames.cs b/osu.Game/Overlays/Profile/Header/Components/PreviousUsernames.cs
new file mode 100644
index 0000000000..f18f319e27
--- /dev/null
+++ b/osu.Game/Overlays/Profile/Header/Components/PreviousUsernames.cs
@@ -0,0 +1,168 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using System.Linq;
+using osu.Framework.Allocation;
+using osu.Framework.Bindables;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Shapes;
+using osu.Framework.Graphics.Sprites;
+using osu.Framework.Input.Events;
+using osu.Game.Graphics;
+using osu.Game.Users;
+using osuTK;
+
+namespace osu.Game.Overlays.Profile.Header.Components
+{
+ public class PreviousUsernames : CompositeDrawable
+ {
+ private const int duration = 200;
+ private const int margin = 10;
+ private const int width = 310;
+ private const int move_offset = 15;
+
+ public readonly Bindable User = new Bindable();
+
+ private readonly TextFlowContainer text;
+ private readonly Box background;
+ private readonly SpriteText header;
+
+ public PreviousUsernames()
+ {
+ HoverIconContainer hoverIcon;
+
+ AutoSizeAxes = Axes.Y;
+ Width = width;
+ Masking = true;
+ CornerRadius = 5;
+
+ AddRangeInternal(new Drawable[]
+ {
+ background = new Box
+ {
+ RelativeSizeAxes = Axes.Both,
+ },
+ new GridContainer
+ {
+ AutoSizeAxes = Axes.Y,
+ RelativeSizeAxes = Axes.X,
+ RowDimensions = new[]
+ {
+ new Dimension(GridSizeMode.AutoSize),
+ new Dimension(GridSizeMode.AutoSize)
+ },
+ ColumnDimensions = new[]
+ {
+ new Dimension(GridSizeMode.AutoSize),
+ new Dimension(GridSizeMode.Distributed)
+ },
+ Content = new[]
+ {
+ new Drawable[]
+ {
+ hoverIcon = new HoverIconContainer(),
+ header = new SpriteText
+ {
+ Anchor = Anchor.BottomLeft,
+ Origin = Anchor.BottomLeft,
+ Text = @"formerly known as",
+ Font = OsuFont.GetFont(size: 10, italics: true)
+ }
+ },
+ new Drawable[]
+ {
+ new Container
+ {
+ RelativeSizeAxes = Axes.Both,
+ },
+ text = new TextFlowContainer(s => s.Font = OsuFont.GetFont(size: 12, weight: FontWeight.Bold, italics: true))
+ {
+ RelativeSizeAxes = Axes.X,
+ AutoSizeAxes = Axes.Y,
+ Direction = FillDirection.Full,
+ Margin = new MarginPadding { Bottom = margin, Top = margin / 2f }
+ }
+ }
+ }
+ }
+ });
+
+ hoverIcon.ActivateHover += showContent;
+ hideContent();
+ }
+
+ [BackgroundDependencyLoader]
+ private void load(OsuColour colours)
+ {
+ background.Colour = colours.GreySeafoamDarker;
+ }
+
+ protected override void LoadComplete()
+ {
+ base.LoadComplete();
+ User.BindValueChanged(onUserChanged, true);
+ }
+
+ private void onUserChanged(ValueChangedEvent user)
+ {
+ text.Text = string.Empty;
+
+ var usernames = user.NewValue?.PreviousUsernames;
+
+ if (usernames?.Any() ?? false)
+ {
+ text.Text = string.Join(", ", usernames);
+ Show();
+ return;
+ }
+
+ Hide();
+ }
+
+ protected override void OnHoverLost(HoverLostEvent e)
+ {
+ base.OnHoverLost(e);
+ hideContent();
+ }
+
+ private void showContent()
+ {
+ text.FadeIn(duration, Easing.OutQuint);
+ header.FadeIn(duration, Easing.OutQuint);
+ background.FadeIn(duration, Easing.OutQuint);
+ this.MoveToY(-move_offset, duration, Easing.OutQuint);
+ }
+
+ private void hideContent()
+ {
+ text.FadeOut(duration, Easing.OutQuint);
+ header.FadeOut(duration, Easing.OutQuint);
+ background.FadeOut(duration, Easing.OutQuint);
+ this.MoveToY(0, duration, Easing.OutQuint);
+ }
+
+ private class HoverIconContainer : Container
+ {
+ public Action ActivateHover;
+
+ public HoverIconContainer()
+ {
+ AutoSizeAxes = Axes.Both;
+ Child = new SpriteIcon
+ {
+ Margin = new MarginPadding { Top = 6, Left = margin, Right = margin * 2 },
+ Size = new Vector2(15),
+ Icon = FontAwesome.Solid.IdCard,
+ };
+ }
+
+ protected override bool OnHover(HoverEvent e)
+ {
+ ActivateHover?.Invoke();
+ return base.OnHover(e);
+ }
+ }
+ }
+}
diff --git a/osu.Game/Overlays/Settings/Sections/Audio/MainMenuSettings.cs b/osu.Game/Overlays/Settings/Sections/Audio/MainMenuSettings.cs
index 4e43caff23..5ccdc952ba 100644
--- a/osu.Game/Overlays/Settings/Sections/Audio/MainMenuSettings.cs
+++ b/osu.Game/Overlays/Settings/Sections/Audio/MainMenuSettings.cs
@@ -1,7 +1,10 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+using System;
+using System.Linq;
using osu.Framework.Allocation;
+using osu.Framework.Graphics;
using osu.Game.Configuration;
namespace osu.Game.Overlays.Settings.Sections.Audio
@@ -13,7 +16,7 @@ namespace osu.Game.Overlays.Settings.Sections.Audio
[BackgroundDependencyLoader]
private void load(OsuConfigManager config)
{
- Children = new[]
+ Children = new Drawable[]
{
new SettingsCheckbox
{
@@ -25,6 +28,12 @@ namespace osu.Game.Overlays.Settings.Sections.Audio
LabelText = "osu! music theme",
Bindable = config.GetBindable(OsuSetting.MenuMusic)
},
+ new SettingsDropdown
+ {
+ LabelText = "Intro sequence",
+ Bindable = config.GetBindable(OsuSetting.IntroSequence),
+ Items = Enum.GetValues(typeof(IntroSequence)).Cast()
+ },
};
}
}
diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs
index ba2375bec1..c8858233aa 100644
--- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs
+++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs
@@ -398,7 +398,7 @@ namespace osu.Game.Rulesets.Scoring
if (rollingMaxBaseScore != 0)
Accuracy.Value = baseScore / rollingMaxBaseScore;
- TotalScore.Value = getScore(Mode.Value) * scoreMultiplier;
+ TotalScore.Value = getScore(Mode.Value);
}
private double getScore(ScoringMode mode)
@@ -407,11 +407,11 @@ namespace osu.Game.Rulesets.Scoring
{
default:
case ScoringMode.Standardised:
- return max_score * (base_portion * baseScore / maxBaseScore + combo_portion * HighestCombo.Value / maxHighestCombo) + bonusScore;
+ return (max_score * (base_portion * baseScore / maxBaseScore + combo_portion * HighestCombo.Value / maxHighestCombo) + bonusScore) * scoreMultiplier;
case ScoringMode.Classic:
// should emulate osu-stable's scoring as closely as we can (https://osu.ppy.sh/help/wiki/Score/ScoreV1)
- return bonusScore + baseScore * (1 + Math.Max(0, HighestCombo.Value - 1) / 25);
+ return bonusScore + baseScore * ((1 + Math.Max(0, HighestCombo.Value - 1) * scoreMultiplier) / 25);
}
}
diff --git a/osu.Game/Screens/BackgroundScreen.cs b/osu.Game/Screens/BackgroundScreen.cs
index bbe162cf7c..5dfaceccf5 100644
--- a/osu.Game/Screens/BackgroundScreen.cs
+++ b/osu.Game/Screens/BackgroundScreen.cs
@@ -11,8 +11,11 @@ namespace osu.Game.Screens
{
public abstract class BackgroundScreen : Screen, IEquatable
{
- protected BackgroundScreen()
+ private readonly bool animateOnEnter;
+
+ protected BackgroundScreen(bool animateOnEnter = true)
{
+ this.animateOnEnter = animateOnEnter;
Anchor = Anchor.Centre;
Origin = Anchor.Centre;
}
@@ -39,11 +42,14 @@ namespace osu.Game.Screens
public override void OnEntering(IScreen last)
{
- this.FadeOut();
- this.MoveToX(x_movement_amount);
+ if (animateOnEnter)
+ {
+ this.FadeOut();
+ this.MoveToX(x_movement_amount);
- this.FadeIn(transition_length, Easing.InOutQuart);
- this.MoveToX(0, transition_length, Easing.InOutQuart);
+ this.FadeIn(transition_length, Easing.InOutQuart);
+ this.MoveToX(0, transition_length, Easing.InOutQuart);
+ }
base.OnEntering(last);
}
diff --git a/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs b/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs
index 55338ea01a..2d7fe6a6a3 100644
--- a/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs
+++ b/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs
@@ -25,6 +25,11 @@ namespace osu.Game.Screens.Backgrounds
private Bindable user;
private Bindable skin;
+ public BackgroundScreenDefault(bool animateOnEnter = true)
+ : base(animateOnEnter)
+ {
+ }
+
[BackgroundDependencyLoader]
private void load(IAPIProvider api, SkinManager skinManager)
{
diff --git a/osu.Game/Screens/Loader.cs b/osu.Game/Screens/Loader.cs
index de00ba2e9f..850349272e 100644
--- a/osu.Game/Screens/Loader.cs
+++ b/osu.Game/Screens/Loader.cs
@@ -9,6 +9,8 @@ using osu.Framework.Graphics.Shaders;
using osu.Game.Screens.Menu;
using osuTK;
using osu.Framework.Screens;
+using osu.Game.Configuration;
+using IntroSequence = osu.Game.Configuration.IntroSequence;
namespace osu.Game.Screens
{
@@ -45,6 +47,8 @@ namespace osu.Game.Screens
private OsuScreen loadableScreen;
private ShaderPrecompiler precompiler;
+ private IntroSequence introSequence;
+
protected virtual OsuScreen CreateLoadableScreen()
{
if (showDisclaimer)
@@ -53,7 +57,17 @@ namespace osu.Game.Screens
return getIntroSequence();
}
- private IntroScreen getIntroSequence() => new IntroCircles();
+ private IntroScreen getIntroSequence()
+ {
+ switch (introSequence)
+ {
+ case IntroSequence.Circles:
+ return new IntroCircles();
+
+ default:
+ return new IntroTriangles();
+ }
+ }
protected virtual ShaderPrecompiler CreateShaderPrecompiler() => new ShaderPrecompiler();
@@ -79,9 +93,10 @@ namespace osu.Game.Screens
}
[BackgroundDependencyLoader]
- private void load(OsuGameBase game)
+ private void load(OsuGameBase game, OsuConfigManager config)
{
showDisclaimer = game.IsDeployedBuild;
+ introSequence = config.Get(OsuSetting.IntroSequence);
}
///
diff --git a/osu.Game/Screens/Menu/IntroTriangles.cs b/osu.Game/Screens/Menu/IntroTriangles.cs
new file mode 100644
index 0000000000..ba0d624959
--- /dev/null
+++ b/osu.Game/Screens/Menu/IntroTriangles.cs
@@ -0,0 +1,413 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using System.Collections.Generic;
+using System.IO;
+using osu.Framework.Allocation;
+using osu.Framework.Audio;
+using osu.Framework.Audio.Sample;
+using osu.Framework.Audio.Track;
+using osu.Framework.Bindables;
+using osu.Framework.Screens;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Shapes;
+using osu.Framework.Graphics.Textures;
+using osu.Framework.Graphics.Video;
+using osu.Framework.MathUtils;
+using osu.Framework.Timing;
+using osu.Game.Beatmaps;
+using osu.Game.Configuration;
+using osu.Game.Graphics;
+using osu.Game.Graphics.Containers;
+using osu.Game.Graphics.Sprites;
+using osu.Game.IO.Archives;
+using osu.Game.Rulesets;
+using osu.Game.Screens.Backgrounds;
+using osuTK;
+using osuTK.Graphics;
+
+namespace osu.Game.Screens.Menu
+{
+ public class IntroTriangles : IntroScreen
+ {
+ private const string menu_music_beatmap_hash = "a1556d0801b3a6b175dda32ef546f0ec812b400499f575c44fccbe9c67f9b1e5";
+
+ private SampleChannel welcome;
+
+ protected override BackgroundScreen CreateBackground() => background = new BackgroundScreenDefault(false)
+ {
+ Alpha = 0,
+ };
+
+ [Resolved]
+ private AudioManager audio { get; set; }
+
+ private Bindable menuMusic;
+ private Track track;
+ private WorkingBeatmap introBeatmap;
+
+ private BackgroundScreenDefault background;
+
+ [BackgroundDependencyLoader]
+ private void load(OsuConfigManager config, BeatmapManager beatmaps, Framework.Game game)
+ {
+ menuMusic = config.GetBindable(OsuSetting.MenuMusic);
+
+ BeatmapSetInfo setInfo = null;
+
+ if (!menuMusic.Value)
+ {
+ var sets = beatmaps.GetAllUsableBeatmapSets();
+ if (sets.Count > 0)
+ setInfo = beatmaps.QueryBeatmapSet(s => s.ID == sets[RNG.Next(0, sets.Count - 1)].ID);
+ }
+
+ if (setInfo == null)
+ {
+ setInfo = beatmaps.QueryBeatmapSet(b => b.Hash == menu_music_beatmap_hash);
+
+ if (setInfo == null)
+ {
+ // we need to import the default menu background beatmap
+ setInfo = beatmaps.Import(new ZipArchiveReader(game.Resources.GetStream(@"Tracks/triangles.osz"), "triangles.osz")).Result;
+
+ setInfo.Protected = true;
+ beatmaps.Update(setInfo);
+ }
+ }
+
+ introBeatmap = beatmaps.GetWorkingBeatmap(setInfo.Beatmaps[0]);
+
+ track = introBeatmap.Track;
+ track.Reset();
+
+ if (config.Get(OsuSetting.MenuVoice) && !menuMusic.Value)
+ // triangles has welcome sound included in the track. only play this if the user doesn't want menu music.
+ welcome = audio.Samples.Get(@"welcome");
+ }
+
+ protected override void LogoArriving(OsuLogo logo, bool resuming)
+ {
+ base.LogoArriving(logo, resuming);
+
+ logo.Triangles = true;
+
+ if (!resuming)
+ {
+ Beatmap.Value = introBeatmap;
+ introBeatmap = null;
+
+ PrepareMenuLoad();
+
+ LoadComponentAsync(new TrianglesIntroSequence(logo, background)
+ {
+ RelativeSizeAxes = Axes.Both,
+ Clock = new FramedClock(menuMusic.Value ? track : null),
+ LoadMenu = LoadMenu
+ }, t =>
+ {
+ AddInternal(t);
+ welcome?.Play();
+
+ // Only start the current track if it is the menu music. A beatmap's track is started when entering the Main Menu.
+ if (menuMusic.Value)
+ track.Start();
+ });
+ }
+ }
+
+ public override void OnResuming(IScreen last)
+ {
+ base.OnResuming(last);
+ background.FadeOut(100);
+ }
+
+ public override void OnSuspending(IScreen next)
+ {
+ track = null;
+ base.OnSuspending(next);
+ }
+
+ private class TrianglesIntroSequence : CompositeDrawable
+ {
+ private readonly OsuLogo logo;
+ private readonly BackgroundScreenDefault background;
+ private OsuSpriteText welcomeText;
+
+ private RulesetFlow rulesets;
+ private Container rulesetsScale;
+ private Drawable logoContainerSecondary;
+ private Drawable logoContainer;
+
+ private GlitchingTriangles triangles;
+
+ public Action LoadMenu;
+
+ public TrianglesIntroSequence(OsuLogo logo, BackgroundScreenDefault background)
+ {
+ this.logo = logo;
+ this.background = background;
+ }
+
+ private OsuGameBase game;
+
+ [BackgroundDependencyLoader]
+ private void load(TextureStore textures, OsuGameBase game)
+ {
+ this.game = game;
+
+ InternalChildren = new[]
+ {
+ triangles = new GlitchingTriangles
+ {
+ Alpha = 0,
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Size = new Vector2(0.4f, 0.16f)
+ },
+ welcomeText = new OsuSpriteText
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Padding = new MarginPadding { Bottom = 10 },
+ Font = OsuFont.GetFont(weight: FontWeight.Light, size: 42),
+ Alpha = 1,
+ Spacing = new Vector2(5),
+ },
+ rulesetsScale = new Container
+ {
+ RelativeSizeAxes = Axes.Both,
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Children = new Drawable[]
+ {
+ rulesets = new RulesetFlow()
+ }
+ },
+ logoContainerSecondary = new Container
+ {
+ RelativeSizeAxes = Axes.Both,
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Child = logoContainer = new LazerLogo(textures.GetStream("Menu/logo-triangles.mp4"))
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ }
+ },
+ };
+ }
+
+ private const double text_1 = 200;
+ private const double text_2 = 400;
+ private const double text_3 = 700;
+ private const double text_4 = 900;
+ private const double text_glitch = 1060;
+
+ private const double rulesets_1 = 1450;
+ private const double rulesets_2 = 1650;
+ private const double rulesets_3 = 1850;
+
+ private const double logo_scale_duration = 920;
+ private const double logo_1 = 2080;
+ private const double logo_2 = logo_1 + logo_scale_duration;
+
+ protected override void LoadComplete()
+ {
+ base.LoadComplete();
+
+ const float scale_start = 1.2f;
+ const float scale_adjust = 0.8f;
+
+ rulesets.Hide();
+ logoContainer.Hide();
+ background.Hide();
+
+ using (BeginAbsoluteSequence(0, true))
+ {
+ using (BeginDelayedSequence(text_1, true))
+ welcomeText.FadeIn().OnComplete(t => t.Text = "wel");
+
+ using (BeginDelayedSequence(text_2, true))
+ welcomeText.FadeIn().OnComplete(t => t.Text = "welcome");
+
+ using (BeginDelayedSequence(text_3, true))
+ welcomeText.FadeIn().OnComplete(t => t.Text = "welcome to");
+
+ using (BeginDelayedSequence(text_4, true))
+ {
+ welcomeText.FadeIn().OnComplete(t => t.Text = "welcome to osu!");
+ welcomeText.TransformTo(nameof(welcomeText.Spacing), new Vector2(50, 0), 5000);
+ }
+
+ using (BeginDelayedSequence(text_glitch, true))
+ triangles.FadeIn();
+
+ using (BeginDelayedSequence(rulesets_1, true))
+ {
+ rulesetsScale.ScaleTo(0.8f, 1000);
+ rulesets.FadeIn().ScaleTo(1).TransformSpacingTo(new Vector2(200, 0));
+ welcomeText.FadeOut();
+ triangles.FadeOut();
+ }
+
+ using (BeginDelayedSequence(rulesets_2, true))
+ {
+ rulesets.ScaleTo(2).TransformSpacingTo(new Vector2(30, 0));
+ }
+
+ using (BeginDelayedSequence(rulesets_3, true))
+ {
+ rulesets.ScaleTo(4).TransformSpacingTo(new Vector2(10, 0));
+ rulesetsScale.ScaleTo(1.3f, 1000);
+ }
+
+ using (BeginDelayedSequence(logo_1, true))
+ {
+ rulesets.FadeOut();
+
+ // matching flyte curve y = 0.25x^2 + (max(0, x - 0.7) / 0.3) ^ 5
+ logoContainer.FadeIn().ScaleTo(scale_start).Then().Delay(logo_scale_duration * 0.7f).ScaleTo(scale_start - scale_adjust, logo_scale_duration * 0.3f, Easing.InQuint);
+ logoContainerSecondary.ScaleTo(scale_start).Then().ScaleTo(scale_start - scale_adjust * 0.25f, logo_scale_duration, Easing.InQuad);
+ }
+
+ using (BeginDelayedSequence(logo_2, true))
+ {
+ logoContainer.FadeOut().OnComplete(_ =>
+ {
+ logo.FadeIn();
+ background.FadeIn();
+
+ game.Add(new GameWideFlash());
+
+ LoadMenu();
+ });
+ }
+ }
+ }
+
+ private class GameWideFlash : Box
+ {
+ private const double flash_length = 1000;
+
+ public GameWideFlash()
+ {
+ Colour = Color4.White;
+ RelativeSizeAxes = Axes.Both;
+ Blending = BlendingMode.Additive;
+ }
+
+ protected override void LoadComplete()
+ {
+ base.LoadComplete();
+ this.FadeOutFromOne(flash_length, Easing.Out);
+ }
+ }
+
+ private class LazerLogo : CompositeDrawable
+ {
+ public LazerLogo(Stream videoStream)
+ {
+ Size = new Vector2(960);
+
+ InternalChild = new VideoSprite(videoStream)
+ {
+ RelativeSizeAxes = Axes.Both,
+ Clock = new FramedOffsetClock(Clock) { Offset = -logo_1 }
+ };
+ }
+ }
+
+ private class RulesetFlow : FillFlowContainer
+ {
+ [BackgroundDependencyLoader]
+ private void load(RulesetStore rulesets)
+ {
+ var modes = new List();
+
+ foreach (var ruleset in rulesets.AvailableRulesets)
+ {
+ var icon = new ConstrainedIconContainer
+ {
+ Icon = ruleset.CreateInstance().CreateIcon(),
+ Size = new Vector2(30),
+ };
+
+ modes.Add(icon);
+ }
+
+ AutoSizeAxes = Axes.Both;
+ Children = modes;
+
+ Anchor = Anchor.Centre;
+ Origin = Anchor.Centre;
+ }
+ }
+
+ private class GlitchingTriangles : CompositeDrawable
+ {
+ public GlitchingTriangles()
+ {
+ RelativeSizeAxes = Axes.Both;
+ }
+
+ private double? lastGenTime;
+
+ private const double time_between_triangles = 22;
+
+ protected override void Update()
+ {
+ base.Update();
+
+ if (lastGenTime == null || Time.Current - lastGenTime > time_between_triangles)
+ {
+ lastGenTime = (lastGenTime ?? Time.Current) + time_between_triangles;
+
+ Drawable triangle = new OutlineTriangle(RNG.NextBool(), (RNG.NextSingle() + 0.2f) * 80)
+ {
+ RelativePositionAxes = Axes.Both,
+ Position = new Vector2(RNG.NextSingle(), RNG.NextSingle()),
+ };
+
+ AddInternal(triangle);
+
+ triangle.FadeOutFromOne(120);
+ }
+ }
+
+ ///
+ /// Represents a sprite that is drawn in a triangle shape, instead of a rectangle shape.
+ ///
+ public class OutlineTriangle : BufferedContainer
+ {
+ public OutlineTriangle(bool outlineOnly, float size)
+ {
+ Size = new Vector2(size);
+
+ InternalChildren = new Drawable[]
+ {
+ new Triangle { RelativeSizeAxes = Axes.Both },
+ };
+
+ if (outlineOnly)
+ {
+ AddInternal(new Triangle
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Colour = Color4.Black,
+ Size = new Vector2(size - 5),
+ Blending = BlendingMode.None,
+ });
+ }
+
+ Blending = BlendingMode.Additive;
+ CacheDrawnFrameBuffer = true;
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboardScope.cs b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboardScope.cs
index 9e480b61c6..dc4c2ba4e2 100644
--- a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboardScope.cs
+++ b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboardScope.cs
@@ -1,13 +1,22 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+using System.ComponentModel;
+
namespace osu.Game.Screens.Select.Leaderboards
{
public enum BeatmapLeaderboardScope
{
+ [Description("Local Ranking")]
Local,
+
+ [Description("Country Ranking")]
Country,
+
+ [Description("Global Ranking")]
Global,
+
+ [Description("Friend Ranking")]
Friend,
}
}
diff --git a/osu.Game/Users/User.cs b/osu.Game/Users/User.cs
index a5f3578711..9986f70557 100644
--- a/osu.Game/Users/User.cs
+++ b/osu.Game/Users/User.cs
@@ -20,6 +20,9 @@ namespace osu.Game.Users
[JsonProperty(@"username")]
public string Username;
+ [JsonProperty(@"previous_usernames")]
+ public string[] PreviousUsernames;
+
[JsonProperty(@"country")]
public Country Country;
diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj
index 1dd19ac7ed..b5266fd75d 100644
--- a/osu.Game/osu.Game.csproj
+++ b/osu.Game/osu.Game.csproj
@@ -14,7 +14,7 @@
-
+
diff --git a/osu.iOS.props b/osu.iOS.props
index 1c3faeed39..103d89cadc 100644
--- a/osu.iOS.props
+++ b/osu.iOS.props
@@ -104,7 +104,7 @@
-
+