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.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/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/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/OsuGame.cs b/osu.Game/OsuGame.cs index 51520a1606..41f2caf40e 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 28d5c557bc..4892985458 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/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/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/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/Ranking/ResultModeButton.cs b/osu.Game/Screens/Ranking/ResultModeButton.cs index 34a93236bd..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,7 +14,7 @@ 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; @@ -24,6 +25,7 @@ namespace osu.Game.Screens.Ranking : base(mode) { icon = mode.Icon; + TooltipText = mode.Name; } [BackgroundDependencyLoader] @@ -85,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/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.sln.DotSettings b/osu.sln.DotSettings index d6882282e6..112efb37f0 100644 --- a/osu.sln.DotSettings +++ b/osu.sln.DotSettings @@ -8,7 +8,8 @@ SOLUTION HINT WARNING - WARNING + + True WARNING WARNING HINT