diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModHardRock.cs b/osu.Game.Rulesets.Osu/Mods/OsuModHardRock.cs index 32d2d40671..1569e868b2 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModHardRock.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModHardRock.cs @@ -26,9 +26,6 @@ namespace osu.Game.Rulesets.Osu.Mods if (slider == null) return; - slider.HeadCircle.Position = new Vector2(slider.HeadCircle.Position.X, OsuPlayfield.BASE_SIZE.Y - slider.HeadCircle.Position.Y); - slider.TailCircle.Position = new Vector2(slider.TailCircle.Position.X, OsuPlayfield.BASE_SIZE.Y - slider.TailCircle.Position.Y); - slider.NestedHitObjects.OfType().ForEach(h => h.Position = new Vector2(h.Position.X, OsuPlayfield.BASE_SIZE.Y - h.Position.Y)); slider.NestedHitObjects.OfType().ForEach(h => h.Position = new Vector2(h.Position.X, OsuPlayfield.BASE_SIZE.Y - h.Position.Y)); diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs index 2ac46a14f2..56c4ea639b 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs @@ -21,7 +21,11 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables protected DrawableOsuHitObject(OsuHitObject hitObject) : base(hitObject) { - base.AddInternal(shakeContainer = new ShakeContainer { RelativeSizeAxes = Axes.Both }); + base.AddInternal(shakeContainer = new ShakeContainer + { + ShakeDuration = 30, + RelativeSizeAxes = Axes.Both + }); Alpha = 0; } diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs index ecb0443f33..5cfc24bdde 100644 --- a/osu.Game.Rulesets.Osu/OsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs @@ -31,8 +31,8 @@ namespace osu.Game.Rulesets.Osu public override IEnumerable GetDefaultKeyBindings(int variant = 0) => new[] { - new KeyBinding(InputKey.A, OsuAction.LeftButton), - new KeyBinding(InputKey.S, OsuAction.RightButton), + new KeyBinding(InputKey.Z, OsuAction.LeftButton), + new KeyBinding(InputKey.X, OsuAction.RightButton), new KeyBinding(InputKey.MouseLeft, OsuAction.LeftButton), new KeyBinding(InputKey.MouseRight, OsuAction.RightButton), }; diff --git a/osu.Game.Tests/Visual/TestCaseAccountCreationOverlay.cs b/osu.Game.Tests/Visual/TestCaseAccountCreationOverlay.cs new file mode 100644 index 0000000000..c54ac448dd --- /dev/null +++ b/osu.Game.Tests/Visual/TestCaseAccountCreationOverlay.cs @@ -0,0 +1,32 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Collections.Generic; +using osu.Framework.Graphics.Containers; +using osu.Game.Overlays; +using osu.Game.Overlays.AccountCreation; + +namespace osu.Game.Tests.Visual +{ + public class TestCaseAccountCreationOverlay : OsuTestCase + { + public override IReadOnlyList RequiredTypes => new[] + { + typeof(ErrorTextFlowContainer), + typeof(AccountCreationBackground), + typeof(ScreenEntry), + typeof(ScreenWarning), + typeof(ScreenWelcome), + typeof(AccountCreationScreen), + }; + + public TestCaseAccountCreationOverlay() + { + var accountCreation = new AccountCreationOverlay(); + Child = accountCreation; + + accountCreation.State = Visibility.Visible; + } + } +} diff --git a/osu.Game.Tests/Visual/TestCaseDrawableDate.cs b/osu.Game.Tests/Visual/TestCaseDrawableDate.cs new file mode 100644 index 0000000000..2e38f5eb28 --- /dev/null +++ b/osu.Game.Tests/Visual/TestCaseDrawableDate.cs @@ -0,0 +1,67 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Tests.Visual +{ + public class TestCaseDrawableDate : OsuTestCase + { + public TestCaseDrawableDate() + { + Child = new FillFlowContainer + { + Direction = FillDirection.Vertical, + AutoSizeAxes = Axes.Both, + Origin = Anchor.Centre, + Anchor = Anchor.Centre, + Children = new Drawable[] + { + new PokeyDrawableDate(DateTimeOffset.Now.Subtract(TimeSpan.FromSeconds(60))), + new PokeyDrawableDate(DateTimeOffset.Now.Subtract(TimeSpan.FromSeconds(55))), + new PokeyDrawableDate(DateTimeOffset.Now.Subtract(TimeSpan.FromSeconds(50))), + new PokeyDrawableDate(DateTimeOffset.Now), + new PokeyDrawableDate(DateTimeOffset.Now.Add(TimeSpan.FromSeconds(60))), + new PokeyDrawableDate(DateTimeOffset.Now.Add(TimeSpan.FromSeconds(65))), + new PokeyDrawableDate(DateTimeOffset.Now.Add(TimeSpan.FromSeconds(70))), + } + }; + } + + private class PokeyDrawableDate : CompositeDrawable + { + public PokeyDrawableDate(DateTimeOffset date) + { + const float box_size = 10; + + DrawableDate drawableDate; + Box flash; + + AutoSizeAxes = Axes.Both; + InternalChildren = new Drawable[] + { + flash = new Box + { + Colour = Color4.Yellow, + Size = new Vector2(box_size), + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Alpha = 0 + }, + drawableDate = new DrawableDate(date) + { + X = box_size + 2, + } + }; + + drawableDate.Current.ValueChanged += v => flash.FadeOutFromOne(500); + } + } + } +} diff --git a/osu.Game.Tests/Visual/TestCaseLeaderboard.cs b/osu.Game.Tests/Visual/TestCaseLeaderboard.cs index f7630f0902..10d7eaee8a 100644 --- a/osu.Game.Tests/Visual/TestCaseLeaderboard.cs +++ b/osu.Game.Tests/Visual/TestCaseLeaderboard.cs @@ -11,6 +11,7 @@ using osu.Framework.Allocation; using osuTK; using System.Linq; using osu.Game.Beatmaps; +using osu.Game.Online.Leaderboards; using osu.Game.Rulesets; using osu.Game.Scoring; @@ -36,7 +37,7 @@ namespace osu.Game.Tests.Visual Origin = Anchor.Centre, Anchor = Anchor.Centre, Size = new Vector2(550f, 450f), - Scope = LeaderboardScope.Global, + Scope = BeatmapLeaderboardScope.Global, }); AddStep(@"New Scores", newScores); @@ -275,7 +276,7 @@ namespace osu.Game.Tests.Visual }; } - private class FailableLeaderboard : Leaderboard + private class FailableLeaderboard : BeatmapLeaderboard { public void SetRetrievalState(PlaceholderState state) { diff --git a/osu.Game.Tests/Visual/TestCaseResults.cs b/osu.Game.Tests/Visual/TestCaseResults.cs index 6a20a808b6..a954c6c57c 100644 --- a/osu.Game.Tests/Visual/TestCaseResults.cs +++ b/osu.Game.Tests/Visual/TestCaseResults.cs @@ -8,7 +8,9 @@ using osu.Framework.Allocation; using osu.Game.Beatmaps; using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; +using osu.Game.Screens.Play; using osu.Game.Screens.Ranking; +using osu.Game.Screens.Ranking.Pages; using osu.Game.Users; namespace osu.Game.Tests.Visual @@ -23,8 +25,8 @@ namespace osu.Game.Tests.Visual typeof(ScoreInfo), typeof(Results), typeof(ResultsPage), - typeof(ResultsPageScore), - typeof(ResultsPageRanking) + typeof(ScoreResultsPage), + typeof(RankingResultsPage) }; [BackgroundDependencyLoader] @@ -41,7 +43,7 @@ namespace osu.Game.Tests.Visual if (beatmapInfo != null) Beatmap.Value = beatmaps.GetWorkingBeatmap(beatmapInfo); - Add(new Results(new ScoreInfo + Add(new SoloResults(new ScoreInfo { TotalScore = 2845370, Accuracy = 0.98, diff --git a/osu.Game.Tests/Visual/TestCaseStandAloneChatDisplay.cs b/osu.Game.Tests/Visual/TestCaseStandAloneChatDisplay.cs index 16ce720ab1..b5ce1f1eaf 100644 --- a/osu.Game.Tests/Visual/TestCaseStandAloneChatDisplay.cs +++ b/osu.Game.Tests/Visual/TestCaseStandAloneChatDisplay.cs @@ -68,31 +68,33 @@ namespace osu.Game.Tests.Visual chatDisplay.Channel.Value = testChannel; chatDisplay2.Channel.Value = testChannel; - AddStep("message from admin", () => testChannel.AddLocalEcho(new LocalEchoMessage + int sequence = 0; + + AddStep("message from admin", () => testChannel.AddNewMessages(new Message(sequence++) { Sender = admin, Content = "I am a wang!" })); - AddStep("message from team red", () => testChannel.AddLocalEcho(new LocalEchoMessage + AddStep("message from team red", () => testChannel.AddNewMessages(new Message(sequence++) { Sender = redUser, Content = "I am team red." })); - AddStep("message from team red", () => testChannel.AddLocalEcho(new LocalEchoMessage + AddStep("message from team red", () => testChannel.AddNewMessages(new Message(sequence++) { Sender = redUser, Content = "I plan to win!" })); - AddStep("message from team blue", () => testChannel.AddLocalEcho(new LocalEchoMessage + AddStep("message from team blue", () => testChannel.AddNewMessages(new Message(sequence++) { Sender = blueUser, Content = "Not on my watch. Prepare to eat saaaaaaaaaand. Lots and lots of saaaaaaand." })); - AddStep("message from admin", () => testChannel.AddLocalEcho(new LocalEchoMessage + AddStep("message from admin", () => testChannel.AddNewMessages(new Message(sequence++) { Sender = admin, Content = "Okay okay, calm down guys. Let's do this!" diff --git a/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs b/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs index a24b6594e0..ad46e50344 100644 --- a/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs +++ b/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs @@ -26,7 +26,6 @@ namespace osu.Game.Graphics.Containers private PreviewTrackManager previewTrackManager; - protected readonly Bindable OverlayActivationMode = new Bindable(OverlayActivation.All); protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) diff --git a/osu.Game/Graphics/Containers/ShakeContainer.cs b/osu.Game/Graphics/Containers/ShakeContainer.cs index fde4d59f46..6b7b59bbd9 100644 --- a/osu.Game/Graphics/Containers/ShakeContainer.cs +++ b/osu.Game/Graphics/Containers/ShakeContainer.cs @@ -11,29 +11,43 @@ namespace osu.Game.Graphics.Containers /// public class ShakeContainer : Container { + /// + /// The length of a single shake. + /// + public float ShakeDuration = 80; + + /// + /// Total number of shakes. May be shortened if possible. + /// + public float TotalShakes = 4; + + /// + /// Pixels of displacement per shake. + /// + public float ShakeMagnitude = 8; + /// /// Shake the contents of this container. /// /// The maximum length the shake should last. - public void Shake(double maximumLength) + public void Shake(double? maximumLength = null) { const float shake_amount = 8; - const float shake_duration = 30; // if we don't have enough time, don't bother shaking. - if (maximumLength < shake_duration * 2) + if (maximumLength < ShakeDuration * 2) return; - var sequence = this.MoveToX(shake_amount, shake_duration / 2, Easing.OutSine).Then() - .MoveToX(-shake_amount, shake_duration, Easing.InOutSine).Then(); + var sequence = this.MoveToX(shake_amount, ShakeDuration / 2, Easing.OutSine).Then() + .MoveToX(-shake_amount, ShakeDuration, Easing.InOutSine).Then(); // if we don't have enough time for the second shake, skip it. - if (maximumLength > shake_duration * 4) + if (!maximumLength.HasValue || maximumLength >= ShakeDuration * 4) sequence = sequence - .MoveToX(shake_amount, shake_duration, Easing.InOutSine).Then() - .MoveToX(-shake_amount, shake_duration, Easing.InOutSine).Then(); + .MoveToX(shake_amount, ShakeDuration, Easing.InOutSine).Then() + .MoveToX(-shake_amount, ShakeDuration, Easing.InOutSine).Then(); - sequence.MoveToX(0, shake_duration / 2, Easing.InSine); + sequence.MoveToX(0, ShakeDuration / 2, Easing.InSine); } } } diff --git a/osu.Game/Graphics/DrawableDate.cs b/osu.Game/Graphics/DrawableDate.cs index 28f8bdf82f..87711c72c7 100644 --- a/osu.Game/Graphics/DrawableDate.cs +++ b/osu.Game/Graphics/DrawableDate.cs @@ -4,6 +4,7 @@ using System; using Humanizer; using osu.Framework.Allocation; +using osu.Framework.Graphics; using osu.Framework.Graphics.Cursor; using osu.Game.Graphics.Sprites; @@ -11,13 +12,27 @@ namespace osu.Game.Graphics { public class DrawableDate : OsuSpriteText, IHasTooltip { - protected readonly DateTimeOffset Date; + private DateTimeOffset date; + + public DateTimeOffset Date + { + get => date; + set + { + if (date == value) + return; + date = value.ToLocalTime(); + + if (LoadState >= LoadState.Ready) + updateTime(); + } + } public DrawableDate(DateTimeOffset date) { Font = "Exo2.0-RegularItalic"; - Date = date.ToLocalTime(); + Date = date; } [BackgroundDependencyLoader] @@ -39,14 +54,14 @@ namespace osu.Game.Graphics var diffToNow = DateTimeOffset.Now.Subtract(Date); double timeUntilNextUpdate = 1000; - if (diffToNow.TotalSeconds > 60) + if (Math.Abs(diffToNow.TotalSeconds) > 120) { timeUntilNextUpdate *= 60; - if (diffToNow.TotalMinutes > 60) + if (Math.Abs(diffToNow.TotalMinutes) > 120) { timeUntilNextUpdate *= 60; - if (diffToNow.TotalHours > 24) + if (Math.Abs(diffToNow.TotalHours) > 48) timeUntilNextUpdate *= 24; } } diff --git a/osu.Game/Graphics/UserInterface/LoadingAnimation.cs b/osu.Game/Graphics/UserInterface/LoadingAnimation.cs index e503436d47..292a9bd088 100644 --- a/osu.Game/Graphics/UserInterface/LoadingAnimation.cs +++ b/osu.Game/Graphics/UserInterface/LoadingAnimation.cs @@ -8,6 +8,9 @@ using osuTK.Graphics; namespace osu.Game.Graphics.UserInterface { + /// + /// A loading spinner. + /// public class LoadingAnimation : VisibilityContainer { private readonly SpriteIcon spinner; diff --git a/osu.Game/Graphics/UserInterface/ProcessingOverlay.cs b/osu.Game/Graphics/UserInterface/ProcessingOverlay.cs new file mode 100644 index 0000000000..0161c17ba9 --- /dev/null +++ b/osu.Game/Graphics/UserInterface/ProcessingOverlay.cs @@ -0,0 +1,56 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Input.Events; +using osuTK.Graphics; + +namespace osu.Game.Graphics.UserInterface +{ + /// + /// An overlay that will consume all available space and block input when required. + /// Useful for disabling all elements in a form and showing we are waiting on a response, for instance. + /// + public class ProcessingOverlay : VisibilityContainer + { + private const float transition_duration = 200; + + public ProcessingOverlay() + { + RelativeSizeAxes = Axes.Both; + } + + [BackgroundDependencyLoader] + private void load() + { + InternalChildren = new Drawable[] + { + new Box + { + Colour = Color4.Black, + RelativeSizeAxes = Axes.Both, + Alpha = 0.9f, + }, + new LoadingAnimation { State = Visibility.Visible } + }; + } + + protected override bool Handle(UIEvent e) + { + return true; + } + + protected override void PopIn() + { + this.FadeIn(transition_duration * 2, Easing.OutQuint); + } + + protected override void PopOut() + { + this.FadeOut(transition_duration, Easing.OutQuint); + } + } +} diff --git a/osu.Game/Online/API/APIAccess.cs b/osu.Game/Online/API/APIAccess.cs index cfbcf0326a..8038c1fc1f 100644 --- a/osu.Game/Online/API/APIAccess.cs +++ b/osu.Game/Online/API/APIAccess.cs @@ -5,7 +5,9 @@ using System; using System.Collections.Generic; using System.Diagnostics; using System.Net; +using System.Net.Http; using System.Threading; +using Newtonsoft.Json.Linq; using osu.Framework.Configuration; using osu.Framework.Graphics; using osu.Framework.Logging; @@ -159,7 +161,7 @@ namespace osu.Game.Online.API //hard bail if we can't get a valid access token. if (authentication.RequestAccessToken() == null) { - Logout(false); + Logout(); continue; } @@ -188,6 +190,39 @@ namespace osu.Game.Online.API this.password = password; } + public RegistrationRequest.RegistrationRequestErrors CreateAccount(string email, string username, string password) + { + Debug.Assert(State == APIState.Offline); + + var req = new RegistrationRequest + { + Url = $@"{Endpoint}/users", + Method = HttpMethod.Post, + Username = username, + Email = email, + Password = password + }; + + try + { + req.Perform(); + } + catch (Exception e) + { + try + { + return JObject.Parse(req.ResponseString).SelectToken("form_error", true).ToObject(); + } + catch + { + // if we couldn't deserialize the error message let's throw the original exception outwards. + throw e; + } + } + + return null; + } + /// /// Handle a single API request. /// Ensures all exceptions are caught and dealt with correctly. @@ -258,7 +293,7 @@ namespace osu.Game.Online.API switch (statusCode) { case HttpStatusCode.Unauthorized: - Logout(false); + Logout(); return true; case HttpStatusCode.RequestTimeout: failureCount++; @@ -307,10 +342,9 @@ namespace osu.Game.Online.API } } - public void Logout(bool clearUsername = true) + public void Logout() { flushQueue(); - if (clearUsername) ProvidedUsername = null; password = null; authentication.Clear(); LocalUser.Value = createGuestUser(); diff --git a/osu.Game/Online/API/RegistrationRequest.cs b/osu.Game/Online/API/RegistrationRequest.cs new file mode 100644 index 0000000000..f703927101 --- /dev/null +++ b/osu.Game/Online/API/RegistrationRequest.cs @@ -0,0 +1,41 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using Newtonsoft.Json; +using osu.Framework.IO.Network; + +namespace osu.Game.Online.API +{ + public class RegistrationRequest : WebRequest + { + internal string Username; + internal string Email; + internal string Password; + + protected override void PrePerform() + { + AddParameter("user[username]", Username); + AddParameter("user[user_email]", Email); + AddParameter("user[password]", Password); + + base.PrePerform(); + } + + public class RegistrationRequestErrors + { + public UserErrors User; + + public class UserErrors + { + [JsonProperty("username")] + public string[] Username; + + [JsonProperty("user_email")] + public string[] Email; + + [JsonProperty("password")] + public string[] Password; + } + } + } +} diff --git a/osu.Game/Online/API/Requests/GetScoresRequest.cs b/osu.Game/Online/API/Requests/GetScoresRequest.cs index 2751dd956b..ae2c7dc269 100644 --- a/osu.Game/Online/API/Requests/GetScoresRequest.cs +++ b/osu.Game/Online/API/Requests/GetScoresRequest.cs @@ -13,15 +13,15 @@ namespace osu.Game.Online.API.Requests public class GetScoresRequest : APIRequest { private readonly BeatmapInfo beatmap; - private readonly LeaderboardScope scope; + private readonly BeatmapLeaderboardScope scope; private readonly RulesetInfo ruleset; - public GetScoresRequest(BeatmapInfo beatmap, RulesetInfo ruleset, LeaderboardScope scope = LeaderboardScope.Global) + public GetScoresRequest(BeatmapInfo beatmap, RulesetInfo ruleset, BeatmapLeaderboardScope scope = BeatmapLeaderboardScope.Global) { if (!beatmap.OnlineBeatmapID.HasValue) throw new InvalidOperationException($"Cannot lookup a beatmap's scores without having a populated {nameof(BeatmapInfo.OnlineBeatmapID)}."); - if (scope == LeaderboardScope.Local) + if (scope == BeatmapLeaderboardScope.Local) throw new InvalidOperationException("Should not attempt to request online scores for a local scoped leaderboard"); this.beatmap = beatmap; diff --git a/osu.Game/Online/Chat/StandAloneChatDisplay.cs b/osu.Game/Online/Chat/StandAloneChatDisplay.cs index 332334a043..1ec138ab57 100644 --- a/osu.Game/Online/Chat/StandAloneChatDisplay.cs +++ b/osu.Game/Online/Chat/StandAloneChatDisplay.cs @@ -1,19 +1,15 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using System.Collections.Generic; -using System.Linq; +using System; using osu.Framework.Allocation; using osu.Framework.Configuration; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.UserInterface; -using osu.Game.Graphics; -using osu.Game.Graphics.Containers; -using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; -using osuTK; +using osu.Game.Overlays.Chat; using osuTK.Graphics; namespace osu.Game.Online.Chat @@ -23,22 +19,27 @@ namespace osu.Game.Online.Chat /// public class StandAloneChatDisplay : CompositeDrawable { + private readonly bool postingTextbox; + public readonly Bindable Channel = new Bindable(); - private readonly FillFlowContainer messagesFlow; - - private Channel lastChannel; - private readonly FocusedTextBox textbox; protected ChannelManager ChannelManager; + private ScrollContainer scroll; + + private DrawableChannel drawableChannel; + + private const float textbox_height = 30; + /// /// Construct a new instance. /// /// Whether a textbox for posting new messages should be displayed. public StandAloneChatDisplay(bool postingTextbox = false) { + this.postingTextbox = postingTextbox; CornerRadius = 10; Masking = true; @@ -50,23 +51,10 @@ namespace osu.Game.Online.Chat Alpha = 0.8f, RelativeSizeAxes = Axes.Both }, - messagesFlow = new FillFlowContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - LayoutEasing = Easing.Out, - LayoutDuration = 500, - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - Direction = FillDirection.Vertical - } }; - const float textbox_height = 30; - if (postingTextbox) { - messagesFlow.Y -= textbox_height; AddInternal(textbox = new FocusedTextBox { RelativeSizeAxes = Axes.X, @@ -117,112 +105,43 @@ namespace osu.Game.Online.Chat this.MoveToY(100, 500, Easing.In); } - protected virtual Drawable CreateMessage(Message message) - { - return new StandAloneMessage(message); - } + protected virtual ChatLine CreateMessage(Message message) => new StandAloneMessage(message); private void channelChanged(Channel channel) { - if (lastChannel != null) - lastChannel.NewMessagesArrived -= newMessages; - - lastChannel = channel; - messagesFlow.Clear(); + drawableChannel?.Expire(); if (channel == null) return; - channel.NewMessagesArrived += newMessages; - - newMessages(channel.Messages); + AddInternal(drawableChannel = new StandAloneDrawableChannel(channel) + { + CreateChatLineAction = CreateMessage, + Padding = new MarginPadding { Bottom = postingTextbox ? textbox_height : 0 } + }); } - private void newMessages(IEnumerable messages) + protected class StandAloneDrawableChannel : DrawableChannel { - var excessChildren = messagesFlow.Children.Count - 10; - if (excessChildren > 0) - foreach (var c in messagesFlow.Children.Take(excessChildren)) - c.Expire(); + public Func CreateChatLineAction; - foreach (var message in messages) + protected override ChatLine CreateChatLine(Message m) => CreateChatLineAction(m); + + public StandAloneDrawableChannel(Channel channel) + : base(channel) { - var formatted = MessageFormatter.FormatMessage(message); - var drawable = CreateMessage(formatted); - drawable.Y = messagesFlow.Height; - messagesFlow.Add(drawable); + ChatLineFlow.Padding = new MarginPadding { Horizontal = 0 }; } } - protected class StandAloneMessage : CompositeDrawable + protected class StandAloneMessage : ChatLine { - protected readonly Message Message; - protected OsuSpriteText SenderText; - protected Circle ColourBox; + protected override float TextSize => 15; - public StandAloneMessage(Message message) + protected override float HorizontalPadding => 10; + protected override float MessagePadding => 120; + + public StandAloneMessage(Message message) : base(message) { - Message = message; - } - - [BackgroundDependencyLoader] - private void load() - { - Margin = new MarginPadding(3); - - RelativeSizeAxes = Axes.X; - AutoSizeAxes = Axes.Y; - - InternalChildren = new Drawable[] - { - new FillFlowContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Direction = FillDirection.Horizontal, - Children = new Drawable[] - { - new Container - { - RelativeSizeAxes = Axes.X, - Width = 0.2f, - Children = new Drawable[] - { - SenderText = new OsuSpriteText - { - Font = @"Exo2.0-Bold", - Anchor = Anchor.TopRight, - Origin = Anchor.TopRight, - Text = Message.Sender.ToString() - } - } - }, - new Container - { - Size = new Vector2(8, OsuSpriteText.FONT_SIZE), - Margin = new MarginPadding { Horizontal = 3 }, - Children = new Drawable[] - { - ColourBox = new Circle - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Size = new Vector2(8) - } - } - }, - new OsuTextFlowContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Width = 0.5f, - Text = Message.DisplayContent - } - } - } - }; - - if (!string.IsNullOrEmpty(Message.Sender.Colour)) - SenderText.Colour = ColourBox.Colour = OsuColour.FromHex(Message.Sender.Colour); } } } diff --git a/osu.Game/Screens/Select/Leaderboards/DrawableRank.cs b/osu.Game/Online/Leaderboards/DrawableRank.cs similarity index 96% rename from osu.Game/Screens/Select/Leaderboards/DrawableRank.cs rename to osu.Game/Online/Leaderboards/DrawableRank.cs index 3258a62adf..1c68c64180 100644 --- a/osu.Game/Screens/Select/Leaderboards/DrawableRank.cs +++ b/osu.Game/Online/Leaderboards/DrawableRank.cs @@ -2,14 +2,14 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using osu.Framework.Allocation; +using osu.Framework.Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; -using osu.Framework.Extensions; using osu.Game.Scoring; -namespace osu.Game.Screens.Select.Leaderboards +namespace osu.Game.Online.Leaderboards { public class DrawableRank : Container { diff --git a/osu.Game/Screens/Select/Leaderboards/Leaderboard.cs b/osu.Game/Online/Leaderboards/Leaderboard.cs similarity index 77% rename from osu.Game/Screens/Select/Leaderboards/Leaderboard.cs rename to osu.Game/Online/Leaderboards/Leaderboard.cs index a65cc6f096..d9d78245bb 100644 --- a/osu.Game/Screens/Select/Leaderboards/Leaderboard.cs +++ b/osu.Game/Online/Leaderboards/Leaderboard.cs @@ -3,27 +3,22 @@ using System; using System.Collections.Generic; -using osuTK; -using osuTK.Graphics; +using System.Linq; using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; using osu.Framework.Threading; -using osu.Game.Beatmaps; using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterface; using osu.Game.Online.API; -using osu.Game.Online.API.Requests; -using System.Linq; -using osu.Framework.Configuration; -using osu.Game.Rulesets; -using osu.Game.Scoring; +using osuTK; +using osuTK.Graphics; -namespace osu.Game.Screens.Select.Leaderboards +namespace osu.Game.Online.Leaderboards { - public class Leaderboard : Container + public abstract class Leaderboard : Container { private const double fade_duration = 300; @@ -32,10 +27,6 @@ namespace osu.Game.Screens.Select.Leaderboards private FillFlowContainer scrollFlow; - private readonly IBindable ruleset = new Bindable(); - - public Action ScoreSelected; - private readonly LoadingAnimation loading; private ScheduledDelegate showScoresDelegate; @@ -70,7 +61,7 @@ namespace osu.Game.Screens.Select.Leaderboards AutoSizeAxes = Axes.Y, Spacing = new Vector2(0f, 5f), Padding = new MarginPadding { Top = 10, Bottom = 5 }, - ChildrenEnumerable = scores.Select((s, index) => new LeaderboardScore(s, index + 1) { Action = () => ScoreSelected?.Invoke(s) }) + ChildrenEnumerable = scores.Select((s, index) => CreateDrawableScore(s, index + 1)) }; // schedule because we may not be loaded yet (LoadComponentAsync complains). @@ -96,18 +87,18 @@ namespace osu.Game.Screens.Select.Leaderboards } } - private LeaderboardScope scope; + private TScope scope; - public LeaderboardScope Scope + public TScope Scope { get { return scope; } set { - if (value == scope) + if (value.Equals(scope)) return; scope = value; - updateScores(); + UpdateScores(); } } @@ -137,7 +128,7 @@ namespace osu.Game.Screens.Select.Leaderboards case PlaceholderState.NetworkFailure: replacePlaceholder(new RetrievalFailurePlaceholder { - OnRetry = updateScores, + OnRetry = UpdateScores, }); break; case PlaceholderState.Unavailable: @@ -159,7 +150,7 @@ namespace osu.Game.Screens.Select.Leaderboards } } - public Leaderboard() + protected Leaderboard() { Children = new Drawable[] { @@ -177,36 +168,14 @@ namespace osu.Game.Screens.Select.Leaderboards } private APIAccess api; - private BeatmapInfo beatmap; - - [Resolved] - private ScoreManager scoreManager { get; set; } private ScheduledDelegate pendingUpdateScores; - public BeatmapInfo Beatmap - { - get { return beatmap; } - set - { - if (beatmap == value) - return; - - beatmap = value; - Scores = null; - - updateScores(); - } - } - - [BackgroundDependencyLoader(permitNulls: true)] - private void load(APIAccess api, IBindable parentRuleset) + [BackgroundDependencyLoader(true)] + private void load(APIAccess api) { this.api = api; - ruleset.BindTo(parentRuleset); - ruleset.ValueChanged += _ => updateScores(); - if (api != null) api.OnStateChange += handleApiStateChange; } @@ -219,21 +188,17 @@ namespace osu.Game.Screens.Select.Leaderboards api.OnStateChange -= handleApiStateChange; } - public void RefreshScores() => updateScores(); + public void RefreshScores() => UpdateScores(); - private GetScoresRequest getScoresRequest; + private APIRequest getScoresRequest; private void handleApiStateChange(APIState oldState, APIState newState) { - if (Scope == LeaderboardScope.Local) - // No need to respond to API state change while current scope is local - return; - if (newState == APIState.Online) - updateScores(); + UpdateScores(); } - private void updateScores() + protected void UpdateScores() { // don't display any scores or placeholder until the first Scores_Set has been called. // this avoids scope changes flickering a "no scores" placeholder before initialisation of song select is finished. @@ -245,40 +210,23 @@ namespace osu.Game.Screens.Select.Leaderboards pendingUpdateScores?.Cancel(); pendingUpdateScores = Schedule(() => { - if (Scope == LeaderboardScope.Local) - { - Scores = scoreManager.QueryScores(s => s.Beatmap.ID == Beatmap.ID).ToArray(); - PlaceholderState = Scores.Any() ? PlaceholderState.Successful : PlaceholderState.NoScores; - return; - } - - if (Beatmap?.OnlineBeatmapID == null) - { - PlaceholderState = PlaceholderState.Unavailable; - return; - } - if (api?.IsLoggedIn != true) { PlaceholderState = PlaceholderState.NotLoggedIn; return; } - if (Scope != LeaderboardScope.Global && !api.LocalUser.Value.IsSupporter) - { - PlaceholderState = PlaceholderState.NotSupporter; - return; - } - PlaceholderState = PlaceholderState.Retrieving; loading.Show(); - getScoresRequest = new GetScoresRequest(Beatmap, ruleset.Value ?? Beatmap.Ruleset, Scope); - getScoresRequest.Success += r => Schedule(() => + getScoresRequest = FetchScores(scores => Schedule(() => { - Scores = r.Scores; + Scores = scores; PlaceholderState = Scores.Any() ? PlaceholderState.Successful : PlaceholderState.NoScores; - }); + })); + + if (getScoresRequest == null) + return; getScoresRequest.Failure += e => Schedule(() => { @@ -292,6 +240,8 @@ namespace osu.Game.Screens.Select.Leaderboards }); } + protected abstract APIRequest FetchScores(Action> scoresCallback); + private Placeholder currentPlaceholder; private void replacePlaceholder(Placeholder placeholder) @@ -344,5 +294,7 @@ namespace osu.Game.Screens.Select.Leaderboards } } } + + protected abstract LeaderboardScore CreateDrawableScore(ScoreInfo model, int index); } } diff --git a/osu.Game/Screens/Select/Leaderboards/LeaderboardScore.cs b/osu.Game/Online/Leaderboards/LeaderboardScore.cs similarity index 87% rename from osu.Game/Screens/Select/Leaderboards/LeaderboardScore.cs rename to osu.Game/Online/Leaderboards/LeaderboardScore.cs index 1ba529c0bf..0c64105d5c 100644 --- a/osu.Game/Screens/Select/Leaderboards/LeaderboardScore.cs +++ b/osu.Game/Online/Leaderboards/LeaderboardScore.cs @@ -1,9 +1,8 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +using System.Collections.Generic; using System.Linq; -using osuTK; -using osuTK.Graphics; using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; @@ -17,35 +16,40 @@ using osu.Game.Graphics.Sprites; using osu.Game.Rulesets.UI; using osu.Game.Scoring; using osu.Game.Users; +using osuTK; +using osuTK.Graphics; -namespace osu.Game.Screens.Select.Leaderboards +namespace osu.Game.Online.Leaderboards { public class LeaderboardScore : OsuClickableContainer { - public static readonly float HEIGHT = 60; - public readonly int RankPosition; - public readonly ScoreInfo Score; + + public const float HEIGHT = 60; private const float corner_radius = 5; private const float edge_margin = 5; private const float background_alpha = 0.25f; private const float rank_width = 30; + protected Container RankContainer { get; private set; } + + private readonly ScoreInfo score; + private Box background; private Container content; private Drawable avatar; - private DrawableRank scoreRank; + private Drawable scoreRank; private OsuSpriteText nameLabel; private GlowingSpriteText scoreLabel; - private ScoreComponentLabel maxCombo; - private ScoreComponentLabel accuracy; private Container flagBadgeContainer; private FillFlowContainer modsContainer; + private List statisticsLabels; + public LeaderboardScore(ScoreInfo score, int rank) { - Score = score; + this.score = score; RankPosition = rank; RelativeSizeAxes = Axes.X; @@ -55,6 +59,10 @@ namespace osu.Game.Screens.Select.Leaderboards [BackgroundDependencyLoader] private void load() { + var user = score.User; + + statisticsLabels = GetStatistics(score).Select(s => new ScoreComponentLabel(s)).ToList(); + Children = new Drawable[] { new Container @@ -102,7 +110,7 @@ namespace osu.Game.Screens.Select.Leaderboards Children = new[] { avatar = new DelayedLoadWrapper( - new Avatar(Score.User) + new Avatar(user) { RelativeSizeAxes = Axes.Both, CornerRadius = corner_radius, @@ -128,7 +136,7 @@ namespace osu.Game.Screens.Select.Leaderboards { nameLabel = new OsuSpriteText { - Text = Score.User.Username, + Text = user.Username, Font = @"Exo2.0-BoldItalic", TextSize = 23, }, @@ -149,7 +157,7 @@ namespace osu.Game.Screens.Select.Leaderboards Masking = true, Children = new Drawable[] { - new DrawableFlag(Score.User?.Country) + new DrawableFlag(user.Country) { Width = 30, RelativeSizeAxes = Axes.Y, @@ -164,11 +172,7 @@ namespace osu.Game.Screens.Select.Leaderboards Direction = FillDirection.Horizontal, Spacing = new Vector2(10f, 0f), Margin = new MarginPadding { Left = edge_margin }, - Children = new Drawable[] - { - maxCombo = new ScoreComponentLabel(FontAwesome.fa_link, Score.MaxCombo.ToString(), "Max Combo"), - accuracy = new ScoreComponentLabel(FontAwesome.fa_crosshairs, string.Format(Score.Accuracy % 1 == 0 ? @"{0:P0}" : @"{0:P2}", Score.Accuracy), "Accuracy"), - }, + Children = statisticsLabels }, }, }, @@ -183,17 +187,17 @@ namespace osu.Game.Screens.Select.Leaderboards Spacing = new Vector2(5f, 0f), Children = new Drawable[] { - scoreLabel = new GlowingSpriteText(Score.TotalScore.ToString(@"N0"), @"Venera", 23, Color4.White, OsuColour.FromHex(@"83ccfa")), - new Container + scoreLabel = new GlowingSpriteText(score.TotalScore.ToString(@"N0"), @"Venera", 23, Color4.White, OsuColour.FromHex(@"83ccfa")), + RankContainer = new Container { Size = new Vector2(40f, 20f), Children = new[] { - scoreRank = new DrawableRank(Score.Rank) + scoreRank = new DrawableRank(score.Rank) { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Size = new Vector2(40f), + Size = new Vector2(40f) }, }, }, @@ -205,7 +209,7 @@ namespace osu.Game.Screens.Select.Leaderboards Origin = Anchor.BottomRight, AutoSizeAxes = Axes.Both, Direction = FillDirection.Horizontal, - ChildrenEnumerable = Score.Mods.Select(mod => new ModIcon(mod) { Scale = new Vector2(0.375f) }) + ChildrenEnumerable = score.Mods.Select(mod => new ModIcon(mod) { Scale = new Vector2(0.375f) }) }, }, }, @@ -216,7 +220,7 @@ namespace osu.Game.Screens.Select.Leaderboards public override void Show() { - foreach (var d in new[] { avatar, nameLabel, scoreLabel, scoreRank, flagBadgeContainer, maxCombo, accuracy, modsContainer }) + foreach (var d in new[] { avatar, nameLabel, scoreLabel, scoreRank, flagBadgeContainer, modsContainer }.Concat(statisticsLabels)) d.FadeOut(); Alpha = 0; @@ -243,7 +247,7 @@ namespace osu.Game.Screens.Select.Leaderboards using (BeginDelayedSequence(50, true)) { - var drawables = new Drawable[] { flagBadgeContainer, maxCombo, accuracy, modsContainer, }; + var drawables = new Drawable[] { flagBadgeContainer, modsContainer }.Concat(statisticsLabels).ToArray(); for (int i = 0; i < drawables.Length; i++) drawables[i].FadeIn(100 + i * 50); } @@ -251,6 +255,12 @@ namespace osu.Game.Screens.Select.Leaderboards } } + protected virtual IEnumerable GetStatistics(ScoreInfo model) => new[] + { + new LeaderboardScoreStatistic(FontAwesome.fa_link, "Max Combo", model.MaxCombo.ToString()), + new LeaderboardScoreStatistic(FontAwesome.fa_crosshairs, "Accuracy", string.Format(model.Accuracy % 1 == 0 ? @"{0:P0}" : @"{0:P2}", model.Accuracy)) + }; + protected override bool OnHover(HoverEvent e) { background.FadeTo(0.5f, 300, Easing.OutQuint); @@ -321,11 +331,10 @@ namespace osu.Game.Screens.Select.Leaderboards public string TooltipText => name; - public ScoreComponentLabel(FontAwesome icon, string value, string name) + public ScoreComponentLabel(LeaderboardScoreStatistic statistic) { - this.name = name; - AutoSizeAxes = Axes.Y; - Width = 60; + name = statistic.Name; + AutoSizeAxes = Axes.Both; Child = content = new FillFlowContainer { @@ -356,11 +365,11 @@ namespace osu.Game.Screens.Select.Leaderboards Origin = Anchor.Centre, Size = new Vector2(icon_size - 6), Colour = OsuColour.FromHex(@"a4edff"), - Icon = icon, + Icon = statistic.Icon, }, }, }, - new GlowingSpriteText(value, @"Exo2.0-Bold", 17, Color4.White, OsuColour.FromHex(@"83ccfa")) + new GlowingSpriteText(statistic.Value, @"Exo2.0-Bold", 17, Color4.White, OsuColour.FromHex(@"83ccfa")) { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, @@ -369,5 +378,19 @@ namespace osu.Game.Screens.Select.Leaderboards }; } } + + public class LeaderboardScoreStatistic + { + public FontAwesome Icon; + public string Value; + public string Name; + + public LeaderboardScoreStatistic(FontAwesome icon, string name, string value) + { + Icon = icon; + Name = name; + Value = value; + } + } } } diff --git a/osu.Game/Screens/Select/Leaderboards/MessagePlaceholder.cs b/osu.Game/Online/Leaderboards/MessagePlaceholder.cs similarity index 94% rename from osu.Game/Screens/Select/Leaderboards/MessagePlaceholder.cs rename to osu.Game/Online/Leaderboards/MessagePlaceholder.cs index f01a55b662..ea92836e6e 100644 --- a/osu.Game/Screens/Select/Leaderboards/MessagePlaceholder.cs +++ b/osu.Game/Online/Leaderboards/MessagePlaceholder.cs @@ -4,7 +4,7 @@ using osu.Framework.Graphics; using osu.Game.Graphics; -namespace osu.Game.Screens.Select.Leaderboards +namespace osu.Game.Online.Leaderboards { public class MessagePlaceholder : Placeholder { diff --git a/osu.Game/Screens/Select/Leaderboards/Placeholder.cs b/osu.Game/Online/Leaderboards/Placeholder.cs similarity index 94% rename from osu.Game/Screens/Select/Leaderboards/Placeholder.cs rename to osu.Game/Online/Leaderboards/Placeholder.cs index 468b43e54f..4994ce0e99 100644 --- a/osu.Game/Screens/Select/Leaderboards/Placeholder.cs +++ b/osu.Game/Online/Leaderboards/Placeholder.cs @@ -5,7 +5,7 @@ using System; using osu.Framework.Graphics; using osu.Game.Graphics.Containers; -namespace osu.Game.Screens.Select.Leaderboards +namespace osu.Game.Online.Leaderboards { public abstract class Placeholder : OsuTextFlowContainer, IEquatable { diff --git a/osu.Game/Screens/Select/Leaderboards/PlaceholderState.cs b/osu.Game/Online/Leaderboards/PlaceholderState.cs similarity index 88% rename from osu.Game/Screens/Select/Leaderboards/PlaceholderState.cs rename to osu.Game/Online/Leaderboards/PlaceholderState.cs index 33a56540f3..504b03432f 100644 --- a/osu.Game/Screens/Select/Leaderboards/PlaceholderState.cs +++ b/osu.Game/Online/Leaderboards/PlaceholderState.cs @@ -1,7 +1,7 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -namespace osu.Game.Screens.Select.Leaderboards +namespace osu.Game.Online.Leaderboards { public enum PlaceholderState { diff --git a/osu.Game/Screens/Select/Leaderboards/RetrievalFailurePlaceholder.cs b/osu.Game/Online/Leaderboards/RetrievalFailurePlaceholder.cs similarity index 97% rename from osu.Game/Screens/Select/Leaderboards/RetrievalFailurePlaceholder.cs rename to osu.Game/Online/Leaderboards/RetrievalFailurePlaceholder.cs index 66a7793f7c..7fed40ed1a 100644 --- a/osu.Game/Screens/Select/Leaderboards/RetrievalFailurePlaceholder.cs +++ b/osu.Game/Online/Leaderboards/RetrievalFailurePlaceholder.cs @@ -8,7 +8,7 @@ using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osuTK; -namespace osu.Game.Screens.Select.Leaderboards +namespace osu.Game.Online.Leaderboards { public class RetrievalFailurePlaceholder : Placeholder { diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 31a00e68ac..4930f75620 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -61,6 +61,8 @@ namespace osu.Game private DialogOverlay dialogOverlay; + private AccountCreationOverlay accountCreation; + private DirectOverlay direct; private SocialOverlay social; @@ -185,7 +187,13 @@ namespace osu.Game } private ExternalLinkOpener externalLinkOpener; - public void OpenUrlExternally(string url) => externalLinkOpener.OpenUrlExternally(url); + public void OpenUrlExternally(string url) + { + if (url.StartsWith("/")) + url = $"{API.Endpoint}{url}"; + + externalLinkOpener.OpenUrlExternally(url); + } private ScheduledDelegate scoreLoad; @@ -400,11 +408,21 @@ namespace osu.Game Origin = Anchor.TopRight, }, overlayContent.Add); - loadComponentSingleFile(dialogOverlay = new DialogOverlay + loadComponentSingleFile(accountCreation = new AccountCreationOverlay { Depth = -6, }, overlayContent.Add); + loadComponentSingleFile(dialogOverlay = new DialogOverlay + { + Depth = -7, + }, overlayContent.Add); + + loadComponentSingleFile(externalLinkOpener = new ExternalLinkOpener + { + Depth = -8, + }, overlayContent.Add); + dependencies.Cache(idleTracker); dependencies.Cache(settings); dependencies.Cache(onscreenDisplay); @@ -417,6 +435,7 @@ namespace osu.Game dependencies.Cache(beatmapSetOverlay); dependencies.Cache(notifications); dependencies.Cache(dialogOverlay); + dependencies.Cache(accountCreation); chatOverlay.StateChanged += state => channelManager.HighPollRate.Value = state == Visibility.Visible; diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 2729676504..0f1819d55b 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -60,6 +60,8 @@ namespace osu.Game protected RulesetConfigCache RulesetConfigCache; + protected APIAccess API; + protected MenuCursorContainer MenuCursorContainer; private Container content; @@ -146,14 +148,14 @@ namespace osu.Game dependencies.Cache(SkinManager = new SkinManager(Host.Storage, contextFactory, Host, Audio)); dependencies.CacheAs(SkinManager); - var api = new APIAccess(LocalConfig); + API = new APIAccess(LocalConfig); - dependencies.Cache(api); - dependencies.CacheAs(api); + dependencies.Cache(API); + dependencies.CacheAs(API); dependencies.Cache(RulesetStore = new RulesetStore(contextFactory)); dependencies.Cache(FileStore = new FileStore(contextFactory, Host.Storage)); - dependencies.Cache(BeatmapManager = new BeatmapManager(Host.Storage, contextFactory, RulesetStore, api, Audio, Host)); + dependencies.Cache(BeatmapManager = new BeatmapManager(Host.Storage, contextFactory, RulesetStore, API, Audio, Host)); dependencies.Cache(ScoreManager = new ScoreManager(RulesetStore, BeatmapManager, Host.Storage, contextFactory, Host)); dependencies.Cache(KeyBindingStore = new KeyBindingStore(contextFactory, RulesetStore)); dependencies.Cache(SettingsStore = new SettingsStore(contextFactory)); @@ -177,7 +179,7 @@ namespace osu.Game FileStore.Cleanup(); - AddInternal(api); + AddInternal(API); GlobalActionContainer globalBinding; diff --git a/osu.Game/Overlays/AccountCreation/AccountCreationBackground.cs b/osu.Game/Overlays/AccountCreation/AccountCreationBackground.cs new file mode 100644 index 0000000000..d1686912c5 --- /dev/null +++ b/osu.Game/Overlays/AccountCreation/AccountCreationBackground.cs @@ -0,0 +1,48 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Graphics.Textures; + +namespace osu.Game.Overlays.AccountCreation +{ + public class AccountCreationBackground : Sprite + { + public AccountCreationBackground() + { + FillMode = FillMode.Fill; + RelativeSizeAxes = Axes.Both; + + Anchor = Anchor.CentreRight; + Origin = Anchor.CentreRight; + } + + [BackgroundDependencyLoader] + private void load(LargeTextureStore textures) + { + Texture = textures.Get("Backgrounds/registration"); + } + + protected override void LoadComplete() + { + const float x_movement = 80; + + const float initial_move_time = 5000; + const float loop_move_time = 10000; + + base.LoadComplete(); + this.FadeInFromZero(initial_move_time / 4, Easing.OutQuint); + this.MoveToX(x_movement / 2).MoveToX(0, initial_move_time, Easing.OutQuint); + + using (BeginDelayedSequence(initial_move_time)) + { + this + .MoveToX(x_movement, loop_move_time, Easing.InOutSine) + .Then().MoveToX(0, loop_move_time, Easing.InOutSine) + .Loop(); + } + } + } +} diff --git a/osu.Game/Overlays/AccountCreation/AccountCreationScreen.cs b/osu.Game/Overlays/AccountCreation/AccountCreationScreen.cs new file mode 100644 index 0000000000..371428d988 --- /dev/null +++ b/osu.Game/Overlays/AccountCreation/AccountCreationScreen.cs @@ -0,0 +1,29 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Graphics; +using osu.Framework.Screens; + +namespace osu.Game.Overlays.AccountCreation +{ + public abstract class AccountCreationScreen : Screen + { + protected override void OnEntering(Screen last) + { + base.OnEntering(last); + Content.FadeOut().Delay(200).FadeIn(200); + } + + protected override void OnResuming(Screen last) + { + base.OnResuming(last); + Content.FadeIn(200); + } + + protected override void OnSuspending(Screen next) + { + base.OnSuspending(next); + Content.FadeOut(200); + } + } +} diff --git a/osu.Game/Overlays/AccountCreation/ErrorTextFlowContainer.cs b/osu.Game/Overlays/AccountCreation/ErrorTextFlowContainer.cs new file mode 100644 index 0000000000..9ff6a5a7ef --- /dev/null +++ b/osu.Game/Overlays/AccountCreation/ErrorTextFlowContainer.cs @@ -0,0 +1,35 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.Collections.Generic; +using osu.Framework.Graphics; +using osu.Game.Graphics.Containers; +using osuTK.Graphics; + +namespace osu.Game.Overlays.AccountCreation +{ + public class ErrorTextFlowContainer : OsuTextFlowContainer + { + private readonly List errorDrawables = new List(); + + public ErrorTextFlowContainer() + : base(cp => { cp.TextSize = 12; }) + { + } + + public void ClearErrors() + { + errorDrawables.ForEach(d => d.Expire()); + } + + public void AddErrors(string[] errors) + { + ClearErrors(); + + if (errors == null) return; + + foreach (var error in errors) + errorDrawables.AddRange(AddParagraph(error, cp => cp.Colour = Color4.Red)); + } + } +} diff --git a/osu.Game/Overlays/AccountCreation/ScreenEntry.cs b/osu.Game/Overlays/AccountCreation/ScreenEntry.cs new file mode 100644 index 0000000000..bfc437f763 --- /dev/null +++ b/osu.Game/Overlays/AccountCreation/ScreenEntry.cs @@ -0,0 +1,220 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using osu.Framework.Allocation; +using osu.Framework.Extensions.IEnumerableExtensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.MathUtils; +using osu.Framework.Screens; +using osu.Game.Graphics; +using osu.Game.Graphics.Containers; +using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; +using osu.Game.Online.API; +using osu.Game.Overlays.Settings; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Overlays.AccountCreation +{ + public class ScreenEntry : AccountCreationScreen + { + private ErrorTextFlowContainer usernameDescription; + private ErrorTextFlowContainer emailAddressDescription; + private ErrorTextFlowContainer passwordDescription; + + private OsuTextBox usernameTextBox; + private OsuTextBox emailTextBox; + private OsuPasswordTextBox passwordTextBox; + + private APIAccess api; + private ShakeContainer registerShake; + private IEnumerable characterCheckText; + + private OsuTextBox[] textboxes; + private ProcessingOverlay processingOverlay; + + [BackgroundDependencyLoader] + private void load(OsuColour colours, APIAccess api) + { + this.api = api; + + Children = new Drawable[] + { + new FillFlowContainer + { + RelativeSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Padding = new MarginPadding(20), + Spacing = new Vector2(0, 10), + Children = new Drawable[] + { + new OsuSpriteText + { + TextSize = 20, + Margin = new MarginPadding { Vertical = 10 }, + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Text = "Let's create an account!", + }, + usernameTextBox = new OsuTextBox + { + PlaceholderText = "username", + RelativeSizeAxes = Axes.X, + TabbableContentContainer = this + }, + usernameDescription = new ErrorTextFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y + }, + emailTextBox = new OsuTextBox + { + PlaceholderText = "email address", + RelativeSizeAxes = Axes.X, + TabbableContentContainer = this + }, + emailAddressDescription = new ErrorTextFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y + }, + passwordTextBox = new OsuPasswordTextBox + { + PlaceholderText = "password", + RelativeSizeAxes = Axes.X, + TabbableContentContainer = this, + }, + passwordDescription = new ErrorTextFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y + }, + new Container + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Children = new Drawable[] + { + registerShake = new ShakeContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Child = new SettingsButton + { + Text = "Register", + Margin = new MarginPadding { Vertical = 20 }, + Action = performRegistration + } + } + } + }, + }, + }, + processingOverlay = new ProcessingOverlay { Alpha = 0 } + }; + + textboxes = new[] { usernameTextBox, emailTextBox, passwordTextBox }; + + usernameDescription.AddText("This will be your public presence. No profanity, no impersonation. Avoid exposing your own personal details, too!"); + + emailAddressDescription.AddText("Will be used for notifications, account verification and in the case you forget your password. No spam, ever."); + emailAddressDescription.AddText(" Make sure to get it right!", cp => cp.Font = "Exo2.0-Bold"); + + passwordDescription.AddText("At least "); + characterCheckText = passwordDescription.AddText("8 characters long"); + passwordDescription.AddText(". Choose something long but also something you will remember, like a line from your favourite song."); + + passwordTextBox.Current.ValueChanged += text => { characterCheckText.ForEach(s => s.Colour = text.Length == 0 ? Color4.White : Interpolation.ValueAt(text.Length, Color4.OrangeRed, Color4.YellowGreen, 0, 8, Easing.In)); }; + } + + protected override void Update() + { + base.Update(); + + if (!textboxes.Any(t => t.HasFocus)) + focusNextTextbox(); + } + + protected override void OnEntering(Screen last) + { + base.OnEntering(last); + processingOverlay.Hide(); + } + + private void performRegistration() + { + if (focusNextTextbox()) + { + registerShake.Shake(); + return; + } + + usernameDescription.ClearErrors(); + emailAddressDescription.ClearErrors(); + passwordDescription.ClearErrors(); + + processingOverlay.Show(); + + Task.Run(() => + { + bool success; + RegistrationRequest.RegistrationRequestErrors errors = null; + + try + { + errors = api.CreateAccount(emailTextBox.Text, usernameTextBox.Text, passwordTextBox.Text); + success = errors == null; + } + catch (Exception) + { + success = false; + } + + Schedule(() => + { + if (!success) + { + if (errors != null) + { + usernameDescription.AddErrors(errors.User.Username); + emailAddressDescription.AddErrors(errors.User.Email); + passwordDescription.AddErrors(errors.User.Password); + } + else + { + passwordDescription.AddErrors(new[] { "Something happened... but we're not sure what." }); + } + + registerShake.Shake(); + processingOverlay.Hide(); + return; + } + + api.Login(emailTextBox.Text, passwordTextBox.Text); + }); + }); + } + + private bool focusNextTextbox() + { + var nextTextbox = nextUnfilledTextbox(); + if (nextTextbox != null) + { + Schedule(() => GetContainingInputManager().ChangeFocus(nextTextbox)); + return true; + } + + return false; + } + + private OsuTextBox nextUnfilledTextbox() => textboxes.FirstOrDefault(t => string.IsNullOrEmpty(t.Text)); + } +} diff --git a/osu.Game/Overlays/AccountCreation/ScreenWarning.cs b/osu.Game/Overlays/AccountCreation/ScreenWarning.cs new file mode 100644 index 0000000000..082eb8a51f --- /dev/null +++ b/osu.Game/Overlays/AccountCreation/ScreenWarning.cs @@ -0,0 +1,133 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Graphics.Textures; +using osu.Framework.Screens; +using osu.Game.Graphics; +using osu.Game.Graphics.Containers; +using osu.Game.Graphics.Sprites; +using osu.Game.Online.API; +using osu.Game.Overlays.Settings; +using osu.Game.Screens.Menu; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Overlays.AccountCreation +{ + public class ScreenWarning : AccountCreationScreen + { + private OsuTextFlowContainer multiAccountExplanationText; + private LinkFlowContainer furtherAssistance; + private APIAccess api; + + private const string help_centre_url = "/help/wiki/Help_Centre#login"; + + protected override void OnEntering(Screen last) + { + if (string.IsNullOrEmpty(api.ProvidedUsername)) + { + Content.FadeOut(); + Push(new ScreenEntry()); + return; + } + + base.OnEntering(last); + } + + [BackgroundDependencyLoader(true)] + private void load(OsuColour colours, APIAccess api, OsuGame game, TextureStore textures) + { + this.api = api; + + if (string.IsNullOrEmpty(api.ProvidedUsername)) + return; + + Children = new Drawable[] + { + new Sprite + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Texture = textures.Get(@"Menu/dev-build-footer"), + }, + new Sprite + { + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + Texture = textures.Get(@"Menu/dev-build-footer"), + }, + new FillFlowContainer + { + RelativeSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Padding = new MarginPadding(20), + Spacing = new Vector2(0, 5), + Children = new Drawable[] + { + new Container + { + RelativeSizeAxes = Axes.X, + Height = 150, + Child = new OsuLogo + { + Scale = new Vector2(0.1f), + Anchor = Anchor.Centre, + Triangles = false, + }, + }, + new OsuSpriteText + { + TextSize = 28, + Font = "Exo2.0-Light", + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Colour = Color4.Red, + Text = "Warning! 注意!", + }, + multiAccountExplanationText = new OsuTextFlowContainer(cp => { cp.TextSize = 12; }) + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y + }, + new SettingsButton + { + Text = "Help, I can't access my account!", + Margin = new MarginPadding { Top = 50 }, + Action = () => game?.OpenUrlExternally(help_centre_url) + }, + new DangerousSettingsButton + { + Text = "I understand. This account isn't for me.", + Action = () => Push(new ScreenEntry()) + }, + furtherAssistance = new LinkFlowContainer(cp => { cp.TextSize = 12; }) + { + Margin = new MarginPadding { Top = 20 }, + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + AutoSizeAxes = Axes.Both + }, + } + } + }; + + multiAccountExplanationText.AddText("Are you "); + multiAccountExplanationText.AddText(api.ProvidedUsername, cp => cp.Colour = colours.BlueLight); + multiAccountExplanationText.AddText("? osu! has a policy of "); + multiAccountExplanationText.AddText("one account per person!", cp => cp.Colour = colours.Yellow); + multiAccountExplanationText.AddText(" Please be aware that creating more than one account per person may result in "); + multiAccountExplanationText.AddText("permanent deactivation of accounts", cp => cp.Colour = colours.Yellow); + multiAccountExplanationText.AddText("."); + + furtherAssistance.AddText("Need further assistance? Contact us via our "); + furtherAssistance.AddLink("support system", help_centre_url); + furtherAssistance.AddText("."); + } + } +} diff --git a/osu.Game/Overlays/AccountCreation/ScreenWelcome.cs b/osu.Game/Overlays/AccountCreation/ScreenWelcome.cs new file mode 100644 index 0000000000..5b7dc21be8 --- /dev/null +++ b/osu.Game/Overlays/AccountCreation/ScreenWelcome.cs @@ -0,0 +1,65 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Graphics.Sprites; +using osu.Game.Overlays.Settings; +using osu.Game.Screens.Menu; +using osuTK; + +namespace osu.Game.Overlays.AccountCreation +{ + public class ScreenWelcome : AccountCreationScreen + { + [BackgroundDependencyLoader] + private void load() + { + Child = new FillFlowContainer + { + RelativeSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Padding = new MarginPadding(20), + Spacing = new Vector2(0, 5), + Children = new Drawable[] + { + new Container + { + RelativeSizeAxes = Axes.X, + Height = 150, + Child = new OsuLogo + { + Scale = new Vector2(0.1f), + Anchor = Anchor.Centre, + Triangles = false, + }, + }, + new OsuSpriteText + { + TextSize = 24, + Font = "Exo2.0-Light", + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Text = "New Player Registration", + }, + new OsuSpriteText + { + TextSize = 12, + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Text = "let's get you started", + }, + new SettingsButton + { + Text = "Let's create an account!", + Margin = new MarginPadding { Vertical = 120 }, + Action = () => Push(new ScreenWarning()) + } + } + }; + } + } +} diff --git a/osu.Game/Overlays/AccountCreationOverlay.cs b/osu.Game/Overlays/AccountCreationOverlay.cs new file mode 100644 index 0000000000..9bc4119716 --- /dev/null +++ b/osu.Game/Overlays/AccountCreationOverlay.cs @@ -0,0 +1,110 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Allocation; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics; +using osu.Game.Graphics.Containers; +using osu.Game.Online.API; +using osu.Game.Overlays.AccountCreation; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Overlays +{ + public class AccountCreationOverlay : OsuFocusedOverlayContainer, IOnlineComponent + { + private const float transition_time = 400; + + private ScreenWelcome welcomeScreen; + + public AccountCreationOverlay() + { + Size = new Vector2(620, 450); + Anchor = Anchor.Centre; + Origin = Anchor.Centre; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours, APIAccess api) + { + api.Register(this); + + Children = new Drawable[] + { + new Container + { + RelativeSizeAxes = Axes.Both, + EdgeEffect = new EdgeEffectParameters + { + Type = EdgeEffectType.Shadow, + Radius = 5, + Colour = Color4.Black.Opacity(0.2f), + }, + Masking = true, + CornerRadius = 10, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.Black, + Alpha = 0.6f, + }, + new DelayedLoadWrapper(new AccountCreationBackground(), 0), + new Container + { + RelativeSizeAxes = Axes.Both, + Width = 0.6f, + AutoSizeDuration = transition_time, + AutoSizeEasing = Easing.OutQuint, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.Black, + Alpha = 0.9f, + }, + welcomeScreen = new ScreenWelcome(), + } + } + } + } + }; + } + + protected override void PopIn() + { + base.PopIn(); + this.FadeIn(transition_time, Easing.OutQuint); + + if (welcomeScreen.ChildScreen != null) + welcomeScreen.MakeCurrent(); + } + + protected override void PopOut() + { + base.PopOut(); + this.FadeOut(100); + } + + public void APIStateChanged(APIAccess api, APIState state) + { + switch (state) + { + case APIState.Offline: + case APIState.Failing: + break; + case APIState.Connecting: + break; + case APIState.Online: + State = Visibility.Hidden; + break; + } + } + } +} diff --git a/osu.Game/Overlays/BeatmapSet/AuthorInfo.cs b/osu.Game/Overlays/BeatmapSet/AuthorInfo.cs index c2f03a4b66..0b260d4f39 100644 --- a/osu.Game/Overlays/BeatmapSet/AuthorInfo.cs +++ b/osu.Game/Overlays/BeatmapSet/AuthorInfo.cs @@ -9,7 +9,6 @@ using osu.Game.Graphics.Sprites; using osu.Game.Users; using osuTK; using osuTK.Graphics; -using osu.Framework.Allocation; using osu.Game.Graphics.Containers; using osu.Framework.Graphics.Cursor; @@ -20,7 +19,6 @@ namespace osu.Game.Overlays.BeatmapSet private const float height = 50; private readonly UpdateableAvatar avatar; - private readonly ClickableArea clickableArea; private readonly FillFlowContainer fields; private BeatmapSetInfo beatmapSet; @@ -73,7 +71,7 @@ namespace osu.Game.Overlays.BeatmapSet Children = new Drawable[] { - clickableArea = new ClickableArea + new Container { AutoSizeAxes = Axes.Both, CornerRadius = 3, @@ -100,14 +98,8 @@ namespace osu.Game.Overlays.BeatmapSet }; } - [BackgroundDependencyLoader(true)] - private void load(UserProfileOverlay profile) + private void load() { - clickableArea.Action = () => - { - if (avatar.User != null) profile?.ShowUser(avatar.User); - }; - updateDisplay(); } diff --git a/osu.Game/Overlays/BeatmapSet/Scores/DrawableScore.cs b/osu.Game/Overlays/BeatmapSet/Scores/DrawableScore.cs index f643e130aa..89416c1098 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/DrawableScore.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/DrawableScore.cs @@ -10,11 +10,11 @@ using osu.Framework.Input.Events; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Online.API.Requests.Responses; +using osu.Game.Online.Leaderboards; using osu.Game.Overlays.Profile.Sections.Ranks; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; -using osu.Game.Screens.Select.Leaderboards; using osu.Game.Users; namespace osu.Game.Overlays.BeatmapSet.Scores diff --git a/osu.Game/Overlays/BeatmapSet/Scores/DrawableTopScore.cs b/osu.Game/Overlays/BeatmapSet/Scores/DrawableTopScore.cs index 643839fa88..6259d85bee 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/DrawableTopScore.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/DrawableTopScore.cs @@ -12,12 +12,12 @@ using osu.Framework.Input.Events; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Online.API.Requests.Responses; +using osu.Game.Online.Leaderboards; using osu.Game.Overlays.Profile.Sections.Ranks; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; using osu.Game.Scoring; -using osu.Game.Screens.Select.Leaderboards; using osu.Game.Users; namespace osu.Game.Overlays.BeatmapSet.Scores diff --git a/osu.Game/Overlays/Chat/ChatLine.cs b/osu.Game/Overlays/Chat/ChatLine.cs index c11de48430..29579056bf 100644 --- a/osu.Game/Overlays/Chat/ChatLine.cs +++ b/osu.Game/Overlays/Chat/ChatLine.cs @@ -1,72 +1,38 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . +// Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using System; using System.Linq; -using osuTK; -using osuTK.Graphics; using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Cursor; +using osu.Framework.Graphics.UserInterface; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; using osu.Game.Online.Chat; using osu.Game.Users; -using osu.Framework.Graphics.Cursor; -using osu.Framework.Graphics.UserInterface; -using osu.Game.Graphics.UserInterface; +using osuTK; +using osuTK.Graphics; namespace osu.Game.Overlays.Chat { - public class ChatLine : Container + public class ChatLine : CompositeDrawable { - private static readonly Color4[] username_colours = - { - OsuColour.FromHex("588c7e"), - OsuColour.FromHex("b2a367"), - OsuColour.FromHex("c98f65"), - OsuColour.FromHex("bc5151"), - OsuColour.FromHex("5c8bd6"), - OsuColour.FromHex("7f6ab7"), - OsuColour.FromHex("a368ad"), - OsuColour.FromHex("aa6880"), + public const float LEFT_PADDING = default_message_padding + default_horizontal_padding * 2; - OsuColour.FromHex("6fad9b"), - OsuColour.FromHex("f2e394"), - OsuColour.FromHex("f2ae72"), - OsuColour.FromHex("f98f8a"), - OsuColour.FromHex("7daef4"), - OsuColour.FromHex("a691f2"), - OsuColour.FromHex("c894d3"), - OsuColour.FromHex("d895b0"), + private const float default_message_padding = 200; - OsuColour.FromHex("53c4a1"), - OsuColour.FromHex("eace5c"), - OsuColour.FromHex("ea8c47"), - OsuColour.FromHex("fc4f4f"), - OsuColour.FromHex("3d94ea"), - OsuColour.FromHex("7760ea"), - OsuColour.FromHex("af52c6"), - OsuColour.FromHex("e25696"), + protected virtual float MessagePadding => default_message_padding; - OsuColour.FromHex("677c66"), - OsuColour.FromHex("9b8732"), - OsuColour.FromHex("8c5129"), - OsuColour.FromHex("8c3030"), - OsuColour.FromHex("1f5d91"), - OsuColour.FromHex("4335a5"), - OsuColour.FromHex("812a96"), - OsuColour.FromHex("992861"), - }; + private const float default_horizontal_padding = 15; - public const float LEFT_PADDING = message_padding + padding * 2; + protected virtual float HorizontalPadding => default_horizontal_padding; - private const float padding = 15; - private const float message_padding = 200; - private const float action_padding = 3; - private const float text_size = 20; + protected virtual float TextSize => 20; private Color4 customUsernameColour; @@ -75,14 +41,13 @@ namespace osu.Game.Overlays.Chat public ChatLine(Message message) { Message = message; - + Padding = new MarginPadding { Left = HorizontalPadding, Right = HorizontalPadding }; RelativeSizeAxes = Axes.X; AutoSizeAxes = Axes.Y; - - Padding = new MarginPadding { Left = padding, Right = padding }; } - private ChannelManager chatManager; + [Resolved(CanBeNull = true)] + private ChannelManager chatManager { get; set; } private Message message; private OsuSpriteText username; @@ -106,10 +71,9 @@ namespace osu.Game.Overlays.Chat } } - [BackgroundDependencyLoader(true)] - private void load(OsuColour colours, ChannelManager chatManager) + [BackgroundDependencyLoader] + private void load(OsuColour colours) { - this.chatManager = chatManager; customUsernameColour = colours.ChatBlue; } @@ -125,7 +89,7 @@ namespace osu.Game.Overlays.Chat { Font = @"Exo2.0-BoldItalic", Colour = hasBackground ? customUsernameColour : username_colours[message.Sender.Id % username_colours.Length], - TextSize = text_size, + TextSize = TextSize, }; if (hasBackground) @@ -163,11 +127,11 @@ namespace osu.Game.Overlays.Chat }; } - Children = new Drawable[] + InternalChildren = new Drawable[] { new Container { - Size = new Vector2(message_padding, text_size), + Size = new Vector2(MessagePadding, TextSize), Children = new Drawable[] { timestamp = new OsuSpriteText @@ -176,7 +140,7 @@ namespace osu.Game.Overlays.Chat Origin = Anchor.CentreLeft, Font = @"Exo2.0-SemiBold", FixedWidth = true, - TextSize = text_size * 0.75f, + TextSize = TextSize * 0.75f, }, new MessageSender(message.Sender) { @@ -191,7 +155,7 @@ namespace osu.Game.Overlays.Chat { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, - Padding = new MarginPadding { Left = message_padding + padding }, + Padding = new MarginPadding { Left = MessagePadding + HorizontalPadding }, Children = new Drawable[] { contentFlow = new LinkFlowContainer(t => @@ -204,7 +168,7 @@ namespace osu.Game.Overlays.Chat t.Colour = OsuColour.FromHex(message.Sender.Colour); } - t.TextSize = text_size; + t.TextSize = TextSize; }) { AutoSizeAxes = Axes.Y, @@ -257,5 +221,44 @@ namespace osu.Game.Overlays.Chat new OsuMenuItem("Start Chat", MenuItemType.Standard, startChatAction), }; } + + private static readonly Color4[] username_colours = + { + OsuColour.FromHex("588c7e"), + OsuColour.FromHex("b2a367"), + OsuColour.FromHex("c98f65"), + OsuColour.FromHex("bc5151"), + OsuColour.FromHex("5c8bd6"), + OsuColour.FromHex("7f6ab7"), + OsuColour.FromHex("a368ad"), + OsuColour.FromHex("aa6880"), + + OsuColour.FromHex("6fad9b"), + OsuColour.FromHex("f2e394"), + OsuColour.FromHex("f2ae72"), + OsuColour.FromHex("f98f8a"), + OsuColour.FromHex("7daef4"), + OsuColour.FromHex("a691f2"), + OsuColour.FromHex("c894d3"), + OsuColour.FromHex("d895b0"), + + OsuColour.FromHex("53c4a1"), + OsuColour.FromHex("eace5c"), + OsuColour.FromHex("ea8c47"), + OsuColour.FromHex("fc4f4f"), + OsuColour.FromHex("3d94ea"), + OsuColour.FromHex("7760ea"), + OsuColour.FromHex("af52c6"), + OsuColour.FromHex("e25696"), + + OsuColour.FromHex("677c66"), + OsuColour.FromHex("9b8732"), + OsuColour.FromHex("8c5129"), + OsuColour.FromHex("8c3030"), + OsuColour.FromHex("1f5d91"), + OsuColour.FromHex("4335a5"), + OsuColour.FromHex("812a96"), + OsuColour.FromHex("992861"), + }; } } diff --git a/osu.Game/Overlays/Chat/DrawableChannel.cs b/osu.Game/Overlays/Chat/DrawableChannel.cs index 2418eda2b6..c8fc15a0bc 100644 --- a/osu.Game/Overlays/Chat/DrawableChannel.cs +++ b/osu.Game/Overlays/Chat/DrawableChannel.cs @@ -17,7 +17,7 @@ namespace osu.Game.Overlays.Chat public class DrawableChannel : Container { public readonly Channel Channel; - private readonly ChatLineContainer flow; + protected readonly ChatLineContainer ChatLineFlow; private readonly ScrollContainer scroll; public DrawableChannel(Channel channel) @@ -38,7 +38,7 @@ namespace osu.Game.Overlays.Chat { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, - Child = flow = new ChatLineContainer + Child = ChatLineFlow = new ChatLineContainer { Padding = new MarginPadding { Left = 20, Right = 20 }, RelativeSizeAxes = Axes.X, @@ -72,17 +72,19 @@ namespace osu.Game.Overlays.Chat Channel.PendingMessageResolved -= pendingMessageResolved; } + protected virtual ChatLine CreateChatLine(Message m) => new ChatLine(m); + private void newMessagesArrived(IEnumerable newMessages) { // Add up to last Channel.MAX_HISTORY messages var displayMessages = newMessages.Skip(Math.Max(0, newMessages.Count() - Channel.MaxHistory)); - flow.AddRange(displayMessages.Select(m => new ChatLine(m))); + ChatLineFlow.AddRange(displayMessages.Select(CreateChatLine)); - if (scroll.IsScrolledToEnd(10) || !flow.Children.Any() || newMessages.Any(m => m is LocalMessage)) + if (scroll.IsScrolledToEnd(10) || !ChatLineFlow.Children.Any() || newMessages.Any(m => m is LocalMessage)) scrollToEnd(); - var staleMessages = flow.Children.Where(c => c.LifetimeEnd == double.MaxValue).ToArray(); + var staleMessages = ChatLineFlow.Children.Where(c => c.LifetimeEnd == double.MaxValue).ToArray(); int count = staleMessages.Length - Channel.MaxHistory; for (int i = 0; i < count; i++) @@ -96,25 +98,25 @@ namespace osu.Game.Overlays.Chat private void pendingMessageResolved(Message existing, Message updated) { - var found = flow.Children.LastOrDefault(c => c.Message == existing); + var found = ChatLineFlow.Children.LastOrDefault(c => c.Message == existing); if (found != null) { Trace.Assert(updated.Id.HasValue, "An updated message was returned with no ID."); - flow.Remove(found); + ChatLineFlow.Remove(found); found.Message = updated; - flow.Add(found); + ChatLineFlow.Add(found); } } private void messageRemoved(Message removed) { - flow.Children.FirstOrDefault(c => c.Message == removed)?.FadeColour(Color4.Red, 400).FadeOut(600).Expire(); + ChatLineFlow.Children.FirstOrDefault(c => c.Message == removed)?.FadeColour(Color4.Red, 400).FadeOut(600).Expire(); } private void scrollToEnd() => ScheduleAfterChildren(() => scroll.ScrollToEnd()); - private class ChatLineContainer : FillFlowContainer + protected class ChatLineContainer : FillFlowContainer { protected override int Compare(Drawable x, Drawable y) { diff --git a/osu.Game/Overlays/Chat/Tabs/PrivateChannelTabItem.cs b/osu.Game/Overlays/Chat/Tabs/PrivateChannelTabItem.cs index e04d7891f8..804ddeabb4 100644 --- a/osu.Game/Overlays/Chat/Tabs/PrivateChannelTabItem.cs +++ b/osu.Game/Overlays/Chat/Tabs/PrivateChannelTabItem.cs @@ -52,6 +52,7 @@ namespace osu.Game.Overlays.Chat.Tabs Child = new DelayedLoadWrapper(new Avatar(value.Users.First()) { RelativeSizeAxes = Axes.Both, + OpenOnClick = { Value = false }, OnLoadComplete = d => d.FadeInFromZero(300, Easing.OutQuint), }) { diff --git a/osu.Game/Overlays/Profile/ProfileHeader.cs b/osu.Game/Overlays/Profile/ProfileHeader.cs index 78f35b3da8..b35ae50c5e 100644 --- a/osu.Game/Overlays/Profile/ProfileHeader.cs +++ b/osu.Game/Overlays/Profile/ProfileHeader.cs @@ -181,6 +181,7 @@ namespace osu.Game.Overlays.Profile Size = new Vector2(avatar_size), Masking = true, CornerRadius = avatar_size * 0.25f, + OpenOnClick = { Value = false }, }, new Container { @@ -584,6 +585,8 @@ namespace osu.Game.Overlays.Profile detailCountryRank.LineColour = colours.Yellow; } + private readonly OsuSpriteText usernameText; + private User user; public User User diff --git a/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileScore.cs b/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileScore.cs index 1c39cb309c..18aa684664 100644 --- a/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileScore.cs +++ b/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileScore.cs @@ -6,8 +6,8 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; +using osu.Game.Online.Leaderboards; using osu.Game.Rulesets.Mods; -using osu.Game.Screens.Select.Leaderboards; using osu.Game.Rulesets.UI; using osu.Game.Scoring; diff --git a/osu.Game/Overlays/Profile/Sections/Recent/DrawableRecentActivity.cs b/osu.Game/Overlays/Profile/Sections/Recent/DrawableRecentActivity.cs index fefb289d17..8b4fb1a229 100644 --- a/osu.Game/Overlays/Profile/Sections/Recent/DrawableRecentActivity.cs +++ b/osu.Game/Overlays/Profile/Sections/Recent/DrawableRecentActivity.cs @@ -10,7 +10,7 @@ using osu.Game.Online.API; using osu.Game.Online.API.Requests; using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Chat; -using osu.Game.Screens.Select.Leaderboards; +using osu.Game.Online.Leaderboards; namespace osu.Game.Overlays.Profile.Sections.Recent { diff --git a/osu.Game/Overlays/Settings/Sections/General/LoginSettings.cs b/osu.Game/Overlays/Settings/Sections/General/LoginSettings.cs index 6623fbc73b..0cb6d2d1aa 100644 --- a/osu.Game/Overlays/Settings/Sections/General/LoginSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/General/LoginSettings.cs @@ -79,7 +79,10 @@ namespace osu.Game.Overlays.Settings.Sections.General Margin = new MarginPadding { Bottom = 5 }, Font = @"Exo2.0-Black", }, - form = new LoginForm() + form = new LoginForm + { + RequestHide = RequestHide + } }; break; case APIState.Failing: @@ -189,6 +192,8 @@ namespace osu.Game.Overlays.Settings.Sections.General private TextBox password; private APIAccess api; + public Action RequestHide; + private void performLogin() { if (!string.IsNullOrEmpty(username.Text) && !string.IsNullOrEmpty(password.Text)) @@ -196,7 +201,7 @@ namespace osu.Game.Overlays.Settings.Sections.General } [BackgroundDependencyLoader(permitNulls: true)] - private void load(APIAccess api, OsuConfigManager config) + private void load(APIAccess api, OsuConfigManager config, AccountCreationOverlay accountCreation) { this.api = api; Direction = FillDirection.Vertical; @@ -207,14 +212,14 @@ namespace osu.Game.Overlays.Settings.Sections.General { username = new OsuTextBox { - PlaceholderText = "Email address", + PlaceholderText = "email address", RelativeSizeAxes = Axes.X, Text = api?.ProvidedUsername ?? string.Empty, TabbableContentContainer = this }, password = new OsuPasswordTextBox { - PlaceholderText = "Password", + PlaceholderText = "password", RelativeSizeAxes = Axes.X, TabbableContentContainer = this, OnCommit = (sender, newText) => performLogin() @@ -237,7 +242,11 @@ namespace osu.Game.Overlays.Settings.Sections.General new SettingsButton { Text = "Register", - //Action = registerLink + Action = () => + { + RequestHide(); + accountCreation.Show(); + } } }; } @@ -322,12 +331,10 @@ namespace osu.Game.Overlays.Settings.Sections.General public const float LABEL_LEFT_MARGIN = 20; private readonly SpriteIcon statusIcon; + public Color4 StatusColour { - set - { - statusIcon.FadeColour(value, 500, Easing.OutQuint); - } + set { statusIcon.FadeColour(value, 500, Easing.OutQuint); } } public UserDropdownHeader() @@ -368,10 +375,13 @@ namespace osu.Game.Overlays.Settings.Sections.General private enum UserAction { Online, + [Description(@"Do not disturb")] DoNotDisturb, + [Description(@"Appear offline")] AppearOffline, + [Description(@"Sign out")] SignOut, } diff --git a/osu.Game/Overlays/Toolbar/ToolbarUserButton.cs b/osu.Game/Overlays/Toolbar/ToolbarUserButton.cs index 017d748600..36299e51f0 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarUserButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarUserButton.cs @@ -31,6 +31,7 @@ namespace osu.Game.Overlays.Toolbar Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, CornerRadius = 4, + OpenOnClick = { Value = false }, EdgeEffect = new EdgeEffectParameters { Type = EdgeEffectType.Shadow, diff --git a/osu.Game/Screens/Menu/OsuLogo.cs b/osu.Game/Screens/Menu/OsuLogo.cs index f858fded92..eafe44c0fc 100644 --- a/osu.Game/Screens/Menu/OsuLogo.cs +++ b/osu.Game/Screens/Menu/OsuLogo.cs @@ -79,8 +79,6 @@ namespace osu.Game.Screens.Menu private readonly Container impactContainer; - private const float default_size = 480; - private const double early_activation = 60; public override bool IsPresent => base.IsPresent || Scheduler.HasPendingTasks; @@ -89,8 +87,6 @@ namespace osu.Game.Screens.Menu { EarlyActivationMilliseconds = early_activation; - Size = new Vector2(default_size); - Origin = Anchor.Centre; AutoSizeAxes = Axes.Both; diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 19b49b099c..1429675ddd 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -28,7 +28,6 @@ using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; using osu.Game.Scoring; -using osu.Game.Screens.Ranking; using osu.Game.Skinning; using osu.Game.Storyboards.Drawables; @@ -288,7 +287,7 @@ namespace osu.Game.Screens.Play if (RulesetContainer.Replay == null) scoreManager.Import(score, true); - Push(new Results(score)); + Push(new SoloResults(score)); onCompletionEvent = null; }); diff --git a/osu.Game/Screens/Play/SoloResults.cs b/osu.Game/Screens/Play/SoloResults.cs new file mode 100644 index 0000000000..5e318e95d1 --- /dev/null +++ b/osu.Game/Screens/Play/SoloResults.cs @@ -0,0 +1,24 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.Collections.Generic; +using osu.Game.Scoring; +using osu.Game.Screens.Ranking; +using osu.Game.Screens.Ranking.Types; + +namespace osu.Game.Screens.Play +{ + public class SoloResults : Results + { + public SoloResults(ScoreInfo score) + : base(score) + { + } + + protected override IEnumerable CreateResultPages() => new IResultPageInfo[] + { + new ScoreOverviewPageInfo(Score, Beatmap), + new BeatmapLeaderboardPageInfo(Score, Beatmap) + }; + } +} diff --git a/osu.Game/Screens/Ranking/ResultMode.cs b/osu.Game/Screens/Ranking/IResultPageInfo.cs similarity index 55% rename from osu.Game/Screens/Ranking/ResultMode.cs rename to osu.Game/Screens/Ranking/IResultPageInfo.cs index 4742864b83..5c6d08d883 100644 --- a/osu.Game/Screens/Ranking/ResultMode.cs +++ b/osu.Game/Screens/Ranking/IResultPageInfo.cs @@ -1,12 +1,16 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +using osu.Game.Graphics; + namespace osu.Game.Screens.Ranking { - public enum ResultMode + public interface IResultPageInfo { - Summary, - Ranking, - Share + FontAwesome Icon { get; } + + string Name { get; } + + ResultsPage CreatePage(); } } diff --git a/osu.Game/Screens/Ranking/ResultsPageRanking.cs b/osu.Game/Screens/Ranking/Pages/RankingResultsPage.cs similarity index 80% rename from osu.Game/Screens/Ranking/ResultsPageRanking.cs rename to osu.Game/Screens/Ranking/Pages/RankingResultsPage.cs index c5a5cc6ad9..4c98e476c4 100644 --- a/osu.Game/Screens/Ranking/ResultsPageRanking.cs +++ b/osu.Game/Screens/Ranking/Pages/RankingResultsPage.cs @@ -3,18 +3,19 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; +using osu.Framework.Graphics.Shapes; using osu.Game.Beatmaps; using osu.Game.Graphics; +using osu.Game.Scoring; using osu.Game.Screens.Select.Leaderboards; using osuTK; -using osu.Framework.Graphics.Shapes; -using osu.Game.Scoring; -namespace osu.Game.Screens.Ranking +namespace osu.Game.Screens.Ranking.Pages { - public class ResultsPageRanking : ResultsPage + public class RankingResultsPage : ResultsPage { - public ResultsPageRanking(ScoreInfo score, WorkingBeatmap beatmap = null) : base(score, beatmap) + public RankingResultsPage(ScoreInfo score, WorkingBeatmap beatmap = null) + : base(score, beatmap) { } @@ -28,7 +29,7 @@ namespace osu.Game.Screens.Ranking Colour = colours.GrayE, RelativeSizeAxes = Axes.Both, }, - new Leaderboard + new BeatmapLeaderboard { Origin = Anchor.Centre, Anchor = Anchor.Centre, diff --git a/osu.Game/Screens/Ranking/ResultsPageScore.cs b/osu.Game/Screens/Ranking/Pages/ScoreResultsPage.cs similarity index 98% rename from osu.Game/Screens/Ranking/ResultsPageScore.cs rename to osu.Game/Screens/Ranking/Pages/ScoreResultsPage.cs index 62103314e1..8f5b21a7cb 100644 --- a/osu.Game/Screens/Ranking/ResultsPageScore.cs +++ b/osu.Game/Screens/Ranking/Pages/ScoreResultsPage.cs @@ -4,36 +4,39 @@ using System; using System.Collections.Generic; using System.Linq; -using osuTK; -using osuTK.Graphics; using osu.Framework.Allocation; +using osu.Framework.Extensions; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; +using osu.Framework.Localisation; using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; +using osu.Game.Online.Leaderboards; using osu.Game.Rulesets.Scoring; -using osu.Game.Screens.Play; -using osu.Game.Screens.Select.Leaderboards; -using osu.Game.Users; -using osu.Framework.Graphics.Shapes; -using osu.Framework.Extensions; -using osu.Framework.Localisation; using osu.Game.Scoring; +using osu.Game.Screens.Play; +using osu.Game.Users; +using osuTK; +using osuTK.Graphics; -namespace osu.Game.Screens.Ranking +namespace osu.Game.Screens.Ranking.Pages { - public class ResultsPageScore : ResultsPage + public class ScoreResultsPage : ResultsPage { private Container scoreContainer; private ScoreCounter scoreCounter; - public ResultsPageScore(ScoreInfo score, WorkingBeatmap beatmap) : base(score, beatmap) { } + public ScoreResultsPage(ScoreInfo score, WorkingBeatmap beatmap) + : base(score, beatmap) + { + } private FillFlowContainer statisticsContainer; diff --git a/osu.Game/Screens/Ranking/ResultModeButton.cs b/osu.Game/Screens/Ranking/ResultModeButton.cs index 5df3ff3afb..f6a886418c 100644 --- a/osu.Game/Screens/Ranking/ResultModeButton.cs +++ b/osu.Game/Screens/Ranking/ResultModeButton.cs @@ -5,6 +5,7 @@ using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.UserInterface; using osu.Game.Graphics; using osuTK; @@ -13,27 +14,18 @@ using osu.Framework.Graphics.Shapes; namespace osu.Game.Screens.Ranking { - public class ResultModeButton : TabItem + public class ResultModeButton : TabItem, IHasTooltip { private readonly FontAwesome icon; private Color4 activeColour; private Color4 inactiveColour; private CircularContainer colouredPart; - public ResultModeButton(ResultMode mode) : base(mode) + public ResultModeButton(IResultPageInfo mode) + : base(mode) { - switch (mode) - { - case ResultMode.Summary: - icon = FontAwesome.fa_asterisk; - break; - case ResultMode.Ranking: - icon = FontAwesome.fa_list; - break; - case ResultMode.Share: - icon = FontAwesome.fa_camera; - break; - } + icon = mode.Icon; + TooltipText = mode.Name; } [BackgroundDependencyLoader] @@ -95,5 +87,7 @@ namespace osu.Game.Screens.Ranking protected override void OnActivated() => colouredPart.FadeColour(activeColour, 200, Easing.OutQuint); protected override void OnDeactivated() => colouredPart.FadeColour(inactiveColour, 200, Easing.OutQuint); + + public string TooltipText { get; private set; } } } diff --git a/osu.Game/Screens/Ranking/ResultModeTabControl.cs b/osu.Game/Screens/Ranking/ResultModeTabControl.cs index e5c6115085..e87749d8dd 100644 --- a/osu.Game/Screens/Ranking/ResultModeTabControl.cs +++ b/osu.Game/Screens/Ranking/ResultModeTabControl.cs @@ -7,7 +7,7 @@ using osuTK; namespace osu.Game.Screens.Ranking { - public class ResultModeTabControl : TabControl + public class ResultModeTabControl : TabControl { public ResultModeTabControl() { @@ -19,9 +19,9 @@ namespace osu.Game.Screens.Ranking TabContainer.Padding = new MarginPadding(5); } - protected override Dropdown CreateDropdown() => null; + protected override Dropdown CreateDropdown() => null; - protected override TabItem CreateTabItem(ResultMode value) => new ResultModeButton(value) + protected override TabItem CreateTabItem(IResultPageInfo value) => new ResultModeButton(value) { Anchor = TabContainer.Anchor, Origin = TabContainer.Origin diff --git a/osu.Game/Screens/Ranking/Results.cs b/osu.Game/Screens/Ranking/Results.cs index 1ff5fc7bfd..bf9e3bcd27 100644 --- a/osu.Game/Screens/Ranking/Results.cs +++ b/osu.Game/Screens/Ranking/Results.cs @@ -2,6 +2,7 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using System.Collections.Generic; +using System.Linq; using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Extensions.IEnumerableExtensions; @@ -21,9 +22,8 @@ using osu.Game.Scoring; namespace osu.Game.Screens.Ranking { - public class Results : OsuScreen + public abstract class Results : OsuScreen { - private readonly ScoreInfo score; private Container circleOuterBackground; private Container circleOuter; private Container circleInner; @@ -34,6 +34,8 @@ namespace osu.Game.Screens.Ranking public override bool AllowBeatmapRulesetChange => false; + protected readonly ScoreInfo Score; + private Container currentPage; private static readonly Vector2 background_blur = new Vector2(20); @@ -44,9 +46,9 @@ namespace osu.Game.Screens.Ranking private const float circle_outer_scale = 0.96f; - public Results(ScoreInfo score) + protected Results(ScoreInfo score) { - this.score = score; + Score = score; } private const float transition_time = 800; @@ -67,7 +69,7 @@ namespace osu.Game.Screens.Ranking backgroundParallax.FadeOut(); modeChangeButtons.FadeOut(); - currentPage.FadeOut(); + currentPage?.FadeOut(); circleOuterBackground .FadeIn(transition_time, Easing.OutQuint) @@ -90,7 +92,7 @@ namespace osu.Game.Screens.Ranking using (BeginDelayedSequence(transition_time * 0.4f, true)) { modeChangeButtons.FadeIn(transition_time, Easing.OutQuint); - currentPage.FadeIn(transition_time, Easing.OutQuint); + currentPage?.FadeIn(transition_time, Easing.OutQuint); } } } @@ -188,7 +190,7 @@ namespace osu.Game.Screens.Ranking }, new OsuSpriteText { - Text = $"{score.MaxCombo}x", + Text = $"{Score.MaxCombo}x", TextSize = 40, RelativePositionAxes = Axes.X, Font = @"Exo2.0-Bold", @@ -209,7 +211,7 @@ namespace osu.Game.Screens.Ranking }, new OsuSpriteText { - Text = $"{score.Accuracy:P2}", + Text = $"{Score.Accuracy:P2}", TextSize = 40, RelativePositionAxes = Axes.X, Font = @"Exo2.0-Bold", @@ -262,30 +264,22 @@ namespace osu.Game.Screens.Ranking }, }; - modeChangeButtons.AddItem(ResultMode.Summary); - modeChangeButtons.AddItem(ResultMode.Ranking); - //modeChangeButtons.AddItem(ResultMode.Share); + foreach (var t in CreateResultPages()) + modeChangeButtons.AddItem(t); + modeChangeButtons.Current.Value = modeChangeButtons.Items.FirstOrDefault(); - modeChangeButtons.Current.ValueChanged += mode => + modeChangeButtons.Current.BindValueChanged(m => { currentPage?.FadeOut(); currentPage?.Expire(); - switch (mode) - { - case ResultMode.Summary: - currentPage = new ResultsPageScore(score, Beatmap.Value); - break; - case ResultMode.Ranking: - currentPage = new ResultsPageRanking(score, Beatmap.Value); - break; - } + currentPage = m?.CreatePage(); if (currentPage != null) circleInner.Add(currentPage); - }; - - modeChangeButtons.Current.TriggerChange(); + }, true); } + + protected abstract IEnumerable CreateResultPages(); } } diff --git a/osu.Game/Screens/Ranking/ResultsPage.cs b/osu.Game/Screens/Ranking/ResultsPage.cs index 5f68623e54..08c6155557 100644 --- a/osu.Game/Screens/Ranking/ResultsPage.cs +++ b/osu.Game/Screens/Ranking/ResultsPage.cs @@ -14,7 +14,7 @@ using osuTK.Graphics; namespace osu.Game.Screens.Ranking { - public class ResultsPage : Container + public abstract class ResultsPage : Container { protected readonly ScoreInfo Score; protected readonly WorkingBeatmap Beatmap; @@ -23,7 +23,7 @@ namespace osu.Game.Screens.Ranking protected override Container Content => content; - public ResultsPage(ScoreInfo score, WorkingBeatmap beatmap) + protected ResultsPage(ScoreInfo score, WorkingBeatmap beatmap) { Score = score; Beatmap = beatmap; diff --git a/osu.Game/Screens/Ranking/Types/BeatmapLeaderboardPageInfo.cs b/osu.Game/Screens/Ranking/Types/BeatmapLeaderboardPageInfo.cs new file mode 100644 index 0000000000..2b192d4bcd --- /dev/null +++ b/osu.Game/Screens/Ranking/Types/BeatmapLeaderboardPageInfo.cs @@ -0,0 +1,28 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Beatmaps; +using osu.Game.Graphics; +using osu.Game.Scoring; +using osu.Game.Screens.Ranking.Pages; + +namespace osu.Game.Screens.Ranking.Types +{ + public class BeatmapLeaderboardPageInfo : IResultPageInfo + { + private readonly ScoreInfo score; + private readonly WorkingBeatmap beatmap; + + public BeatmapLeaderboardPageInfo(ScoreInfo score, WorkingBeatmap beatmap) + { + this.score = score; + this.beatmap = beatmap; + } + + public FontAwesome Icon => FontAwesome.fa_list; + + public string Name => @"Beatmap Leaderboard"; + + public ResultsPage CreatePage() => new RankingResultsPage(score, beatmap); + } +} diff --git a/osu.Game/Screens/Ranking/Types/ScoreOverviewPageInfo.cs b/osu.Game/Screens/Ranking/Types/ScoreOverviewPageInfo.cs new file mode 100644 index 0000000000..03998d2840 --- /dev/null +++ b/osu.Game/Screens/Ranking/Types/ScoreOverviewPageInfo.cs @@ -0,0 +1,30 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Beatmaps; +using osu.Game.Graphics; +using osu.Game.Scoring; +using osu.Game.Screens.Ranking.Pages; + +namespace osu.Game.Screens.Ranking.Types +{ + public class ScoreOverviewPageInfo : IResultPageInfo + { + public FontAwesome Icon => FontAwesome.fa_asterisk; + + public string Name => "Overview"; + private readonly ScoreInfo score; + private readonly WorkingBeatmap beatmap; + + public ScoreOverviewPageInfo(ScoreInfo score, WorkingBeatmap beatmap) + { + this.score = score; + this.beatmap = beatmap; + } + + public ResultsPage CreatePage() + { + return new ScoreResultsPage(score, beatmap); + } + } +} diff --git a/osu.Game/Screens/Select/BeatmapDetailArea.cs b/osu.Game/Screens/Select/BeatmapDetailArea.cs index a6fbd201d0..c5c4960ed4 100644 --- a/osu.Game/Screens/Select/BeatmapDetailArea.cs +++ b/osu.Game/Screens/Select/BeatmapDetailArea.cs @@ -17,7 +17,7 @@ namespace osu.Game.Screens.Select protected override Container Content => content; public readonly BeatmapDetails Details; - public readonly Leaderboard Leaderboard; + public readonly BeatmapLeaderboard Leaderboard; private WorkingBeatmap beatmap; public WorkingBeatmap Beatmap @@ -52,7 +52,7 @@ namespace osu.Game.Screens.Select default: Details.Hide(); - Leaderboard.Scope = (LeaderboardScope)tab - 1; + Leaderboard.Scope = (BeatmapLeaderboardScope)tab - 1; Leaderboard.Show(); break; } @@ -73,7 +73,7 @@ namespace osu.Game.Screens.Select Alpha = 0, Margin = new MarginPadding { Top = details_padding }, }, - Leaderboard = new Leaderboard + Leaderboard = new BeatmapLeaderboard { RelativeSizeAxes = Axes.Both, } diff --git a/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs index 272332f1ce..49779f5e11 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs @@ -24,12 +24,12 @@ namespace osu.Game.Screens.Select.Carousel { base.Filter(criteria); - bool match = criteria.Ruleset == null || Beatmap.RulesetID == criteria.Ruleset.ID || Beatmap.RulesetID == 0 && criteria.Ruleset.ID > 0 && criteria.AllowConvertedBeatmaps; + bool match = criteria.Ruleset == null || Beatmap.RulesetID == criteria.Ruleset.ID || (Beatmap.RulesetID == 0 && criteria.Ruleset.ID > 0 && criteria.AllowConvertedBeatmaps); - if (!string.IsNullOrEmpty(criteria.SearchText)) + foreach (var criteriaTerm in criteria.SearchTerms) match &= - Beatmap.Metadata.SearchableTerms.Any(term => term.IndexOf(criteria.SearchText, StringComparison.InvariantCultureIgnoreCase) >= 0) || - Beatmap.Version.IndexOf(criteria.SearchText, StringComparison.InvariantCultureIgnoreCase) >= 0; + Beatmap.Metadata.SearchableTerms.Any(term => term.IndexOf(criteriaTerm, StringComparison.InvariantCultureIgnoreCase) >= 0) || + Beatmap.Version.IndexOf(criteriaTerm, StringComparison.InvariantCultureIgnoreCase) >= 0; Filtered.Value = !match; } diff --git a/osu.Game/Screens/Select/FilterCriteria.cs b/osu.Game/Screens/Select/FilterCriteria.cs index bea806f00f..71cfedfdbf 100644 --- a/osu.Game/Screens/Select/FilterCriteria.cs +++ b/osu.Game/Screens/Select/FilterCriteria.cs @@ -1,6 +1,8 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +using System; +using System.Linq; using osu.Game.Rulesets; using osu.Game.Screens.Select.Filter; @@ -10,8 +12,22 @@ namespace osu.Game.Screens.Select { public GroupMode Group; public SortMode Sort; - public string SearchText; + + public string[] SearchTerms = Array.Empty(); + public RulesetInfo Ruleset; public bool AllowConvertedBeatmaps; + + private string searchText; + + public string SearchText + { + get { return searchText; } + set + { + searchText = value; + SearchTerms = searchText.Split(',', ' ', '!').Where(s => !string.IsNullOrEmpty(s)).ToArray(); + } + } } } diff --git a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs new file mode 100644 index 0000000000..9f8726c86a --- /dev/null +++ b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs @@ -0,0 +1,87 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Collections.Generic; +using System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Configuration; +using osu.Game.Beatmaps; +using osu.Game.Online.API; +using osu.Game.Online.API.Requests; +using osu.Game.Online.Leaderboards; +using osu.Game.Rulesets; +using osu.Game.Scoring; + +namespace osu.Game.Screens.Select.Leaderboards +{ + public class BeatmapLeaderboard : Leaderboard + { + public Action ScoreSelected; + + private BeatmapInfo beatmap; + + public BeatmapInfo Beatmap + { + get { return beatmap; } + set + { + if (beatmap == value) + return; + + beatmap = value; + Scores = null; + + UpdateScores(); + } + } + + [Resolved] + private ScoreManager scoreManager { get; set; } + + [Resolved] + private IBindable ruleset { get; set; } + + [Resolved] + private APIAccess api { get; set; } + + [BackgroundDependencyLoader] + private void load() + { + ruleset.ValueChanged += _ => UpdateScores(); + } + + protected override APIRequest FetchScores(Action> scoresCallback) + { + if (Scope == BeatmapLeaderboardScope.Local) + { + Scores = scoreManager.QueryScores(s => s.Beatmap.ID == Beatmap.ID).ToArray(); + PlaceholderState = Scores.Any() ? PlaceholderState.Successful : PlaceholderState.NoScores; + return null; + } + + if (Beatmap?.OnlineBeatmapID == null) + { + PlaceholderState = PlaceholderState.Unavailable; + return null; + } + + if (Scope != BeatmapLeaderboardScope.Global && !api.LocalUser.Value.IsSupporter) + { + PlaceholderState = PlaceholderState.NotSupporter; + return null; + } + + var req = new GetScoresRequest(Beatmap, ruleset.Value ?? Beatmap.Ruleset, Scope); + + req.Success += r => scoresCallback?.Invoke(r.Scores); + + return req; + } + + protected override LeaderboardScore CreateDrawableScore(ScoreInfo model, int index) => new LeaderboardScore(model, index) + { + Action = () => ScoreSelected?.Invoke(model) + }; + } +} diff --git a/osu.Game/Screens/Select/Leaderboards/LeaderboardScope.cs b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboardScope.cs similarity index 87% rename from osu.Game/Screens/Select/Leaderboards/LeaderboardScope.cs rename to osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboardScope.cs index 761f53a5e8..39d9580792 100644 --- a/osu.Game/Screens/Select/Leaderboards/LeaderboardScope.cs +++ b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboardScope.cs @@ -3,7 +3,7 @@ namespace osu.Game.Screens.Select.Leaderboards { - public enum LeaderboardScope + public enum BeatmapLeaderboardScope { Local, Country, diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 71b63c8e5b..f65cc0e49d 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -28,7 +28,7 @@ using osu.Game.Rulesets.Mods; using osu.Game.Screens.Backgrounds; using osu.Game.Screens.Edit; using osu.Game.Screens.Menu; -using osu.Game.Screens.Ranking; +using osu.Game.Screens.Play; using osu.Game.Screens.Select.Options; using osu.Game.Skinning; @@ -210,7 +210,7 @@ namespace osu.Game.Screens.Select }); } - BeatmapDetails.Leaderboard.ScoreSelected += s => Push(new Results(s)); + BeatmapDetails.Leaderboard.ScoreSelected += s => Push(new SoloResults(s)); } [BackgroundDependencyLoader(true)] diff --git a/osu.Game/Users/Avatar.cs b/osu.Game/Users/Avatar.cs index e6e1ba3c53..24864cce51 100644 --- a/osu.Game/Users/Avatar.cs +++ b/osu.Game/Users/Avatar.cs @@ -3,17 +3,29 @@ using System; using osu.Framework.Allocation; +using osu.Framework.Configuration; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; +using osu.Framework.Input.Events; +using osu.Game.Graphics.Containers; namespace osu.Game.Users { public class Avatar : Container { + /// + /// Whether to open the user's profile when clicked. + /// + public readonly BindableBool OpenOnClick = new BindableBool(true); + private readonly User user; + [Resolved(CanBeNull = true)] + private OsuGame game { get; set; } + /// /// An avatar for specified user. /// @@ -33,14 +45,43 @@ namespace osu.Game.Users if (user != null && user.Id > 1) texture = textures.Get($@"https://a.ppy.sh/{user.Id}"); if (texture == null) texture = textures.Get(@"Online/avatar-guest"); - Add(new Sprite + ClickableArea clickableArea; + Add(clickableArea = new ClickableArea { RelativeSizeAxes = Axes.Both, - Texture = texture, - FillMode = FillMode.Fit, - Anchor = Anchor.Centre, - Origin = Anchor.Centre + Child = new Sprite + { + RelativeSizeAxes = Axes.Both, + Texture = texture, + FillMode = FillMode.Fit, + Anchor = Anchor.Centre, + Origin = Anchor.Centre + }, + Action = openProfile }); + + clickableArea.Enabled.BindTo(OpenOnClick); + } + + private void openProfile() + { + if (!OpenOnClick) + return; + + if (user != null) + game?.ShowUser(user.Id); + } + + private class ClickableArea : OsuClickableContainer, IHasTooltip + { + public string TooltipText => Enabled.Value ? @"View Profile" : null; + + protected override bool OnClick(ClickEvent e) + { + if (!Enabled) + return false; + return base.OnClick(e); + } } } } diff --git a/osu.Game/Users/UpdateableAvatar.cs b/osu.Game/Users/UpdateableAvatar.cs index 6c0b841abf..a8d9d3d66b 100644 --- a/osu.Game/Users/UpdateableAvatar.cs +++ b/osu.Game/Users/UpdateableAvatar.cs @@ -1,6 +1,7 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +using osu.Framework.Configuration; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -35,6 +36,11 @@ namespace osu.Game.Users } } + /// + /// Whether to open the user's profile when clicked. + /// + public readonly BindableBool OpenOnClick = new BindableBool(true); + protected override void LoadComplete() { base.LoadComplete(); @@ -45,15 +51,18 @@ namespace osu.Game.Users { displayedAvatar?.FadeOut(300); displayedAvatar?.Expire(); + if (user != null || ShowGuestOnNull) { - Add(displayedAvatar = new DelayedLoadWrapper( - new Avatar(user) - { - RelativeSizeAxes = Axes.Both, - OnLoadComplete = d => d.FadeInFromZero(300, Easing.OutQuint), - }) - ); + var avatar = new Avatar(user) + { + RelativeSizeAxes = Axes.Both, + OnLoadComplete = d => d.FadeInFromZero(300, Easing.OutQuint), + }; + + avatar.OpenOnClick.BindTo(OpenOnClick); + + Add(displayedAvatar = new DelayedLoadWrapper(avatar)); } } } diff --git a/osu.Game/Users/UserPanel.cs b/osu.Game/Users/UserPanel.cs index 3d0127bba4..edfab05702 100644 --- a/osu.Game/Users/UserPanel.cs +++ b/osu.Game/Users/UserPanel.cs @@ -99,6 +99,7 @@ namespace osu.Game.Users User = user, Masking = true, CornerRadius = 5, + OpenOnClick = { Value = false }, EdgeEffect = new EdgeEffectParameters { Type = EdgeEffectType.Shadow, diff --git a/osu.sln.DotSettings b/osu.sln.DotSettings index 170a7bd8c9..aac799f961 100644 --- a/osu.sln.DotSettings +++ b/osu.sln.DotSettings @@ -8,7 +8,8 @@ SOLUTION HINT WARNING - WARNING + + True WARNING WARNING HINT