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 d2dd8acad9..065c66ebba 100644
--- a/osu.Android.props
+++ b/osu.Android.props
@@ -61,6 +61,6 @@
-
+
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboard.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboard.cs
index ead7a4b7fc..ff8437311e 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboard.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboard.cs
@@ -21,32 +21,38 @@ namespace osu.Game.Tests.Visual.Gameplay
private readonly Container storyboardContainer;
private DrawableStoryboard storyboard;
+ [Cached]
+ private MusicController musicController = new MusicController();
+
public TestSceneStoryboard()
{
Clock = new FramedClock();
- Add(new Container
+ AddRange(new Drawable[]
{
- RelativeSizeAxes = Axes.Both,
- Children = new Drawable[]
+ musicController,
+ new Container
{
- new Box
+ RelativeSizeAxes = Axes.Both,
+ Children = new Drawable[]
{
- RelativeSizeAxes = Axes.Both,
- Colour = Color4.Black,
- },
- storyboardContainer = new Container
- {
- RelativeSizeAxes = Axes.Both,
+ new Box
+ {
+ RelativeSizeAxes = Axes.Both,
+ Colour = Color4.Black,
+ },
+ storyboardContainer = new Container
+ {
+ RelativeSizeAxes = Axes.Both,
+ },
},
},
- });
-
- Add(new MusicController
- {
- Origin = Anchor.TopRight,
- Anchor = Anchor.TopRight,
- State = { Value = Visibility.Visible },
+ new NowPlayingOverlay
+ {
+ Origin = Anchor.TopRight,
+ Anchor = Anchor.TopRight,
+ State = { Value = Visibility.Visible },
+ }
});
AddStep("Restart", restart);
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/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/TestSceneBeatSyncedContainer.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneBeatSyncedContainer.cs
index 28f0cc027e..94228e22f0 100644
--- a/osu.Game.Tests/Visual/UserInterface/TestSceneBeatSyncedContainer.cs
+++ b/osu.Game.Tests/Visual/UserInterface/TestSceneBeatSyncedContainer.cs
@@ -3,6 +3,7 @@
using System;
using NUnit.Framework;
+using osu.Framework.Allocation;
using osu.Framework.Audio.Track;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
@@ -22,30 +23,36 @@ namespace osu.Game.Tests.Visual.UserInterface
[TestFixture]
public class TestSceneBeatSyncedContainer : OsuTestScene
{
- private readonly MusicController mc;
+ private readonly NowPlayingOverlay np;
+
+ [Cached]
+ private MusicController musicController = new MusicController();
public TestSceneBeatSyncedContainer()
{
Clock = new FramedClock();
Clock.ProcessFrame();
- Add(new BeatContainer
+ AddRange(new Drawable[]
{
- Anchor = Anchor.BottomCentre,
- Origin = Anchor.BottomCentre,
- });
-
- Add(mc = new MusicController
- {
- Origin = Anchor.TopRight,
- Anchor = Anchor.TopRight,
+ musicController,
+ new BeatContainer
+ {
+ Anchor = Anchor.BottomCentre,
+ Origin = Anchor.BottomCentre,
+ },
+ np = new NowPlayingOverlay
+ {
+ Origin = Anchor.TopRight,
+ Anchor = Anchor.TopRight,
+ }
});
}
protected override void LoadComplete()
{
base.LoadComplete();
- mc.ToggleVisibility();
+ np.ToggleVisibility();
}
private class BeatContainer : BeatSyncedContainer
diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneMusicController.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneNowPlayingOverlay.cs
similarity index 58%
rename from osu.Game.Tests/Visual/UserInterface/TestSceneMusicController.cs
rename to osu.Game.Tests/Visual/UserInterface/TestSceneNowPlayingOverlay.cs
index ab2ca47100..e3daa9c279 100644
--- a/osu.Game.Tests/Visual/UserInterface/TestSceneMusicController.cs
+++ b/osu.Game.Tests/Visual/UserInterface/TestSceneNowPlayingOverlay.cs
@@ -2,6 +2,7 @@
// See the LICENCE file in the repository root for full licence text.
using NUnit.Framework;
+using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Timing;
using osu.Game.Overlays;
@@ -9,22 +10,27 @@ using osu.Game.Overlays;
namespace osu.Game.Tests.Visual.UserInterface
{
[TestFixture]
- public class TestSceneMusicController : OsuTestScene
+ public class TestSceneNowPlayingOverlay : OsuTestScene
{
- public TestSceneMusicController()
+ [Cached]
+ private MusicController musicController = new MusicController();
+
+ public TestSceneNowPlayingOverlay()
{
Clock = new FramedClock();
- var mc = new MusicController
+ var np = new NowPlayingOverlay
{
Origin = Anchor.Centre,
Anchor = Anchor.Centre
};
- Add(mc);
- AddStep(@"show", () => mc.Show());
+ Add(musicController);
+ Add(np);
+
+ AddStep(@"show", () => np.Show());
AddToggleStep(@"toggle beatmap lock", state => Beatmap.Disabled = state);
- AddStep(@"show", () => mc.Hide());
+ AddStep(@"show", () => np.Hide());
}
}
}
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/OsuGame.cs b/osu.Game/OsuGame.cs
index b9e2b79b05..edf3424e0e 100644
--- a/osu.Game/OsuGame.cs
+++ b/osu.Game/OsuGame.cs
@@ -469,6 +469,8 @@ namespace osu.Game
loadComponentSingleFile(volume = new VolumeOverlay(), leftFloatingOverlayContent.Add);
loadComponentSingleFile(new OnScreenDisplay(), Add, true);
+ loadComponentSingleFile(musicController = new MusicController(), Add, true);
+
loadComponentSingleFile(notifications = new NotificationOverlay
{
GetToolbarHeight = () => ToolbarOffset,
@@ -495,7 +497,7 @@ namespace osu.Game
Origin = Anchor.TopRight,
}, rightFloatingOverlayContent.Add, true);
- loadComponentSingleFile(musicController = new MusicController
+ loadComponentSingleFile(new NowPlayingOverlay
{
GetToolbarHeight = () => ToolbarOffset,
Anchor = Anchor.TopRight,
diff --git a/osu.Game/Overlays/MusicController.cs b/osu.Game/Overlays/MusicController.cs
index abbcec5094..da9c34238e 100644
--- a/osu.Game/Overlays/MusicController.cs
+++ b/osu.Game/Overlays/MusicController.cs
@@ -1,292 +1,106 @@
-// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// 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.Linq;
-using System.Threading.Tasks;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
-using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
-using osu.Framework.Graphics.Containers;
-using osu.Framework.Graphics.Effects;
-using osu.Framework.Graphics.Shapes;
-using osu.Framework.Graphics.Sprites;
-using osu.Framework.Graphics.Textures;
-using osu.Framework.Input.Events;
-using osu.Framework.Localisation;
using osu.Framework.Threading;
using osu.Game.Beatmaps;
-using osu.Game.Graphics;
-using osu.Game.Graphics.Containers;
-using osu.Game.Graphics.Sprites;
-using osu.Game.Graphics.UserInterface;
-using osu.Game.Overlays.Music;
using osu.Game.Rulesets.Mods;
-using osuTK;
-using osuTK.Graphics;
namespace osu.Game.Overlays
{
- public class MusicController : OsuFocusedOverlayContainer
+ ///
+ /// Handles playback of the global music track.
+ ///
+ public class MusicController : Component
{
- private const float player_height = 130;
- private const float transition_length = 800;
- private const float progress_height = 10;
- private const float bottom_black_area_height = 55;
-
- private Drawable background;
- private ProgressBar progressBar;
-
- private IconButton prevButton;
- private IconButton playButton;
- private IconButton nextButton;
- private IconButton playlistButton;
-
- private SpriteText title, artist;
-
- private PlaylistOverlay playlist;
-
- private BeatmapManager beatmaps;
+ [Resolved]
+ private BeatmapManager beatmaps { get; set; }
private List beatmapSets;
- private Container dragContainer;
- private Container playerContainer;
-
public bool IsUserPaused { get; private set; }
+ ///
+ /// Fired when the global has changed.
+ /// Includes direction information for display purposes.
+ ///
+ public event Action TrackChanged;
+
[Resolved]
- private Bindable beatmap { get; set; }
+ private IBindable beatmap { get; set; }
[Resolved]
private IBindable> mods { get; set; }
- ///
- /// Provide a source for the toolbar height.
- ///
- public Func GetToolbarHeight;
-
- public MusicController()
- {
- Width = 400;
- Margin = new MarginPadding(10);
- }
-
[BackgroundDependencyLoader]
- private void load(Bindable beatmap, BeatmapManager beatmaps, OsuColour colours)
+ private void load()
{
- this.beatmaps = beatmaps;
-
- Children = new Drawable[]
- {
- dragContainer = new DragContainer
- {
- Anchor = Anchor.Centre,
- Origin = Anchor.Centre,
- RelativeSizeAxes = Axes.X,
- AutoSizeAxes = Axes.Y,
- Children = new Drawable[]
- {
- playlist = new PlaylistOverlay
- {
- RelativeSizeAxes = Axes.X,
- Y = player_height + 10,
- OrderChanged = playlistOrderChanged
- },
- playerContainer = new Container
- {
- RelativeSizeAxes = Axes.X,
- Height = player_height,
- Masking = true,
- CornerRadius = 5,
- EdgeEffect = new EdgeEffectParameters
- {
- Type = EdgeEffectType.Shadow,
- Colour = Color4.Black.Opacity(40),
- Radius = 5,
- },
- Children = new[]
- {
- background = new Background(),
- title = new OsuSpriteText
- {
- Origin = Anchor.BottomCentre,
- Anchor = Anchor.TopCentre,
- Position = new Vector2(0, 40),
- Font = OsuFont.GetFont(size: 25, italics: true),
- Colour = Color4.White,
- Text = @"Nothing to play",
- },
- artist = new OsuSpriteText
- {
- Origin = Anchor.TopCentre,
- Anchor = Anchor.TopCentre,
- Position = new Vector2(0, 45),
- Font = OsuFont.GetFont(size: 15, weight: FontWeight.Bold, italics: true),
- Colour = Color4.White,
- Text = @"Nothing to play",
- },
- new Container
- {
- Padding = new MarginPadding { Bottom = progress_height },
- Height = bottom_black_area_height,
- RelativeSizeAxes = Axes.X,
- Origin = Anchor.BottomCentre,
- Anchor = Anchor.BottomCentre,
- Children = new Drawable[]
- {
- new FillFlowContainer
- {
- AutoSizeAxes = Axes.Both,
- Direction = FillDirection.Horizontal,
- Spacing = new Vector2(5),
- Origin = Anchor.Centre,
- Anchor = Anchor.Centre,
- Children = new[]
- {
- prevButton = new MusicIconButton
- {
- Anchor = Anchor.Centre,
- Origin = Anchor.Centre,
- Action = prev,
- Icon = FontAwesome.Solid.StepBackward,
- },
- playButton = new MusicIconButton
- {
- Anchor = Anchor.Centre,
- Origin = Anchor.Centre,
- Scale = new Vector2(1.4f),
- IconScale = new Vector2(1.4f),
- Action = togglePause,
- Icon = FontAwesome.Regular.PlayCircle,
- },
- nextButton = new MusicIconButton
- {
- Anchor = Anchor.Centre,
- Origin = Anchor.Centre,
- Action = () => next(),
- Icon = FontAwesome.Solid.StepForward,
- },
- }
- },
- playlistButton = new MusicIconButton
- {
- Origin = Anchor.Centre,
- Anchor = Anchor.CentreRight,
- Position = new Vector2(-bottom_black_area_height / 2, 0),
- Icon = FontAwesome.Solid.Bars,
- Action = () => playlist.ToggleVisibility(),
- },
- }
- },
- progressBar = new ProgressBar
- {
- Origin = Anchor.BottomCentre,
- Anchor = Anchor.BottomCentre,
- Height = progress_height,
- FillColour = colours.Yellow,
- OnSeek = attemptSeek
- }
- },
- },
- }
- }
- };
-
beatmapSets = beatmaps.GetAllUsableBeatmapSets();
beatmaps.ItemAdded += handleBeatmapAdded;
beatmaps.ItemRemoved += handleBeatmapRemoved;
-
- playlist.State.ValueChanged += s => playlistButton.FadeColour(s.NewValue == Visibility.Visible ? colours.Yellow : Color4.White, 200, Easing.OutQuint);
}
- private ScheduledDelegate seekDelegate;
-
- private void attemptSeek(double progress)
+ protected override void LoadComplete()
{
- seekDelegate?.Cancel();
- seekDelegate = Schedule(() =>
- {
- if (!beatmap.Disabled)
- current?.Track.Seek(progress);
- });
+ beatmap.BindValueChanged(beatmapChanged, true);
+ mods.BindValueChanged(_ => updateAudioAdjustments(), true);
+ base.LoadComplete();
}
- private void playlistOrderChanged(BeatmapSetInfo beatmapSetInfo, int index)
+ ///
+ /// Change the position of a in the current playlist.
+ ///
+ /// The beatmap to move.
+ /// The new position.
+ public void ChangeBeatmapSetPosition(BeatmapSetInfo beatmapSetInfo, int index)
{
beatmapSets.Remove(beatmapSetInfo);
beatmapSets.Insert(index, beatmapSetInfo);
}
- private void handleBeatmapAdded(BeatmapSetInfo set) => Schedule(() => beatmapSets.Add(set));
+ ///
+ /// Returns whether the current beatmap track is playing.
+ ///
+ public bool IsPlaying => beatmap.Value.Track.IsRunning;
- private void handleBeatmapRemoved(BeatmapSetInfo set) => Schedule(() => beatmapSets.RemoveAll(s => s.ID == set.ID));
+ private void handleBeatmapAdded(BeatmapSetInfo set) =>
+ Schedule(() => beatmapSets.Add(set));
- protected override void LoadComplete()
+ private void handleBeatmapRemoved(BeatmapSetInfo set) =>
+ Schedule(() => beatmapSets.RemoveAll(s => s.ID == set.ID));
+
+ private ScheduledDelegate seekDelegate;
+
+ public void SeekTo(double position)
{
- beatmap.BindValueChanged(beatmapChanged, true);
- beatmap.BindDisabledChanged(beatmapDisabledChanged, true);
- mods.BindValueChanged(_ => updateAudioAdjustments(), true);
- base.LoadComplete();
- }
-
- private void beatmapDisabledChanged(bool disabled)
- {
- if (disabled)
- playlist.Hide();
-
- playButton.Enabled.Value = !disabled;
- prevButton.Enabled.Value = !disabled;
- nextButton.Enabled.Value = !disabled;
- playlistButton.Enabled.Value = !disabled;
- }
-
- protected override void UpdateAfterChildren()
- {
- base.UpdateAfterChildren();
- Height = dragContainer.Height;
-
- dragContainer.Padding = new MarginPadding { Top = GetToolbarHeight?.Invoke() ?? 0 };
- }
-
- protected override void Update()
- {
- base.Update();
-
- if (pendingBeatmapSwitch != null)
+ seekDelegate?.Cancel();
+ seekDelegate = Schedule(() =>
{
- pendingBeatmapSwitch();
- pendingBeatmapSwitch = null;
- }
-
- var track = current?.TrackLoaded ?? false ? current.Track : null;
-
- if (track?.IsDummyDevice == false)
- {
- progressBar.EndTime = track.Length;
- progressBar.CurrentTime = track.CurrentTime;
-
- playButton.Icon = track.IsRunning ? FontAwesome.Regular.PauseCircle : FontAwesome.Regular.PlayCircle;
- }
- else
- {
- progressBar.CurrentTime = 0;
- progressBar.EndTime = 1;
- playButton.Icon = FontAwesome.Regular.PlayCircle;
- }
+ if (!beatmap.Disabled)
+ current?.Track.Seek(position);
+ });
}
- private void togglePause()
+ ///
+ /// Toggle pause / play.
+ ///
+ /// Whether the operation was successful.
+ public bool TogglePause()
{
var track = current?.Track;
if (track == null)
{
- if (!beatmap.Disabled)
- next(true);
- return;
+ if (beatmap.Disabled)
+ return false;
+
+ next(true);
+ return true;
}
if (track.IsRunning)
@@ -299,48 +113,70 @@ namespace osu.Game.Overlays
track.Start();
IsUserPaused = false;
}
+
+ return true;
}
- private void prev()
+ ///
+ /// Play the previous track.
+ ///
+ /// Whether the operation was successful.
+ public bool PrevTrack()
{
- queuedDirection = TransformDirection.Prev;
+ queuedDirection = TrackChangeDirection.Prev;
var playable = beatmapSets.TakeWhile(i => i.ID != current.BeatmapSetInfo.ID).LastOrDefault() ?? beatmapSets.LastOrDefault();
if (playable != null)
{
- beatmap.Value = beatmaps.GetWorkingBeatmap(playable.Beatmaps.First(), beatmap.Value);
+ if (beatmap is Bindable working)
+ working.Value = beatmaps.GetWorkingBeatmap(playable.Beatmaps.First(), beatmap.Value);
beatmap.Value.Track.Restart();
+
+ return true;
}
+
+ return false;
}
- private void next(bool instant = false)
+ ///
+ /// Play the next random or playlist track.
+ ///
+ /// Whether the operation was successful.
+ public bool NextTrack() => next();
+
+ private bool next(bool instant = false)
{
if (!instant)
- queuedDirection = TransformDirection.Next;
+ queuedDirection = TrackChangeDirection.Next;
var playable = beatmapSets.SkipWhile(i => i.ID != current.BeatmapSetInfo.ID).Skip(1).FirstOrDefault() ?? beatmapSets.FirstOrDefault();
if (playable != null)
{
- beatmap.Value = beatmaps.GetWorkingBeatmap(playable.Beatmaps.First(), beatmap.Value);
+ if (beatmap is Bindable working)
+ working.Value = beatmaps.GetWorkingBeatmap(playable.Beatmaps.First(), beatmap.Value);
beatmap.Value.Track.Restart();
+ return true;
}
+
+ return false;
}
private WorkingBeatmap current;
- private TransformDirection? queuedDirection;
+
+ private TrackChangeDirection? queuedDirection;
private void beatmapChanged(ValueChangedEvent beatmap)
{
- TransformDirection direction = TransformDirection.None;
+ TrackChangeDirection direction = TrackChangeDirection.None;
if (current != null)
{
bool audioEquals = beatmap.NewValue?.BeatmapInfo?.AudioEquals(current.BeatmapInfo) ?? false;
if (audioEquals)
- direction = TransformDirection.None;
+ direction = TrackChangeDirection.None;
else if (queuedDirection.HasValue)
{
direction = queuedDirection.Value;
@@ -352,13 +188,13 @@ namespace osu.Game.Overlays
var last = beatmapSets.TakeWhile(b => b.ID != current.BeatmapSetInfo?.ID).Count();
var next = beatmap.NewValue == null ? -1 : beatmapSets.TakeWhile(b => b.ID != beatmap.NewValue.BeatmapSetInfo?.ID).Count();
- direction = last > next ? TransformDirection.Prev : TransformDirection.Next;
+ direction = last > next ? TrackChangeDirection.Prev : TrackChangeDirection.Next;
}
}
- progressBar.CurrentTime = 0;
+ current = beatmap.NewValue;
+ TrackChanged?.Invoke(current, direction);
- updateDisplay(current = beatmap.NewValue, direction);
updateAudioAdjustments();
queuedDirection = null;
@@ -376,167 +212,22 @@ namespace osu.Game.Overlays
mod.ApplyToClock(track);
}
- private Action pendingBeatmapSwitch;
-
- private void updateDisplay(WorkingBeatmap beatmap, TransformDirection direction)
+ protected override void Dispose(bool isDisposing)
{
- // avoid using scheduler as our scheduler may not be run for a long time, holding references to beatmaps.
- pendingBeatmapSwitch = delegate
+ base.Dispose(isDisposing);
+
+ if (beatmaps != null)
{
- // todo: this can likely be replaced with WorkingBeatmap.GetBeatmapAsync()
- Task.Run(() =>
- {
- if (beatmap?.Beatmap == null) //this is not needed if a placeholder exists
- {
- title.Text = @"Nothing to play";
- artist.Text = @"Nothing to play";
- }
- else
- {
- BeatmapMetadata metadata = beatmap.Metadata;
- title.Text = new LocalisedString((metadata.TitleUnicode, metadata.Title));
- artist.Text = new LocalisedString((metadata.ArtistUnicode, metadata.Artist));
- }
- });
-
- LoadComponentAsync(new Background(beatmap) { Depth = float.MaxValue }, newBackground =>
- {
- switch (direction)
- {
- case TransformDirection.Next:
- newBackground.Position = new Vector2(400, 0);
- newBackground.MoveToX(0, 500, Easing.OutCubic);
- background.MoveToX(-400, 500, Easing.OutCubic);
- break;
-
- case TransformDirection.Prev:
- newBackground.Position = new Vector2(-400, 0);
- newBackground.MoveToX(0, 500, Easing.OutCubic);
- background.MoveToX(400, 500, Easing.OutCubic);
- break;
- }
-
- background.Expire();
- background = newBackground;
-
- playerContainer.Add(newBackground);
- });
- };
- }
-
- protected override void PopIn()
- {
- base.PopIn();
-
- this.FadeIn(transition_length, Easing.OutQuint);
- dragContainer.ScaleTo(1, transition_length, Easing.OutElastic);
- }
-
- protected override void PopOut()
- {
- base.PopOut();
-
- this.FadeOut(transition_length, Easing.OutQuint);
- dragContainer.ScaleTo(0.9f, transition_length, Easing.OutQuint);
- }
-
- private enum TransformDirection
- {
- None,
- Next,
- Prev
- }
-
- private class MusicIconButton : IconButton
- {
- public MusicIconButton()
- {
- AutoSizeAxes = Axes.Both;
- }
-
- [BackgroundDependencyLoader]
- private void load(OsuColour colours)
- {
- HoverColour = colours.YellowDark.Opacity(0.6f);
- FlashColour = colours.Yellow;
- }
-
- protected override void LoadComplete()
- {
- base.LoadComplete();
-
- // works with AutoSizeAxes above to make buttons autosize with the scale animation.
- Content.AutoSizeAxes = Axes.None;
- Content.Size = new Vector2(DEFAULT_BUTTON_SIZE);
+ beatmaps.ItemAdded -= handleBeatmapAdded;
+ beatmaps.ItemRemoved -= handleBeatmapRemoved;
}
}
+ }
- private class Background : BufferedContainer
- {
- private readonly Sprite sprite;
- private readonly WorkingBeatmap beatmap;
-
- public Background(WorkingBeatmap beatmap = null)
- {
- this.beatmap = beatmap;
- CacheDrawnFrameBuffer = true;
- Depth = float.MaxValue;
- RelativeSizeAxes = Axes.Both;
-
- Children = new Drawable[]
- {
- sprite = new Sprite
- {
- RelativeSizeAxes = Axes.Both,
- Colour = OsuColour.Gray(150),
- FillMode = FillMode.Fill,
- },
- new Box
- {
- RelativeSizeAxes = Axes.X,
- Height = bottom_black_area_height,
- Origin = Anchor.BottomCentre,
- Anchor = Anchor.BottomCentre,
- Colour = Color4.Black.Opacity(0.5f)
- }
- };
- }
-
- [BackgroundDependencyLoader]
- private void load(TextureStore textures)
- {
- sprite.Texture = beatmap?.Background ?? textures.Get(@"Backgrounds/bg4");
- }
- }
-
- private class DragContainer : Container
- {
- protected override bool OnDragStart(DragStartEvent e)
- {
- return true;
- }
-
- protected override bool 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 : (float)Math.Pow(change.Length, 0.7f) / change.Length;
-
- this.MoveTo(change);
- return true;
- }
-
- protected override bool OnDragEnd(DragEndEvent e)
- {
- this.MoveTo(Vector2.Zero, 800, Easing.OutElastic);
- return base.OnDragEnd(e);
- }
- }
-
- ///
- /// Play the next random or playlist track.
- ///
- public void NextTrack() => next();
+ public enum TrackChangeDirection
+ {
+ None,
+ Next,
+ Prev
}
}
diff --git a/osu.Game/Overlays/NowPlayingOverlay.cs b/osu.Game/Overlays/NowPlayingOverlay.cs
new file mode 100644
index 0000000000..a3243a655e
--- /dev/null
+++ b/osu.Game/Overlays/NowPlayingOverlay.cs
@@ -0,0 +1,404 @@
+// 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.Threading.Tasks;
+using osu.Framework.Allocation;
+using osu.Framework.Bindables;
+using osu.Framework.Extensions.Color4Extensions;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Effects;
+using osu.Framework.Graphics.Shapes;
+using osu.Framework.Graphics.Sprites;
+using osu.Framework.Graphics.Textures;
+using osu.Framework.Input.Events;
+using osu.Framework.Localisation;
+using osu.Game.Beatmaps;
+using osu.Game.Graphics;
+using osu.Game.Graphics.Containers;
+using osu.Game.Graphics.Sprites;
+using osu.Game.Graphics.UserInterface;
+using osu.Game.Overlays.Music;
+using osuTK;
+using osuTK.Graphics;
+
+namespace osu.Game.Overlays
+{
+ public class NowPlayingOverlay : OsuFocusedOverlayContainer
+ {
+ private const float player_height = 130;
+ private const float transition_length = 800;
+ private const float progress_height = 10;
+ private const float bottom_black_area_height = 55;
+
+ private Drawable background;
+ private ProgressBar progressBar;
+
+ private IconButton prevButton;
+ private IconButton playButton;
+ private IconButton nextButton;
+ private IconButton playlistButton;
+
+ private SpriteText title, artist;
+
+ private PlaylistOverlay playlist;
+
+ private Container dragContainer;
+ private Container playerContainer;
+
+ ///
+ /// Provide a source for the toolbar height.
+ ///
+ public Func GetToolbarHeight;
+
+ [Resolved]
+ private MusicController musicController { get; set; }
+
+ [Resolved]
+ private Bindable beatmap { get; set; }
+
+ public NowPlayingOverlay()
+ {
+ Width = 400;
+ Margin = new MarginPadding(10);
+ }
+
+ [BackgroundDependencyLoader]
+ private void load(OsuColour colours)
+ {
+ Children = new Drawable[]
+ {
+ dragContainer = new DragContainer
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ RelativeSizeAxes = Axes.X,
+ AutoSizeAxes = Axes.Y,
+ Children = new Drawable[]
+ {
+ playlist = new PlaylistOverlay
+ {
+ RelativeSizeAxes = Axes.X,
+ Y = player_height + 10,
+ OrderChanged = musicController.ChangeBeatmapSetPosition
+ },
+ playerContainer = new Container
+ {
+ RelativeSizeAxes = Axes.X,
+ Height = player_height,
+ Masking = true,
+ CornerRadius = 5,
+ EdgeEffect = new EdgeEffectParameters
+ {
+ Type = EdgeEffectType.Shadow,
+ Colour = Color4.Black.Opacity(40),
+ Radius = 5,
+ },
+ Children = new[]
+ {
+ background = new Background(),
+ title = new OsuSpriteText
+ {
+ Origin = Anchor.BottomCentre,
+ Anchor = Anchor.TopCentre,
+ Position = new Vector2(0, 40),
+ Font = OsuFont.GetFont(size: 25, italics: true),
+ Colour = Color4.White,
+ Text = @"Nothing to play",
+ },
+ artist = new OsuSpriteText
+ {
+ Origin = Anchor.TopCentre,
+ Anchor = Anchor.TopCentre,
+ Position = new Vector2(0, 45),
+ Font = OsuFont.GetFont(size: 15, weight: FontWeight.Bold, italics: true),
+ Colour = Color4.White,
+ Text = @"Nothing to play",
+ },
+ new Container
+ {
+ Padding = new MarginPadding { Bottom = progress_height },
+ Height = bottom_black_area_height,
+ RelativeSizeAxes = Axes.X,
+ Origin = Anchor.BottomCentre,
+ Anchor = Anchor.BottomCentre,
+ Children = new Drawable[]
+ {
+ new FillFlowContainer
+ {
+ AutoSizeAxes = Axes.Both,
+ Direction = FillDirection.Horizontal,
+ Spacing = new Vector2(5),
+ Origin = Anchor.Centre,
+ Anchor = Anchor.Centre,
+ Children = new[]
+ {
+ prevButton = new MusicIconButton
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Action = () => musicController.PrevTrack(),
+ Icon = FontAwesome.Solid.StepBackward,
+ },
+ playButton = new MusicIconButton
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Scale = new Vector2(1.4f),
+ IconScale = new Vector2(1.4f),
+ Action = () => musicController.TogglePause(),
+ Icon = FontAwesome.Regular.PlayCircle,
+ },
+ nextButton = new MusicIconButton
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Action = () => musicController.NextTrack(),
+ Icon = FontAwesome.Solid.StepForward,
+ },
+ }
+ },
+ playlistButton = new MusicIconButton
+ {
+ Origin = Anchor.Centre,
+ Anchor = Anchor.CentreRight,
+ Position = new Vector2(-bottom_black_area_height / 2, 0),
+ Icon = FontAwesome.Solid.Bars,
+ Action = () => playlist.ToggleVisibility(),
+ },
+ }
+ },
+ progressBar = new ProgressBar
+ {
+ Origin = Anchor.BottomCentre,
+ Anchor = Anchor.BottomCentre,
+ Height = progress_height,
+ FillColour = colours.Yellow,
+ OnSeek = musicController.SeekTo
+ }
+ },
+ },
+ }
+ }
+ };
+
+ playlist.State.ValueChanged += s => playlistButton.FadeColour(s.NewValue == Visibility.Visible ? colours.Yellow : Color4.White, 200, Easing.OutQuint);
+ }
+
+ protected override void LoadComplete()
+ {
+ base.LoadComplete();
+
+ beatmap.BindDisabledChanged(beatmapDisabledChanged, true);
+
+ musicController.TrackChanged += trackChanged;
+ trackChanged(beatmap.Value);
+ }
+
+ protected override void PopIn()
+ {
+ base.PopIn();
+
+ this.FadeIn(transition_length, Easing.OutQuint);
+ dragContainer.ScaleTo(1, transition_length, Easing.OutElastic);
+ }
+
+ protected override void PopOut()
+ {
+ base.PopOut();
+
+ this.FadeOut(transition_length, Easing.OutQuint);
+ dragContainer.ScaleTo(0.9f, transition_length, Easing.OutQuint);
+ }
+
+ protected override void UpdateAfterChildren()
+ {
+ base.UpdateAfterChildren();
+
+ Height = dragContainer.Height;
+ dragContainer.Padding = new MarginPadding { Top = GetToolbarHeight?.Invoke() ?? 0 };
+ }
+
+ protected override void Update()
+ {
+ base.Update();
+
+ if (pendingBeatmapSwitch != null)
+ {
+ pendingBeatmapSwitch();
+ pendingBeatmapSwitch = null;
+ }
+
+ var track = beatmap.Value?.TrackLoaded ?? false ? beatmap.Value.Track : null;
+
+ if (track?.IsDummyDevice == false)
+ {
+ progressBar.EndTime = track.Length;
+ progressBar.CurrentTime = track.CurrentTime;
+
+ playButton.Icon = track.IsRunning ? FontAwesome.Regular.PauseCircle : FontAwesome.Regular.PlayCircle;
+ }
+ else
+ {
+ progressBar.CurrentTime = 0;
+ progressBar.EndTime = 1;
+ playButton.Icon = FontAwesome.Regular.PlayCircle;
+ }
+ }
+
+ private Action pendingBeatmapSwitch;
+
+ private void trackChanged(WorkingBeatmap beatmap, TrackChangeDirection direction = TrackChangeDirection.None)
+ {
+ // avoid using scheduler as our scheduler may not be run for a long time, holding references to beatmaps.
+ pendingBeatmapSwitch = delegate
+ {
+ // todo: this can likely be replaced with WorkingBeatmap.GetBeatmapAsync()
+ Task.Run(() =>
+ {
+ if (beatmap?.Beatmap == null) //this is not needed if a placeholder exists
+ {
+ title.Text = @"Nothing to play";
+ artist.Text = @"Nothing to play";
+ }
+ else
+ {
+ BeatmapMetadata metadata = beatmap.Metadata;
+ title.Text = new LocalisedString((metadata.TitleUnicode, metadata.Title));
+ artist.Text = new LocalisedString((metadata.ArtistUnicode, metadata.Artist));
+ }
+ });
+
+ LoadComponentAsync(new Background(beatmap) { Depth = float.MaxValue }, newBackground =>
+ {
+ switch (direction)
+ {
+ case TrackChangeDirection.Next:
+ newBackground.Position = new Vector2(400, 0);
+ newBackground.MoveToX(0, 500, Easing.OutCubic);
+ background.MoveToX(-400, 500, Easing.OutCubic);
+ break;
+
+ case TrackChangeDirection.Prev:
+ newBackground.Position = new Vector2(-400, 0);
+ newBackground.MoveToX(0, 500, Easing.OutCubic);
+ background.MoveToX(400, 500, Easing.OutCubic);
+ break;
+ }
+
+ background.Expire();
+ background = newBackground;
+
+ playerContainer.Add(newBackground);
+ });
+ };
+ }
+
+ private void beatmapDisabledChanged(bool disabled)
+ {
+ if (disabled)
+ playlist.Hide();
+
+ playButton.Enabled.Value = !disabled;
+ prevButton.Enabled.Value = !disabled;
+ nextButton.Enabled.Value = !disabled;
+ playlistButton.Enabled.Value = !disabled;
+ }
+
+ protected override void Dispose(bool isDisposing)
+ {
+ base.Dispose(isDisposing);
+
+ if (musicController != null)
+ musicController.TrackChanged -= trackChanged;
+ }
+
+ private class MusicIconButton : IconButton
+ {
+ public MusicIconButton()
+ {
+ AutoSizeAxes = Axes.Both;
+ }
+
+ [BackgroundDependencyLoader]
+ private void load(OsuColour colours)
+ {
+ HoverColour = colours.YellowDark.Opacity(0.6f);
+ FlashColour = colours.Yellow;
+ }
+
+ protected override void LoadComplete()
+ {
+ base.LoadComplete();
+
+ // works with AutoSizeAxes above to make buttons autosize with the scale animation.
+ Content.AutoSizeAxes = Axes.None;
+ Content.Size = new Vector2(DEFAULT_BUTTON_SIZE);
+ }
+ }
+
+ private class Background : BufferedContainer
+ {
+ private readonly Sprite sprite;
+ private readonly WorkingBeatmap beatmap;
+
+ public Background(WorkingBeatmap beatmap = null)
+ {
+ this.beatmap = beatmap;
+ CacheDrawnFrameBuffer = true;
+ Depth = float.MaxValue;
+ RelativeSizeAxes = Axes.Both;
+
+ Children = new Drawable[]
+ {
+ sprite = new Sprite
+ {
+ RelativeSizeAxes = Axes.Both,
+ Colour = OsuColour.Gray(150),
+ FillMode = FillMode.Fill,
+ },
+ new Box
+ {
+ RelativeSizeAxes = Axes.X,
+ Height = bottom_black_area_height,
+ Origin = Anchor.BottomCentre,
+ Anchor = Anchor.BottomCentre,
+ Colour = Color4.Black.Opacity(0.5f)
+ }
+ };
+ }
+
+ [BackgroundDependencyLoader]
+ private void load(TextureStore textures)
+ {
+ sprite.Texture = beatmap?.Background ?? textures.Get(@"Backgrounds/bg4");
+ }
+ }
+
+ private class DragContainer : Container
+ {
+ protected override bool OnDragStart(DragStartEvent e)
+ {
+ return true;
+ }
+
+ protected override bool 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 : (float)Math.Pow(change.Length, 0.7f) / change.Length;
+
+ this.MoveTo(change);
+ return true;
+ }
+
+ protected override bool OnDragEnd(DragEndEvent e)
+ {
+ this.MoveTo(Vector2.Zero, 800, Easing.OutElastic);
+ return base.OnDragEnd(e);
+ }
+ }
+ }
+}
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/Overlays/Toolbar/ToolbarMusicButton.cs b/osu.Game/Overlays/Toolbar/ToolbarMusicButton.cs
index f03df2ed93..b29aec5842 100644
--- a/osu.Game/Overlays/Toolbar/ToolbarMusicButton.cs
+++ b/osu.Game/Overlays/Toolbar/ToolbarMusicButton.cs
@@ -14,7 +14,7 @@ namespace osu.Game.Overlays.Toolbar
}
[BackgroundDependencyLoader(true)]
- private void load(MusicController music)
+ private void load(NowPlayingOverlay music)
{
StateContainer = music;
}
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/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 b5266fd75d..e149c4338d 100644
--- a/osu.Game/osu.Game.csproj
+++ b/osu.Game/osu.Game.csproj
@@ -15,7 +15,7 @@
-
+
diff --git a/osu.iOS.props b/osu.iOS.props
index f2256d8d1c..9ea5081658 100644
--- a/osu.iOS.props
+++ b/osu.iOS.props
@@ -118,12 +118,12 @@
-
-
+
+
-
+