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]