diff --git a/osu-framework b/osu-framework index fac688633b..a191c104b8 160000 --- a/osu-framework +++ b/osu-framework @@ -1 +1 @@ -Subproject commit fac688633b8fcf34ae5d0514c26b03e217161eb4 +Subproject commit a191c104b8e254e81a1a7bb1c200ccdf02628796 diff --git a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs index f40ef67dfb..ad500606ed 100644 --- a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs +++ b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs @@ -28,7 +28,20 @@ namespace osu.Game.Rulesets.Catch.Beatmaps var endTime = obj as IHasEndTime; if (positionData == null) + { + if (endTime != null) + { + yield return new BananaShower + { + StartTime = obj.StartTime, + Samples = obj.Samples, + Duration = endTime.Duration, + NewCombo = comboData?.NewCombo ?? false + }; + } + yield break; + } if (curveData != null) { @@ -48,19 +61,6 @@ namespace osu.Game.Rulesets.Catch.Beatmaps yield break; } - if (endTime != null) - { - yield return new BananaShower - { - StartTime = obj.StartTime, - Samples = obj.Samples, - Duration = endTime.Duration, - NewCombo = comboData?.NewCombo ?? false - }; - - yield break; - } - yield return new Fruit { StartTime = obj.StartTime, diff --git a/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs b/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs index e6e3028d62..93652f7610 100644 --- a/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs @@ -105,7 +105,8 @@ namespace osu.Game.Rulesets.Mania.Difficulty private double computeAccuracyValue(double strainValue) { - double hitWindowGreat = (Beatmap.HitObjects.First().HitWindows.Great / 2 - 0.5) / TimeRate; + // Todo: This int cast is temporary to achieve 1:1 results with osu!stable, and should be remoevd in the future + double hitWindowGreat = (int)(Beatmap.HitObjects.First().HitWindows.Great / 2) / TimeRate; if (hitWindowGreat <= 0) return 0; diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs index eeb776fa6e..57cf962fa7 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -61,20 +61,12 @@ namespace osu.Game.Rulesets.Osu.Difficulty if (mods.Any(m => !m.Ranked)) return 0; - // Todo: In the future we should apply changes to PreEmpt/AR at an OsuHitObject/BaseDifficulty level, but this is done - // locally for now as doing so would modify animations and other things unexpectedly - // DO NOT MODIFY THIS - double ar = Beatmap.BeatmapInfo.BaseDifficulty.ApproachRate; - if (mods.Any(m => m is OsuModHardRock)) - ar = Math.Min(10, ar * 1.4); - if (mods.Any(m => m is OsuModEasy)) - ar = Math.Max(0, ar / 2); - - double preEmpt = BeatmapDifficulty.DifficultyRange(ar, 1800, 1200, 450) / TimeRate; - double hitWindowGreat = (Beatmap.HitObjects.First().HitWindows.Great / 2 - 0.5) / TimeRate; + // Todo: These int casts are temporary to achieve 1:1 results with osu!stable, and should be remoevd in the future + double hitWindowGreat = (int)(Beatmap.HitObjects.First().HitWindows.Great / 2) / TimeRate; + double preEmpt = (int)BeatmapDifficulty.DifficultyRange(Beatmap.BeatmapInfo.BaseDifficulty.ApproachRate, 1800, 1200, 450) / TimeRate; realApproachRate = preEmpt > 1200 ? (1800 - preEmpt) / 120 : (1200 - preEmpt) / 150 + 5; - realOverallDifficulty = (80 - 0.5 - hitWindowGreat) / 6; + realOverallDifficulty = (80 - hitWindowGreat) / 6; // Custom multipliers for NoFail and SpunOut. double multiplier = 1.12f; // This is being adjusted to keep the final pp value scaled around what it used to be when changing things diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs index 9c9cd1f0fb..6b1a25d667 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs @@ -94,7 +94,8 @@ namespace osu.Game.Rulesets.Taiko.Difficulty private double computeAccuracyValue() { - double hitWindowGreat = (Beatmap.HitObjects.First().HitWindows.Great / 2 - 0.5) / TimeRate; + // Todo: This int cast is temporary to achieve 1:1 results with osu!stable, and should be remoevd in the future + double hitWindowGreat = (int)(Beatmap.HitObjects.First().HitWindows.Great / 2) / TimeRate; if (hitWindowGreat <= 0) return 0; diff --git a/osu.Game.Tests/Visual/TestCaseCursors.cs b/osu.Game.Tests/Visual/TestCaseCursors.cs index 977f241f7a..0aa8e9691e 100644 --- a/osu.Game.Tests/Visual/TestCaseCursors.cs +++ b/osu.Game.Tests/Visual/TestCaseCursors.cs @@ -19,12 +19,12 @@ namespace osu.Game.Tests.Visual [TestFixture] public class TestCaseCursors : ManualInputManagerTestCase { - private readonly CursorOverrideContainer cursorOverrideContainer; + private readonly MenuCursorContainer menuCursorContainer; private readonly CustomCursorBox[] cursorBoxes = new CustomCursorBox[6]; public TestCaseCursors() { - Child = cursorOverrideContainer = new CursorOverrideContainer + Child = menuCursorContainer = new MenuCursorContainer { RelativeSizeAxes = Axes.Both, Children = new[] @@ -99,7 +99,7 @@ namespace osu.Game.Tests.Visual AddAssert("Check green cursor at mouse", () => checkAtMouse(cursorBoxes[0].Cursor)); AddStep("Move out", moveOut); AddAssert("Check green cursor invisible", () => !checkVisible(cursorBoxes[0].Cursor)); - AddAssert("Check global cursor visible", () => checkVisible(cursorOverrideContainer.Cursor)); + AddAssert("Check global cursor visible", () => checkVisible(menuCursorContainer.Cursor)); } /// @@ -112,11 +112,11 @@ namespace osu.Game.Tests.Visual AddStep("Move to purple area", () => InputManager.MoveMouseTo(cursorBoxes[3])); AddAssert("Check purple cursor visible", () => checkVisible(cursorBoxes[3].Cursor)); AddAssert("Check purple cursor at mouse", () => checkAtMouse(cursorBoxes[3].Cursor)); - AddAssert("Check global cursor visible", () => checkVisible(cursorOverrideContainer.Cursor)); - AddAssert("Check global cursor at mouse", () => checkAtMouse(cursorOverrideContainer.Cursor)); + AddAssert("Check global cursor visible", () => checkVisible(menuCursorContainer.Cursor)); + AddAssert("Check global cursor at mouse", () => checkAtMouse(menuCursorContainer.Cursor)); AddStep("Move out", moveOut); AddAssert("Check purple cursor visible", () => checkVisible(cursorBoxes[3].Cursor)); - AddAssert("Check global cursor visible", () => checkVisible(cursorOverrideContainer.Cursor)); + AddAssert("Check global cursor visible", () => checkVisible(menuCursorContainer.Cursor)); } /// diff --git a/osu.Game.Tests/Visual/TestCaseExternalLinkButton.cs b/osu.Game.Tests/Visual/TestCaseExternalLinkButton.cs new file mode 100644 index 0000000000..7d8535f428 --- /dev/null +++ b/osu.Game.Tests/Visual/TestCaseExternalLinkButton.cs @@ -0,0 +1,23 @@ +// 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.Game.Graphics.UserInterface; +using OpenTK; + +namespace osu.Game.Tests.Visual +{ + public class TestCaseExternalLinkButton : OsuTestCase + { + public override IReadOnlyList RequiredTypes => new[] { typeof(ExternalLinkButton) }; + + public TestCaseExternalLinkButton() + { + Child = new ExternalLinkButton("https://osu.ppy.sh/home") + { + Size = new Vector2(50) + }; + } + } +} diff --git a/osu.Game.Tests/Visual/TestCaseQuitButton.cs b/osu.Game.Tests/Visual/TestCaseQuitButton.cs new file mode 100644 index 0000000000..a427b7a20a --- /dev/null +++ b/osu.Game.Tests/Visual/TestCaseQuitButton.cs @@ -0,0 +1,53 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.Linq; +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Sprites; +using osu.Game.Screens.Play.HUD; +using OpenTK; +using OpenTK.Input; + +namespace osu.Game.Tests.Visual +{ + [Description("'Hold to Quit' UI element")] + public class TestCaseQuitButton : ManualInputManagerTestCase + { + private bool exitAction; + + [BackgroundDependencyLoader] + private void load() + { + QuitButton quitButton; + + Add(quitButton = new QuitButton + { + Origin = Anchor.BottomRight, + Anchor = Anchor.BottomRight, + Action = () => exitAction = true + }); + + var text = quitButton.Children.OfType().First(); + + AddStep("Trigger text fade in", () => InputManager.MoveMouseTo(quitButton)); + AddUntilStep(() => text.IsPresent && !exitAction, "Text visible"); + AddStep("Trigger text fade out", () => InputManager.MoveMouseTo(Vector2.One)); + AddUntilStep(() => !text.IsPresent && !exitAction, "Text is not visible"); + + AddStep("Trigger exit action", () => + { + exitAction = false; + InputManager.MoveMouseTo(quitButton); + InputManager.PressButton(MouseButton.Left); + }); + + AddStep("Early release", () => InputManager.ReleaseButton(MouseButton.Left)); + AddAssert("action not triggered", () => !exitAction); + + AddStep("Trigger exit action", () => InputManager.PressButton(MouseButton.Left)); + AddUntilStep(() => exitAction, $"{nameof(quitButton.Action)} was triggered"); + } + } +} diff --git a/osu.Game/Graphics/Containers/HoldToConfirmContainer.cs b/osu.Game/Graphics/Containers/HoldToConfirmContainer.cs new file mode 100644 index 0000000000..adfc258f61 --- /dev/null +++ b/osu.Game/Graphics/Containers/HoldToConfirmContainer.cs @@ -0,0 +1,52 @@ +// 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.Configuration; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; + +namespace osu.Game.Graphics.Containers +{ + public abstract class HoldToConfirmContainer : Container + { + public Action Action; + + private const int activate_delay = 400; + private const int fadeout_delay = 200; + + private bool fired; + private bool confirming; + + /// + /// Whether the overlay should be allowed to return from a fired state. + /// + protected virtual bool AllowMultipleFires => false; + + public Bindable Progress = new BindableDouble(); + + protected void BeginConfirm() + { + if (confirming || !AllowMultipleFires && fired) return; + + confirming = true; + + this.TransformBindableTo(Progress, 1, activate_delay * (1 - Progress.Value), Easing.Out).OnComplete(_ => Confirm()); + } + + protected virtual void Confirm() + { + Action?.Invoke(); + fired = true; + } + + protected void AbortConfirm() + { + if (!AllowMultipleFires && fired) return; + + confirming = false; + + this.TransformBindableTo(Progress, 0, fadeout_delay, Easing.Out); + } + } +} diff --git a/osu.Game/Graphics/Cursor/CursorOverrideContainer.cs b/osu.Game/Graphics/Cursor/MenuCursorContainer.cs similarity index 94% rename from osu.Game/Graphics/Cursor/CursorOverrideContainer.cs rename to osu.Game/Graphics/Cursor/MenuCursorContainer.cs index 1e56cb6052..5823fad93a 100644 --- a/osu.Game/Graphics/Cursor/CursorOverrideContainer.cs +++ b/osu.Game/Graphics/Cursor/MenuCursorContainer.cs @@ -12,7 +12,7 @@ namespace osu.Game.Graphics.Cursor /// /// A container which provides a which can be overridden by hovered s. /// - public class CursorOverrideContainer : Container, IProvideCursor + public class MenuCursorContainer : Container, IProvideCursor { protected override Container Content => content; private readonly Container content; @@ -25,7 +25,7 @@ namespace osu.Game.Graphics.Cursor public CursorContainer Cursor { get; } public bool ProvidingUserCursor => true; - public CursorOverrideContainer() + public MenuCursorContainer() { AddRangeInternal(new Drawable[] { diff --git a/osu.Game/Graphics/UserInterface/ExternalLinkButton.cs b/osu.Game/Graphics/UserInterface/ExternalLinkButton.cs new file mode 100644 index 0000000000..77079894cc --- /dev/null +++ b/osu.Game/Graphics/UserInterface/ExternalLinkButton.cs @@ -0,0 +1,63 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.Diagnostics; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Cursor; +using osu.Framework.Input; +using OpenTK; +using OpenTK.Graphics; + +namespace osu.Game.Graphics.UserInterface +{ + public class ExternalLinkButton : CompositeDrawable, IHasTooltip + { + public string Link { get; set; } + + private Color4 hoverColour; + + public ExternalLinkButton(string link = null) + { + Link = link; + Size = new Vector2(12); + InternalChild = new SpriteIcon + { + Icon = FontAwesome.fa_external_link, + RelativeSizeAxes = Axes.Both + }; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + hoverColour = colours.Yellow; + } + + protected override bool OnHover(InputState state) + { + InternalChild.FadeColour(hoverColour, 500, Easing.OutQuint); + return base.OnHover(state); + } + + protected override void OnHoverLost(InputState state) + { + InternalChild.FadeColour(Color4.White, 500, Easing.OutQuint); + base.OnHoverLost(state); + } + + protected override bool OnClick(InputState state) + { + if(Link != null) + Process.Start(new ProcessStartInfo + { + FileName = Link, + UseShellExecute = true //see https://github.com/dotnet/corefx/issues/10361 + }); + return true; + } + + public string TooltipText => "View in browser"; + } +} diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index ec93b1ae46..a43c1507b6 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -212,7 +212,7 @@ namespace osu.Game protected override void LoadComplete() { - // this needs to be cached before base.LoadComplete as it is used by CursorOverrideContainer. + // this needs to be cached before base.LoadComplete as it is used by MenuCursorContainer. dependencies.Cache(screenshotManager = new ScreenshotManager()); base.LoadComplete(); @@ -220,7 +220,7 @@ namespace osu.Game // The next time this is updated is in UpdateAfterChildren, which occurs too late and results // in the cursor being shown for a few frames during the intro. // This prevents the cursor from showing until we have a screen with CursorVisible = true - CursorOverrideContainer.CanShowCursor = currentScreen?.CursorVisible ?? false; + MenuCursorContainer.CanShowCursor = currentScreen?.CursorVisible ?? false; // hook up notifications to components. SkinManager.PostNotification = n => notifications?.Post(n); @@ -548,7 +548,7 @@ namespace osu.Game mainContent.Padding = new MarginPadding { Top = ToolbarOffset }; - CursorOverrideContainer.CanShowCursor = currentScreen?.CursorVisible ?? false; + MenuCursorContainer.CanShowCursor = currentScreen?.CursorVisible ?? false; } private void screenAdded(Screen newScreen) diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 487cb50c9a..a3a081d6d1 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -57,7 +57,7 @@ namespace osu.Game protected SettingsStore SettingsStore; - protected CursorOverrideContainer CursorOverrideContainer; + protected MenuCursorContainer MenuCursorContainer; protected override string MainResourceFile => @"osu.Game.Resources.dll"; @@ -191,14 +191,14 @@ namespace osu.Game GlobalActionContainer globalBinding; - CursorOverrideContainer = new CursorOverrideContainer { RelativeSizeAxes = Axes.Both }; - CursorOverrideContainer.Child = globalBinding = new GlobalActionContainer(this) + MenuCursorContainer = new MenuCursorContainer { RelativeSizeAxes = Axes.Both }; + MenuCursorContainer.Child = globalBinding = new GlobalActionContainer(this) { RelativeSizeAxes = Axes.Both, - Child = content = new OsuTooltipContainer(CursorOverrideContainer.Cursor) { RelativeSizeAxes = Axes.Both } + Child = content = new OsuTooltipContainer(MenuCursorContainer.Cursor) { RelativeSizeAxes = Axes.Both } }; - base.Content.Add(new DrawSizePreservingFillContainer { Child = CursorOverrideContainer }); + base.Content.Add(new DrawSizePreservingFillContainer { Child = MenuCursorContainer }); KeyBindingStore.Register(globalBinding); dependencies.Cache(globalBinding); diff --git a/osu.Game/Overlays/BeatmapSet/Header.cs b/osu.Game/Overlays/BeatmapSet/Header.cs index 9b25d61f58..8833a89479 100644 --- a/osu.Game/Overlays/BeatmapSet/Header.cs +++ b/osu.Game/Overlays/BeatmapSet/Header.cs @@ -11,6 +11,7 @@ using osu.Game.Beatmaps; using osu.Game.Beatmaps.Drawables; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; using osu.Game.Overlays.BeatmapSet.Buttons; using OpenTK; using OpenTK.Graphics; @@ -93,6 +94,7 @@ namespace osu.Game.Overlays.BeatmapSet public Header() { + ExternalLinkButton externalLink; RelativeSizeAxes = Axes.X; Height = 400; Masking = true; @@ -160,10 +162,24 @@ namespace osu.Game.Overlays.BeatmapSet Height = 113, Child = Picker = new BeatmapPicker(), }, - title = new OsuSpriteText + new FillFlowContainer { - Font = @"Exo2.0-BoldItalic", - TextSize = 37, + Direction = FillDirection.Horizontal, + AutoSizeAxes = Axes.Both, + Children = new Drawable[] + { + title = new OsuSpriteText + { + Font = @"Exo2.0-BoldItalic", + TextSize = 37, + }, + externalLink = new ExternalLinkButton + { + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + Margin = new MarginPadding { Left = 3, Bottom = 4 }, //To better lineup with the font + }, + } }, artist = new OsuSpriteText { @@ -247,6 +263,7 @@ namespace osu.Game.Overlays.BeatmapSet }; Picker.Beatmap.ValueChanged += b => Details.Beatmap = b; + Picker.Beatmap.ValueChanged += b => externalLink.Link = $@"https://osu.ppy.sh/beatmapsets/{BeatmapSet?.OnlineBeatmapSetID}#{b?.Ruleset.ShortName}/{b?.OnlineBeatmapID}"; } [BackgroundDependencyLoader] diff --git a/osu.Game/Overlays/HoldToConfirmOverlay.cs b/osu.Game/Overlays/HoldToConfirmOverlay.cs index a0e4bf1a39..7e2f6f5891 100644 --- a/osu.Game/Overlays/HoldToConfirmOverlay.cs +++ b/osu.Game/Overlays/HoldToConfirmOverlay.cs @@ -1,11 +1,10 @@ // 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.Allocation; using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics.Containers; using OpenTK.Graphics; namespace osu.Game.Overlays @@ -14,22 +13,10 @@ namespace osu.Game.Overlays /// An overlay which will display a black screen that dims over a period before confirming an exit action. /// Action is BYO (derived class will need to call and from a user event). /// - public abstract class HoldToConfirmOverlay : Container + public abstract class HoldToConfirmOverlay : HoldToConfirmContainer { - public Action Action; - private Box overlay; - private const int activate_delay = 400; - private const int fadeout_delay = 200; - - private bool fired; - - /// - /// Whether the overlay should be allowed to return from a fired state. - /// - protected virtual bool AllowMultipleFires => false; - [BackgroundDependencyLoader] private void load() { @@ -45,22 +32,8 @@ namespace osu.Game.Overlays RelativeSizeAxes = Axes.Both, } }; - } - protected void BeginConfirm() - { - if (!AllowMultipleFires && fired) return; - overlay.FadeIn(activate_delay * (1 - overlay.Alpha), Easing.Out).OnComplete(_ => - { - Action?.Invoke(); - fired = true; - }); - } - - protected void AbortConfirm() - { - if (!AllowMultipleFires && fired) return; - overlay.FadeOut(fadeout_delay, Easing.Out); + Progress.ValueChanged += v => overlay.Alpha = (float)v; } } } diff --git a/osu.Game/Overlays/Music/PlaylistList.cs b/osu.Game/Overlays/Music/PlaylistList.cs index 8c8ff89420..d4bc8c5b27 100644 --- a/osu.Game/Overlays/Music/PlaylistList.cs +++ b/osu.Game/Overlays/Music/PlaylistList.cs @@ -101,7 +101,7 @@ namespace osu.Game.Overlays.Music private void updateSelectedSet() { foreach (PlaylistItem s in items.Children) - s.Selected = s.BeatmapSetInfo.ID == beatmapBacking.Value.BeatmapSetInfo.ID; + s.Selected = s.BeatmapSetInfo.ID == beatmapBacking.Value.BeatmapSetInfo?.ID; } public string SearchTerm diff --git a/osu.Game/Overlays/Music/PlaylistOverlay.cs b/osu.Game/Overlays/Music/PlaylistOverlay.cs index 76c2222f8b..085a44ecba 100644 --- a/osu.Game/Overlays/Music/PlaylistOverlay.cs +++ b/osu.Game/Overlays/Music/PlaylistOverlay.cs @@ -79,7 +79,10 @@ namespace osu.Game.Overlays.Music { BeatmapInfo beatmap = list.FirstVisibleSet?.Beatmaps?.FirstOrDefault(); if (beatmap != null) + { beatmapBacking.Value = beatmaps.GetWorkingBeatmap(beatmap); + beatmapBacking.Value.Track.Restart(); + } }; } @@ -109,6 +112,7 @@ namespace osu.Game.Overlays.Music } beatmapBacking.Value = beatmaps.GetWorkingBeatmap(set.Beatmaps.First()); + beatmapBacking.Value.Track.Restart(); } } diff --git a/osu.Game/Overlays/Profile/ProfileHeader.cs b/osu.Game/Overlays/Profile/ProfileHeader.cs index 4c411b3210..067144c26e 100644 --- a/osu.Game/Overlays/Profile/ProfileHeader.cs +++ b/osu.Game/Overlays/Profile/ProfileHeader.cs @@ -2,7 +2,6 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using System; -using System.Diagnostics; using OpenTK; using OpenTK.Graphics; using osu.Framework.Allocation; @@ -10,13 +9,13 @@ using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; using osu.Game.Overlays.Profile.Header; using osu.Game.Users; @@ -105,11 +104,28 @@ namespace osu.Game.Overlays.Profile Y = -75, Size = new Vector2(25, 25) }, - new ProfileLink(user) + new FillFlowContainer { + Direction = FillDirection.Horizontal, + AutoSizeAxes = Axes.Both, Anchor = Anchor.BottomLeft, Origin = Anchor.BottomLeft, Y = -48, + Children = new Drawable[] + { + new OsuSpriteText + { + Text = user.Username, + Font = @"Exo2.0-RegularItalic", + TextSize = 30, + }, + new ExternalLinkButton($@"https://osu.ppy.sh/users/{user.Id}") + { + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + Margin = new MarginPadding { Left = 3, Bottom = 3 }, //To better lineup with the font + }, + } }, countryFlag = new DrawableFlag(user.Country) { @@ -455,28 +471,6 @@ namespace osu.Game.Overlays.Profile infoTextRight.NewLine(); } - private class ProfileLink : OsuHoverContainer, IHasTooltip - { - public string TooltipText => "View Profile in Browser"; - - public override bool HandleMouseInput => true; - - public ProfileLink(User user) - { - Action = () => Process.Start($@"https://osu.ppy.sh/users/{user.Id}"); - - AutoSizeAxes = Axes.Both; - - Child = new OsuSpriteText - { - Text = user.Username, - Font = @"Exo2.0-RegularItalic", - TextSize = 30, - }; - } - } - - private class GradeBadge : Container { private const float width = 50; diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index b657fe5597..be08fffc77 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -48,7 +48,7 @@ namespace osu.Game.Screens.Edit { // TODO: should probably be done at a RulesetContainer level to share logic with Player. var sourceClock = (IAdjustableClock)Beatmap.Value.Track ?? new StopwatchClock(); - clock = new EditorClock(Beatmap.Value.Beatmap.ControlPointInfo, beatDivisor) { IsCoupled = false }; + clock = new EditorClock(Beatmap, beatDivisor) { IsCoupled = false }; clock.ChangeSource(sourceClock); dependencies.CacheAs(clock); diff --git a/osu.Game/Screens/Edit/EditorClock.cs b/osu.Game/Screens/Edit/EditorClock.cs index 67025f0620..72fb91e7df 100644 --- a/osu.Game/Screens/Edit/EditorClock.cs +++ b/osu.Game/Screens/Edit/EditorClock.cs @@ -3,10 +3,13 @@ using System; using System.Linq; +using osu.Framework.Configuration; using osu.Framework.MathUtils; using osu.Framework.Timing; +using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Screens.Edit.Screens.Compose; +using OpenTK; namespace osu.Game.Screens.Edit { @@ -15,15 +18,26 @@ namespace osu.Game.Screens.Edit /// public class EditorClock : DecoupleableInterpolatingFramedClock { + public readonly double TrackLength; + public ControlPointInfo ControlPointInfo; private readonly BindableBeatDivisor beatDivisor; - public EditorClock(ControlPointInfo controlPointInfo, BindableBeatDivisor beatDivisor) + public EditorClock(Bindable beatmap, BindableBeatDivisor beatDivisor) + { + this.beatDivisor = beatDivisor; + + ControlPointInfo = beatmap.Value.Beatmap.ControlPointInfo; + TrackLength = beatmap.Value.Track.Length; + } + + public EditorClock(ControlPointInfo controlPointInfo, double trackLength, BindableBeatDivisor beatDivisor) { this.beatDivisor = beatDivisor; ControlPointInfo = controlPointInfo; + TrackLength = trackLength; } /// @@ -111,6 +125,8 @@ namespace osu.Game.Screens.Edit if (seekTime > nextTimingPoint?.Time) seekTime = nextTimingPoint.Time; + // Ensure the sought point is within the boundaries + seekTime = MathHelper.Clamp(seekTime, 0, TrackLength); Seek(seekTime); } } diff --git a/osu.Game/Screens/Menu/ButtonSystem.cs b/osu.Game/Screens/Menu/ButtonSystem.cs index d1d388ae1f..8b88204ed0 100644 --- a/osu.Game/Screens/Menu/ButtonSystem.cs +++ b/osu.Game/Screens/Menu/ButtonSystem.cs @@ -154,6 +154,8 @@ namespace osu.Game.Screens.Menu case Key.Space: logo?.TriggerOnClick(state); return true; + case Key.Escape: + return goBack(); } return false; @@ -164,17 +166,22 @@ namespace osu.Game.Screens.Menu switch (action) { case GlobalAction.Back: - switch (State) - { - case MenuState.TopLevel: - State = MenuState.Initial; - return true; - case MenuState.Play: - backButton.TriggerOnClick(); - return true; - default: - return false; - } + return goBack(); + default: + return false; + } + } + + private bool goBack() + { + switch (State) + { + case MenuState.TopLevel: + State = MenuState.Initial; + return true; + case MenuState.Play: + backButton.TriggerOnClick(); + return true; default: return false; } @@ -328,6 +335,9 @@ namespace osu.Game.Screens.Menu logoDelayedAction = Scheduler.AddDelayed(() => { + hideOverlaysOnEnter.Value = true; + allowOpeningOverlays.Value = false; + logo.ClearTransforms(targetMember: nameof(Position)); logo.RelativePositionAxes = Axes.Both; @@ -355,6 +365,7 @@ namespace osu.Game.Screens.Menu logoTracking = true; logo.Impact(); + hideOverlaysOnEnter.Value = false; allowOpeningOverlays.Value = true; }, 200); diff --git a/osu.Game/Screens/Menu/Disclaimer.cs b/osu.Game/Screens/Menu/Disclaimer.cs index 9a671cf780..b8cb7f2a4a 100644 --- a/osu.Game/Screens/Menu/Disclaimer.cs +++ b/osu.Game/Screens/Menu/Disclaimer.cs @@ -19,6 +19,7 @@ namespace osu.Game.Screens.Menu private Color4 iconColour; protected override bool HideOverlaysOnEnter => true; + protected override bool AllowOpeningOverlays => false; public override bool CursorVisible => false; diff --git a/osu.Game/Screens/OsuScreen.cs b/osu.Game/Screens/OsuScreen.cs index 4b1562291b..a188b7aa64 100644 --- a/osu.Game/Screens/OsuScreen.cs +++ b/osu.Game/Screens/OsuScreen.cs @@ -37,14 +37,14 @@ namespace osu.Game.Screens /// /// Whether overlays should be hidden when this screen is entered or resumed. /// - protected virtual bool HideOverlaysOnEnter => hideOverlaysOnEnter; + protected virtual bool HideOverlaysOnEnter => false; private readonly BindableBool allowOpeningOverlays = new BindableBool(); /// /// Whether overlays should be able to be opened while this screen is active. /// - protected virtual bool AllowOpeningOverlays => allowOpeningOverlays; + protected virtual bool AllowOpeningOverlays => true; /// /// Whether this allows the cursor to be displayed. diff --git a/osu.Game/Screens/Play/HUD/QuitButton.cs b/osu.Game/Screens/Play/HUD/QuitButton.cs new file mode 100644 index 0000000000..d0aa0dad92 --- /dev/null +++ b/osu.Game/Screens/Play/HUD/QuitButton.cs @@ -0,0 +1,198 @@ +// 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.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.UserInterface; +using osu.Framework.Input; +using osu.Framework.MathUtils; +using osu.Game.Graphics; +using osu.Game.Graphics.Containers; +using osu.Game.Graphics.Sprites; +using OpenTK; + +namespace osu.Game.Screens.Play.HUD +{ + public class QuitButton : FillFlowContainer + { + public override bool ReceiveMouseInputAt(Vector2 screenSpacePos) => true; + + private readonly Button button; + + public Action Action + { + set => button.Action = value; + } + + private readonly OsuSpriteText text; + + public QuitButton() + { + Direction = FillDirection.Horizontal; + Spacing = new Vector2(20, 0); + Margin = new MarginPadding(10); + Children = new Drawable[] + { + text = new OsuSpriteText + { + Text = "hold for menu", + Font = @"Exo2.0-Bold", + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft + }, + button = new Button + { + HoverGained = () => text.FadeIn(500, Easing.OutQuint), + HoverLost = () => text.FadeOut(500, Easing.OutQuint) + } + }; + AutoSizeAxes = Axes.Both; + } + + protected override void LoadComplete() + { + text.FadeInFromZero(500, Easing.OutQuint).Delay(1500).FadeOut(500, Easing.OutQuint); + base.LoadComplete(); + } + + private float positionalAdjust; + + protected override bool OnMouseMove(InputState state) + { + positionalAdjust = Vector2.Distance(state.Mouse.NativeState.Position, button.ScreenSpaceDrawQuad.Centre) / 200; + return base.OnMouseMove(state); + } + + protected override void Update() + { + base.Update(); + + if (text.Alpha > 0 || button.Progress.Value > 0 || button.IsHovered) + Alpha = 1; + else + Alpha = Interpolation.ValueAt( + MathHelper.Clamp(Clock.ElapsedFrameTime, 0, 1000), + Alpha, MathHelper.Clamp(1 - positionalAdjust, 0.04f, 1), 0, 200, Easing.OutQuint); + } + + private class Button : HoldToConfirmContainer + { + private SpriteIcon icon; + private CircularProgress circularProgress; + private Circle overlayCircle; + + protected override bool AllowMultipleFires => true; + + public Action HoverGained; + public Action HoverLost; + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + Size = new Vector2(60); + + Child = new CircularContainer + { + Masking = true, + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = colours.Gray1, + Alpha = 0.5f, + }, + circularProgress = new CircularProgress + { + RelativeSizeAxes = Axes.Both, + InnerRadius = 1 + }, + overlayCircle = new Circle + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + Colour = colours.Gray1, + Size = new Vector2(0.9f), + }, + icon = new SpriteIcon + { + Shadow = false, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(15), + Icon = FontAwesome.fa_close + }, + } + }; + + bind(); + } + + private void bind() + { + circularProgress.Current.BindTo(Progress); + Progress.ValueChanged += v => icon.Scale = new Vector2(1 + (float)v * 0.2f); + } + + private bool pendingAnimation; + + protected override void Confirm() + { + base.Confirm(); + + // temporarily unbind as to not look weird if releasing during confirm animation (can see the unwind of progress). + Progress.UnbindAll(); + + // avoid starting a new confirm call until we finish animating. + pendingAnimation = true; + + Progress.Value = 0; + + overlayCircle.ScaleTo(0, 100) + .Then().FadeOut().ScaleTo(1).FadeIn(500) + .OnComplete(a => + { + icon.ScaleTo(1, 100); + circularProgress.FadeOut(100).OnComplete(_ => + { + bind(); + + circularProgress.FadeIn(); + pendingAnimation = false; + }); + }); + } + + protected override bool OnHover(InputState state) + { + HoverGained?.Invoke(); + return true; + } + + protected override void OnHoverLost(InputState state) + { + HoverLost?.Invoke(); + base.OnHoverLost(state); + } + + protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) + { + if (!pendingAnimation && state.Mouse.Buttons.Count == 1) + BeginConfirm(); + return true; + } + + protected override bool OnMouseUp(InputState state, MouseUpEventArgs args) + { + if (state.Mouse.Buttons.Count == 0) + AbortConfirm(); + return true; + } + } + } +} diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index 36d8bb75c0..f920b20649 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -34,6 +34,7 @@ namespace osu.Game.Screens.Play public readonly HealthDisplay HealthDisplay; public readonly SongProgress Progress; public readonly ModDisplay ModDisplay; + public readonly QuitButton HoldToQuit; public readonly PlayerSettingsOverlay PlayerSettingsOverlay; private Bindable showHud; @@ -51,14 +52,26 @@ namespace osu.Game.Screens.Play Children = new Drawable[] { - KeyCounter = CreateKeyCounter(), ComboCounter = CreateComboCounter(), ScoreCounter = CreateScoreCounter(), AccuracyCounter = CreateAccuracyCounter(), HealthDisplay = CreateHealthDisplay(), Progress = CreateProgress(), ModDisplay = CreateModsContainer(), - PlayerSettingsOverlay = CreatePlayerSettingsOverlay() + PlayerSettingsOverlay = CreatePlayerSettingsOverlay(), + new FillFlowContainer + { + Anchor = Anchor.BottomRight, + Origin = Anchor.BottomRight, + Position = -new Vector2(5, TwoLayerButton.SIZE_RETRACTED.Y), + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, + Children = new Drawable[] + { + KeyCounter = CreateKeyCounter(), + HoldToQuit = CreateQuitButton(), + } + } } }); @@ -187,7 +200,6 @@ namespace osu.Game.Screens.Play Anchor = Anchor.BottomRight, Origin = Anchor.BottomRight, Margin = new MarginPadding(10), - Y = -TwoLayerButton.SIZE_RETRACTED.Y, }; protected virtual ScoreCounter CreateScoreCounter() => new ScoreCounter(6) @@ -205,6 +217,12 @@ namespace osu.Game.Screens.Play RelativeSizeAxes = Axes.X, }; + protected virtual QuitButton CreateQuitButton() => new QuitButton + { + Anchor = Anchor.BottomRight, + Origin = Anchor.BottomRight, + }; + protected virtual ModDisplay CreateModsContainer() => new ModDisplay { Anchor = Anchor.TopRight, diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index ec7a99145e..0150d76251 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -183,6 +183,7 @@ namespace osu.Game.Screens.Play ProcessCustomClock = false, Breaks = beatmap.Breaks }, + RulesetContainer.Cursor?.CreateProxy() ?? new Container(), hudOverlay = new HUDOverlay(scoreProcessor, RulesetContainer, working, offsetClock, adjustableClock) { Clock = Clock, // hud overlay doesn't want to use the audio clock directly @@ -190,7 +191,6 @@ namespace osu.Game.Screens.Play Anchor = Anchor.Centre, Origin = Anchor.Centre }, - RulesetContainer.Cursor?.CreateProxy() ?? new Container(), new SkipOverlay(firstObjectTime) { Clock = Clock, // skip button doesn't want to use the audio clock directly @@ -219,6 +219,8 @@ namespace osu.Game.Screens.Play } }; + hudOverlay.HoldToQuit.Action = Exit; + if (ShowStoryboard) initializeStoryboard(false); diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs index 734837a4f1..9c8961498a 100644 --- a/osu.Game/Screens/Play/PlayerLoader.cs +++ b/osu.Game/Screens/Play/PlayerLoader.cs @@ -7,15 +7,15 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; using osu.Framework.Input; +using osu.Framework.Localisation; using osu.Framework.Screens; +using osu.Framework.Threading; using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; -using OpenTK; -using osu.Framework.Localisation; -using osu.Framework.Threading; using osu.Game.Screens.Menu; using osu.Game.Screens.Play.PlayerSettings; +using OpenTK; namespace osu.Game.Screens.Play { @@ -51,11 +51,19 @@ namespace osu.Game.Screens.Play Origin = Anchor.Centre, }); - Add(new VisualSettings + Add(new FillFlowContainer { Anchor = Anchor.TopRight, Origin = Anchor.TopRight, - Margin = new MarginPadding(25) + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, + Spacing = new Vector2(0, 20), + Margin = new MarginPadding(25), + Children = new PlayerSettingsGroup[] + { + new VisualSettings(), + new InputSettings() + } }); loadTask = LoadComponentAsync(player); diff --git a/osu.Game/Screens/Play/PlayerSettings/InputSettings.cs b/osu.Game/Screens/Play/PlayerSettings/InputSettings.cs new file mode 100644 index 0000000000..755ba468cc --- /dev/null +++ b/osu.Game/Screens/Play/PlayerSettings/InputSettings.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.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Game.Configuration; + +namespace osu.Game.Screens.Play.PlayerSettings +{ + public class InputSettings : PlayerSettingsGroup + { + protected override string Title => "Input settings"; + + private readonly PlayerCheckbox mouseButtonsCheckbox; + + public InputSettings() + { + Children = new Drawable[] + { + mouseButtonsCheckbox = new PlayerCheckbox + { + LabelText = "Disable mouse buttons" + } + }; + } + + [BackgroundDependencyLoader] + private void load(OsuConfigManager config) => mouseButtonsCheckbox.Bindable = config.GetBindable(OsuSetting.MouseDisableButtons); + } +} diff --git a/osu.Game/Tests/Visual/EditorClockTestCase.cs b/osu.Game/Tests/Visual/EditorClockTestCase.cs index 43b20f7021..85d3684530 100644 --- a/osu.Game/Tests/Visual/EditorClockTestCase.cs +++ b/osu.Game/Tests/Visual/EditorClockTestCase.cs @@ -29,7 +29,7 @@ namespace osu.Game.Tests.Visual protected EditorClockTestCase() { - Clock = new EditorClock(new ControlPointInfo(), BeatDivisor) { IsCoupled = false }; + Clock = new EditorClock(new ControlPointInfo(), 5000, BeatDivisor) { IsCoupled = false }; } [BackgroundDependencyLoader]