diff --git a/.gitattributes b/.gitattributes index baf69b41d1..e3ffd343db 100644 --- a/.gitattributes +++ b/.gitattributes @@ -14,6 +14,7 @@ App.config text eol=crlf *.bat text eol=crlf *.cmd text eol=crlf *.snippet text eol=crlf +*.manifest text eol=crlf # Check out with lf (UNIX) line endings *.sh text eol=lf diff --git a/app.manifest b/app.manifest new file mode 100644 index 0000000000..533c6ff208 --- /dev/null +++ b/app.manifest @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + true + + + + + + + + diff --git a/osu-framework b/osu-framework index 02d7a0fa47..16e6a453db 160000 --- a/osu-framework +++ b/osu-framework @@ -1 +1 @@ -Subproject commit 02d7a0fa4798d197cd08570ee48951edbb7c7860 +Subproject commit 16e6a453db9a8f4454238a2911eb5f1444b7ec2a diff --git a/osu.Desktop.Deploy/osu.Desktop.Deploy.csproj b/osu.Desktop.Deploy/osu.Desktop.Deploy.csproj index a18db9477a..6b7509a381 100644 --- a/osu.Desktop.Deploy/osu.Desktop.Deploy.csproj +++ b/osu.Desktop.Deploy/osu.Desktop.Deploy.csproj @@ -11,7 +11,7 @@ - + diff --git a/osu.Game.Rulesets.Catch/CatchRuleset.cs b/osu.Game.Rulesets.Catch/CatchRuleset.cs index 8930b09089..cfe0fc5cec 100644 --- a/osu.Game.Rulesets.Catch/CatchRuleset.cs +++ b/osu.Game.Rulesets.Catch/CatchRuleset.cs @@ -12,6 +12,7 @@ using osu.Framework.Graphics; using osu.Framework.Input.Bindings; using osu.Game.Rulesets.Catch.Replays; using osu.Game.Rulesets.Replays.Types; +using osu.Game.Beatmaps.Legacy; namespace osu.Game.Rulesets.Catch { @@ -29,6 +30,44 @@ namespace osu.Game.Rulesets.Catch new KeyBinding(InputKey.Shift, CatchAction.Dash), }; + public override IEnumerable ConvertLegacyMods(LegacyMods mods) + { + if (mods.HasFlag(LegacyMods.Nightcore)) + yield return new CatchModNightcore(); + else if (mods.HasFlag(LegacyMods.DoubleTime)) + yield return new CatchModDoubleTime(); + + if (mods.HasFlag(LegacyMods.Autoplay)) + yield return new CatchModAutoplay(); + + if (mods.HasFlag(LegacyMods.Easy)) + yield return new CatchModEasy(); + + if (mods.HasFlag(LegacyMods.Flashlight)) + yield return new CatchModFlashlight(); + + if (mods.HasFlag(LegacyMods.HalfTime)) + yield return new CatchModHalfTime(); + + if (mods.HasFlag(LegacyMods.HardRock)) + yield return new CatchModHardRock(); + + if (mods.HasFlag(LegacyMods.Hidden)) + yield return new CatchModHidden(); + + if (mods.HasFlag(LegacyMods.NoFail)) + yield return new CatchModNoFail(); + + if (mods.HasFlag(LegacyMods.Perfect)) + yield return new CatchModPerfect(); + + if (mods.HasFlag(LegacyMods.Relax)) + yield return new CatchModRelax(); + + if (mods.HasFlag(LegacyMods.SuddenDeath)) + yield return new CatchModSuddenDeath(); + } + public override IEnumerable GetModsFor(ModType type) { switch (type) diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModHardRock.cs b/osu.Game.Rulesets.Catch/Mods/CatchModHardRock.cs index 9e48e8de74..df7578799f 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModHardRock.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModHardRock.cs @@ -1,13 +1,88 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +using osu.Framework.MathUtils; +using osu.Game.Rulesets.Catch.Objects; +using osu.Game.Rulesets.Catch.UI; using osu.Game.Rulesets.Mods; +using System; namespace osu.Game.Rulesets.Catch.Mods { - public class CatchModHardRock : ModHardRock + public class CatchModHardRock : ModHardRock, IApplicableToHitObject { public override double ScoreMultiplier => 1.12; public override bool Ranked => true; + + private float lastStartX; + private int lastStartTime; + + public void ApplyToHitObject(CatchHitObject hitObject) + { + float position = hitObject.X; + int startTime = (int)hitObject.StartTime; + + if (lastStartX == 0) + { + lastStartX = position; + lastStartTime = startTime; + return; + } + + float diff = lastStartX - position; + int timeDiff = startTime - lastStartTime; + + if (timeDiff > 1000) + { + lastStartX = position; + lastStartTime = startTime; + return; + } + + if (diff == 0) + { + bool right = RNG.NextBool(); + + float rand = Math.Min(20, (float)RNG.NextDouble(0, timeDiff / 4d)) / CatchPlayfield.BASE_WIDTH; + + if (right) + { + if (position + rand <= 1) + position += rand; + else + position -= rand; + } + else + { + if (position - rand >= 0) + position -= rand; + else + position += rand; + } + + hitObject.X = position; + + return; + } + + if (Math.Abs(diff) < timeDiff / 3d) + { + if (diff > 0) + { + if (position - diff > 0) + position -= diff; + } + else + { + if (position - diff < 1) + position -= diff; + } + } + + hitObject.X = position; + + lastStartX = position; + lastStartTime = startTime; + } } } diff --git a/osu.Game.Rulesets.Mania/ManiaRuleset.cs b/osu.Game.Rulesets.Mania/ManiaRuleset.cs index 7f37f55d14..0546cbc174 100644 --- a/osu.Game.Rulesets.Mania/ManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/ManiaRuleset.cs @@ -14,6 +14,7 @@ using osu.Framework.Input.Bindings; using osu.Game.Graphics; using osu.Game.Rulesets.Mania.Replays; using osu.Game.Rulesets.Replays.Types; +using osu.Game.Beatmaps.Legacy; namespace osu.Game.Rulesets.Mania { @@ -21,6 +22,74 @@ namespace osu.Game.Rulesets.Mania { public override RulesetContainer CreateRulesetContainerWith(WorkingBeatmap beatmap, bool isForCurrentRuleset) => new ManiaRulesetContainer(this, beatmap, isForCurrentRuleset); + public override IEnumerable ConvertLegacyMods(LegacyMods mods) + { + if (mods.HasFlag(LegacyMods.Nightcore)) + yield return new ManiaModNightcore(); + else if (mods.HasFlag(LegacyMods.DoubleTime)) + yield return new ManiaModDoubleTime(); + + if (mods.HasFlag(LegacyMods.Autoplay)) + yield return new ManiaModAutoplay(); + + if (mods.HasFlag(LegacyMods.Easy)) + yield return new ManiaModEasy(); + + if (mods.HasFlag(LegacyMods.FadeIn)) + yield return new ManiaModFadeIn(); + + if (mods.HasFlag(LegacyMods.Flashlight)) + yield return new ManiaModFlashlight(); + + if (mods.HasFlag(LegacyMods.HalfTime)) + yield return new ManiaModHalfTime(); + + if (mods.HasFlag(LegacyMods.HardRock)) + yield return new ManiaModHardRock(); + + if (mods.HasFlag(LegacyMods.Hidden)) + yield return new ManiaModHidden(); + + if (mods.HasFlag(LegacyMods.Key1)) + yield return new ManiaModKey1(); + + if (mods.HasFlag(LegacyMods.Key2)) + yield return new ManiaModKey2(); + + if (mods.HasFlag(LegacyMods.Key3)) + yield return new ManiaModKey3(); + + if (mods.HasFlag(LegacyMods.Key4)) + yield return new ManiaModKey4(); + + if (mods.HasFlag(LegacyMods.Key5)) + yield return new ManiaModKey5(); + + if (mods.HasFlag(LegacyMods.Key6)) + yield return new ManiaModKey6(); + + if (mods.HasFlag(LegacyMods.Key7)) + yield return new ManiaModKey7(); + + if (mods.HasFlag(LegacyMods.Key8)) + yield return new ManiaModKey8(); + + if (mods.HasFlag(LegacyMods.Key9)) + yield return new ManiaModKey9(); + + if (mods.HasFlag(LegacyMods.NoFail)) + yield return new ManiaModNoFail(); + + if (mods.HasFlag(LegacyMods.Perfect)) + yield return new ManiaModPerfect(); + + if (mods.HasFlag(LegacyMods.Random)) + yield return new ManiaModRandom(); + + if (mods.HasFlag(LegacyMods.SuddenDeath)) + yield return new ManiaModSuddenDeath(); + } + public override IEnumerable GetModsFor(ModType type) { switch (type) diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs index 37e6ec3817..e0ecee97a3 100644 --- a/osu.Game.Rulesets.Osu/OsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs @@ -21,6 +21,7 @@ using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Osu.Replays; using osu.Game.Rulesets.Replays.Types; +using osu.Game.Beatmaps.Legacy; namespace osu.Game.Rulesets.Osu { @@ -66,6 +67,53 @@ namespace osu.Game.Rulesets.Osu }; } + public override IEnumerable ConvertLegacyMods(LegacyMods mods) + { + if (mods.HasFlag(LegacyMods.Nightcore)) + yield return new OsuModNightcore(); + else if (mods.HasFlag(LegacyMods.DoubleTime)) + yield return new OsuModDoubleTime(); + + if (mods.HasFlag(LegacyMods.Autopilot)) + yield return new OsuModAutopilot(); + + if (mods.HasFlag(LegacyMods.Autoplay)) + yield return new OsuModAutoplay(); + + if (mods.HasFlag(LegacyMods.Easy)) + yield return new OsuModEasy(); + + if (mods.HasFlag(LegacyMods.Flashlight)) + yield return new OsuModFlashlight(); + + if (mods.HasFlag(LegacyMods.HalfTime)) + yield return new OsuModHalfTime(); + + if (mods.HasFlag(LegacyMods.HardRock)) + yield return new OsuModHardRock(); + + if (mods.HasFlag(LegacyMods.Hidden)) + yield return new OsuModHidden(); + + if (mods.HasFlag(LegacyMods.NoFail)) + yield return new OsuModNoFail(); + + if (mods.HasFlag(LegacyMods.Perfect)) + yield return new OsuModPerfect(); + + if (mods.HasFlag(LegacyMods.Relax)) + yield return new OsuModRelax(); + + if (mods.HasFlag(LegacyMods.SpunOut)) + yield return new OsuModSpunOut(); + + if (mods.HasFlag(LegacyMods.SuddenDeath)) + yield return new OsuModSuddenDeath(); + + if (mods.HasFlag(LegacyMods.Target)) + yield return new OsuModTarget(); + } + public override IEnumerable GetModsFor(ModType type) { switch (type) diff --git a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs index cb5e020601..06a8e44a14 100644 --- a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs @@ -12,6 +12,7 @@ using osu.Framework.Graphics; using osu.Framework.Input.Bindings; using osu.Game.Rulesets.Replays.Types; using osu.Game.Rulesets.Taiko.Replays; +using osu.Game.Beatmaps.Legacy; namespace osu.Game.Rulesets.Taiko { @@ -31,6 +32,44 @@ namespace osu.Game.Rulesets.Taiko new KeyBinding(InputKey.MouseRight, TaikoAction.RightRim), }; + public override IEnumerable ConvertLegacyMods(LegacyMods mods) + { + if (mods.HasFlag(LegacyMods.Nightcore)) + yield return new TaikoModNightcore(); + else if (mods.HasFlag(LegacyMods.DoubleTime)) + yield return new TaikoModDoubleTime(); + + if (mods.HasFlag(LegacyMods.Autoplay)) + yield return new TaikoModAutoplay(); + + if (mods.HasFlag(LegacyMods.Easy)) + yield return new TaikoModEasy(); + + if (mods.HasFlag(LegacyMods.Flashlight)) + yield return new TaikoModFlashlight(); + + if (mods.HasFlag(LegacyMods.HalfTime)) + yield return new TaikoModHalfTime(); + + if (mods.HasFlag(LegacyMods.HardRock)) + yield return new TaikoModHardRock(); + + if (mods.HasFlag(LegacyMods.Hidden)) + yield return new TaikoModHidden(); + + if (mods.HasFlag(LegacyMods.NoFail)) + yield return new TaikoModNoFail(); + + if (mods.HasFlag(LegacyMods.Perfect)) + yield return new TaikoModPerfect(); + + if (mods.HasFlag(LegacyMods.Relax)) + yield return new TaikoModRelax(); + + if (mods.HasFlag(LegacyMods.SuddenDeath)) + yield return new TaikoModSuddenDeath(); + } + public override IEnumerable GetModsFor(ModType type) { switch (type) diff --git a/osu.Game.Tests/Visual/TestCaseBadgeContainer.cs b/osu.Game.Tests/Visual/TestCaseBadgeContainer.cs new file mode 100644 index 0000000000..8177e2e272 --- /dev/null +++ b/osu.Game.Tests/Visual/TestCaseBadgeContainer.cs @@ -0,0 +1,62 @@ +// 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 NUnit.Framework; +using osu.Framework.Graphics; +using osu.Game.Overlays.Profile.Header; +using osu.Game.Users; + +namespace osu.Game.Tests.Visual +{ + [TestFixture] + public class TestCaseBadgeContainer : OsuTestCase + { + public override IReadOnlyList RequiredTypes => new[] { typeof(BadgeContainer) }; + + public TestCaseBadgeContainer() + { + BadgeContainer badgeContainer; + + Child = badgeContainer = new BadgeContainer + { + RelativeSizeAxes = Axes.Both + }; + + AddStep("Show 1 badge", () => badgeContainer.ShowBadges(new[] + { + new Badge + { + AwardedAt = DateTimeOffset.Now, + Description = "Appreciates compasses", + ImageUrl = "https://assets.ppy.sh/profile-badges/mg2018-1star.png", + } + })); + + AddStep("Show 2 badges", () => badgeContainer.ShowBadges(new[] + { + new Badge + { + AwardedAt = DateTimeOffset.Now, + Description = "Contributed to osu!lazer testing", + ImageUrl = "https://assets.ppy.sh/profile-badges/contributor.png", + }, + new Badge + { + AwardedAt = DateTimeOffset.Now, + Description = "Appreciates compasses", + ImageUrl = "https://assets.ppy.sh/profile-badges/mg2018-1star.png", + } + })); + + AddStep("Show many badges", () => badgeContainer.ShowBadges(Enumerable.Range(1, 20).Select(i => new Badge + { + AwardedAt = DateTimeOffset.Now, + Description = $"Contributed to osu!lazer testing {i} times", + ImageUrl = "https://assets.ppy.sh/profile-badges/contributor.jpg", + }).ToArray())); + } + } +} diff --git a/osu.Game.Tests/Visual/TestCaseRankGraph.cs b/osu.Game.Tests/Visual/TestCaseRankGraph.cs index 45f6651537..f5558620ad 100644 --- a/osu.Game.Tests/Visual/TestCaseRankGraph.cs +++ b/osu.Game.Tests/Visual/TestCaseRankGraph.cs @@ -1,7 +1,6 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using osu.Game.Overlays.Profile; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using OpenTK; @@ -11,6 +10,7 @@ using System.Collections.Generic; using System; using NUnit.Framework; using osu.Game.Graphics.UserInterface; +using osu.Game.Overlays.Profile.Header; using osu.Game.Users; namespace osu.Game.Tests.Visual diff --git a/osu.Game.Tests/Visual/TestCaseUserProfile.cs b/osu.Game.Tests/Visual/TestCaseUserProfile.cs index b060b9f2f8..0fdc01a974 100644 --- a/osu.Game.Tests/Visual/TestCaseUserProfile.cs +++ b/osu.Game.Tests/Visual/TestCaseUserProfile.cs @@ -8,6 +8,7 @@ using NUnit.Framework; using osu.Game.Graphics.UserInterface; using osu.Game.Overlays; using osu.Game.Overlays.Profile; +using osu.Game.Overlays.Profile.Header; using osu.Game.Users; namespace osu.Game.Tests.Visual @@ -23,6 +24,7 @@ namespace osu.Game.Tests.Visual typeof(UserProfileOverlay), typeof(RankGraph), typeof(LineGraph), + typeof(BadgeContainer) }; public TestCaseUserProfile() @@ -53,6 +55,15 @@ namespace osu.Game.Tests.Visual { Mode = @"osu", Data = Enumerable.Range(2345, 45).Concat(Enumerable.Range(2109, 40)).ToArray() + }, + Badges = new[] + { + new Badge + { + AwardedAt = DateTimeOffset.FromUnixTimeSeconds(1505741569), + Description = "Outstanding help by being a voluntary test subject.", + ImageUrl = "https://assets.ppy.sh/profile-badges/contributor.jpg" + } } }, false)); diff --git a/osu.Game.Tests/Visual/TestCaseWaveContainer.cs b/osu.Game.Tests/Visual/TestCaseWaveContainer.cs new file mode 100644 index 0000000000..2163d7c3aa --- /dev/null +++ b/osu.Game.Tests/Visual/TestCaseWaveContainer.cs @@ -0,0 +1,54 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics; +using osu.Game.Graphics.Containers; +using osu.Game.Graphics.Sprites; +using OpenTK; +using OpenTK.Graphics; + +namespace osu.Game.Tests.Visual +{ + [TestFixture] + public class TestCaseWaveContainer : OsuTestCase + { + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + WaveContainer container; + Add(container = new WaveContainer + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(400), + FirstWaveColour = colours.Red, + SecondWaveColour = colours.Green, + ThirdWaveColour = colours.Blue, + FourthWaveColour = colours.Pink, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.Black.Opacity(0.5f), + }, + new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + TextSize = 20, + Text = @"Wave Container", + }, + }, + }); + + AddStep(@"show", container.Show); + AddStep(@"hide", container.Hide); + } + } +} diff --git a/osu.Game.props b/osu.Game.props index 87edafb97f..ec859e64a5 100644 --- a/osu.Game.props +++ b/osu.Game.props @@ -3,6 +3,9 @@ 7 + + ..\app.manifest + osu.licenseheader diff --git a/osu.Game/Beatmaps/Legacy/LegacyMods.cs b/osu.Game/Beatmaps/Legacy/LegacyMods.cs new file mode 100644 index 0000000000..0983610ba0 --- /dev/null +++ b/osu.Game/Beatmaps/Legacy/LegacyMods.cs @@ -0,0 +1,42 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; + +namespace osu.Game.Beatmaps.Legacy +{ + [Flags] + public enum LegacyMods + { + None = 0, + NoFail = 1 << 0, + Easy = 1 << 1, + TouchDevice = 1 << 2, + Hidden = 1 << 3, + HardRock = 1 << 4, + SuddenDeath = 1 << 5, + DoubleTime = 1 << 6, + Relax = 1 << 7, + HalfTime = 1 << 8, + Nightcore = 1 << 9, + Flashlight = 1 << 10, + Autoplay = 1 << 11, + SpunOut = 1 << 12, + Autopilot = 1 << 13, + Perfect = 1 << 14, + Key4 = 1 << 15, + Key5 = 1 << 16, + Key6 = 1 << 17, + Key7 = 1 << 18, + Key8 = 1 << 19, + FadeIn = 1 << 20, + Random = 1 << 21, + Cinema = 1 << 22, + Target = 1 << 23, + Key9 = 1 << 24, + KeyCoop = 1 << 25, + Key1 = 1 << 26, + Key3 = 1 << 27, + Key2 = 1 << 28, + } +} diff --git a/osu.Game/Beatmaps/RankStatus.cs b/osu.Game/Beatmaps/RankStatus.cs deleted file mode 100644 index dce4f494f1..0000000000 --- a/osu.Game/Beatmaps/RankStatus.cs +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System.ComponentModel; - -namespace osu.Game.Beatmaps -{ - public enum RankStatus - { - Any = 7, - [Description("Ranked & Approved")] - RankedApproved = 0, - Approved = 1, - Loved = 8, - Favourites = 2, - [Description("Mod Requests")] - ModRequests = 3, - Pending = 4, - Graveyard = 5, - [Description("My Maps")] - MyMaps = 6, - } -} diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index 1c4beb5f88..ebc674c900 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -84,6 +84,7 @@ namespace osu.Game.Configuration Set(OsuSetting.Version, string.Empty); Set(OsuSetting.ScreenshotFormat, ScreenshotFormat.Jpg); + Set(OsuSetting.ScreenshotCaptureMenuCursor, false); Set(OsuSetting.SelectScrollRightClick, false); } @@ -131,6 +132,7 @@ namespace osu.Game.Configuration SpeedChangeVisualisation, Skin, ScreenshotFormat, + ScreenshotCaptureMenuCursor, SelectScrollRightClick } } diff --git a/osu.Game/Graphics/Containers/WaveContainer.cs b/osu.Game/Graphics/Containers/WaveContainer.cs new file mode 100644 index 0000000000..82432c6ee7 --- /dev/null +++ b/osu.Game/Graphics/Containers/WaveContainer.cs @@ -0,0 +1,167 @@ +// 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.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using OpenTK.Graphics; + +namespace osu.Game.Graphics.Containers +{ + public class WaveContainer : VisibilityContainer + { + public const float APPEAR_DURATION = 800; + public const float DISAPPEAR_DURATION = 500; + + private const Easing easing_show = Easing.OutSine; + private const Easing easing_hide = Easing.InSine; + + private readonly Wave firstWave; + private readonly Wave secondWave; + private readonly Wave thirdWave; + private readonly Wave fourthWave; + + private readonly Container wavesContainer; + private readonly Container contentContainer; + + protected override Container Content => contentContainer; + + public Color4 FirstWaveColour + { + get => firstWave.Colour; + set => firstWave.Colour = value; + } + + public Color4 SecondWaveColour + { + get => secondWave.Colour; + set => secondWave.Colour = value; + } + + public Color4 ThirdWaveColour + { + get => thirdWave.Colour; + set => thirdWave.Colour = value; + } + + public Color4 FourthWaveColour + { + get => fourthWave.Colour; + set => fourthWave.Colour = value; + } + + public WaveContainer() + { + Masking = true; + + AddInternal(wavesContainer = new Container + { + RelativeSizeAxes = Axes.X, + Origin = Anchor.TopCentre, + Anchor = Anchor.TopCentre, + Masking = true, + Children = new[] + { + firstWave = new Wave + { + Rotation = 13, + FinalPosition = -930, + }, + secondWave = new Wave + { + Origin = Anchor.TopRight, + Anchor = Anchor.TopRight, + Rotation = -7, + FinalPosition = -560, + }, + thirdWave = new Wave + { + Rotation = 4, + FinalPosition = -390, + }, + fourthWave = new Wave + { + Origin = Anchor.TopRight, + Anchor = Anchor.TopRight, + Rotation = -2, + FinalPosition = -220, + }, + }, + }); + + AddInternal(contentContainer = new Container + { + RelativeSizeAxes = Axes.Both, + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + }); + } + + protected override void PopIn() + { + foreach (var w in wavesContainer.Children) + w.State = Visibility.Visible; + + this.FadeIn(100, Easing.OutQuint); + contentContainer.MoveToY(0, APPEAR_DURATION, Easing.OutQuint); + + this.FadeIn(100, Easing.OutQuint); + } + + protected override void PopOut() + { + this.FadeOut(DISAPPEAR_DURATION, Easing.InQuint); + contentContainer.MoveToY(DrawHeight * 2f, DISAPPEAR_DURATION, Easing.In); + + foreach (var w in wavesContainer.Children) + w.State = Visibility.Hidden; + + this.FadeOut(DISAPPEAR_DURATION, Easing.InQuint); + } + + protected override void UpdateAfterChildren() + { + base.UpdateAfterChildren(); + + // This is done as an optimization, such that invisible parts of the waves + // are masked away, and thus do not consume fill rate. + wavesContainer.Height = Math.Max(0, DrawHeight - (contentContainer.DrawHeight - contentContainer.Y)); + } + + private class Wave : VisibilityContainer + { + public float FinalPosition; + + protected override bool StartHidden => true; + + public Wave() + { + RelativeSizeAxes = Axes.X; + Width = 1.5f; + Masking = true; + EdgeEffect = new EdgeEffectParameters + { + Type = EdgeEffectType.Shadow, + Colour = Color4.Black.Opacity(50), + Radius = 20f, + }; + + Child = new Box { RelativeSizeAxes = Axes.Both }; + } + + protected override void Update() + { + base.Update(); + + // We can not use RelativeSizeAxes for Height, because the height + // of our parent diminishes as the content moves up. + Height = Parent.Parent.DrawSize.Y * 1.5f; + } + + protected override void PopIn() => this.MoveToY(FinalPosition, APPEAR_DURATION, easing_show); + protected override void PopOut() => this.MoveToY(Parent.Parent.DrawSize.Y, DISAPPEAR_DURATION, easing_hide); + } + } +} diff --git a/osu.Game/Graphics/Cursor/CursorOverrideContainer.cs b/osu.Game/Graphics/Cursor/CursorOverrideContainer.cs index 81ae3198c7..1e56cb6052 100644 --- a/osu.Game/Graphics/Cursor/CursorOverrideContainer.cs +++ b/osu.Game/Graphics/Cursor/CursorOverrideContainer.cs @@ -20,7 +20,7 @@ namespace osu.Game.Graphics.Cursor /// /// Whether any cursors can be displayed. /// - public bool CanShowCursor = true; + internal bool CanShowCursor = true; public CursorContainer Cursor { get; } public bool ProvidingUserCursor => true; diff --git a/osu.Game/Graphics/Cursor/MenuCursor.cs b/osu.Game/Graphics/Cursor/MenuCursor.cs index 34d815ec14..5f57fb76b0 100644 --- a/osu.Game/Graphics/Cursor/MenuCursor.cs +++ b/osu.Game/Graphics/Cursor/MenuCursor.cs @@ -12,12 +12,16 @@ using osu.Framework.Input; using osu.Game.Configuration; using System; using System.Diagnostics; +using JetBrains.Annotations; using osu.Framework.Graphics.Textures; namespace osu.Game.Graphics.Cursor { public class MenuCursor : CursorContainer { + private readonly IBindable screenshotCursorVisibility = new Bindable(true); + public override bool IsPresent => screenshotCursorVisibility.Value && base.IsPresent; + protected override Drawable CreateCursor() => new Cursor(); private Bindable cursorRotate; @@ -25,6 +29,15 @@ namespace osu.Game.Graphics.Cursor private bool startRotation; + [BackgroundDependencyLoader(true)] + private void load([NotNull] OsuConfigManager config, [CanBeNull] ScreenshotManager screenshotManager) + { + cursorRotate = config.GetBindable(OsuSetting.CursorRotation); + + if (screenshotManager != null) + screenshotCursorVisibility.BindTo(screenshotManager.CursorVisibility); + } + protected override bool OnMouseMove(InputState state) { if (cursorRotate && dragging) @@ -104,12 +117,6 @@ namespace osu.Game.Graphics.Cursor ActiveCursor.ScaleTo(0.6f, 250, Easing.In); } - [BackgroundDependencyLoader] - private void load(OsuConfigManager config) - { - cursorRotate = config.GetBindable(OsuSetting.CursorRotation); - } - public class Cursor : Container { private Container cursorContainer; diff --git a/osu.Game/Graphics/ScreenshotManager.cs b/osu.Game/Graphics/ScreenshotManager.cs index 5e0b9c9340..90580c50df 100644 --- a/osu.Game/Graphics/ScreenshotManager.cs +++ b/osu.Game/Graphics/ScreenshotManager.cs @@ -4,6 +4,8 @@ using System; using System.Drawing.Imaging; using System.IO; +using System.Threading; +using System.Threading.Tasks; using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Audio.Sample; @@ -12,6 +14,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Input; using osu.Framework.Input.Bindings; using osu.Framework.Platform; +using osu.Framework.Threading; using osu.Game.Configuration; using osu.Game.Input.Bindings; using osu.Game.Overlays; @@ -21,7 +24,17 @@ namespace osu.Game.Graphics { public class ScreenshotManager : Container, IKeyBindingHandler, IHandleGlobalInput { + private readonly BindableBool cursorVisibility = new BindableBool(true); + + /// + /// Changed when screenshots are being or have finished being taken, to control whether cursors should be visible. + /// If cursors should not be visible, cursors have 3 frames to hide themselves. + /// + public IBindable CursorVisibility => cursorVisibility; + private Bindable screenshotFormat; + private Bindable captureMenuCursor; + private GameHost host; private Storage storage; private NotificationOverlay notificationOverlay; @@ -36,6 +49,7 @@ namespace osu.Game.Graphics this.notificationOverlay = notificationOverlay; screenshotFormat = config.GetBindable(OsuSetting.ScreenshotFormat); + captureMenuCursor = config.GetBindable(OsuSetting.ScreenshotCaptureMenuCursor); shutter = audio.Sample.Get("UI/shutter"); } @@ -55,10 +69,31 @@ namespace osu.Game.Graphics public bool OnReleased(GlobalAction action) => false; - public async void TakeScreenshotAsync() + private volatile int screenShotTasks; + + public async Task TakeScreenshotAsync() => await Task.Run(async () => { + Interlocked.Increment(ref screenShotTasks); + + if (!captureMenuCursor.Value) + { + cursorVisibility.Value = false; + + // We need to wait for at most 3 draw nodes to be drawn, following which we can be assured at least one DrawNode has been generated/drawn with the set value + const int frames_to_wait = 3; + + int framesWaited = 0; + ScheduledDelegate waitDelegate = host.DrawThread.Scheduler.AddDelayed(() => framesWaited++, 0, true); + while (framesWaited < frames_to_wait) + Thread.Sleep(10); + + waitDelegate.Cancel(); + } + using (var bitmap = await host.TakeScreenshotAsync()) { + Interlocked.Decrement(ref screenShotTasks); + var fileName = getFileName(); if (fileName == null) return; @@ -86,6 +121,14 @@ namespace osu.Game.Graphics } }); } + }); + + protected override void Update() + { + base.Update(); + + if (cursorVisibility == false && Interlocked.CompareExchange(ref screenShotTasks, 0, 0) == 0) + cursorVisibility.Value = true; } private string getFileName() diff --git a/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs b/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs index e961dd9a9e..d68314f8fc 100644 --- a/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs +++ b/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs @@ -2,7 +2,7 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using System.Collections.Generic; -using osu.Game.Beatmaps; +using System.ComponentModel; using osu.Game.Overlays; using osu.Game.Overlays.Direct; using osu.Game.Rulesets; @@ -13,21 +13,36 @@ namespace osu.Game.Online.API.Requests { private readonly string query; private readonly RulesetInfo ruleset; - private readonly RankStatus rankStatus; + private readonly BeatmapSearchCategory searchCategory; private readonly DirectSortCriteria sortCriteria; private readonly SortDirection direction; private string directionString => direction == SortDirection.Descending ? @"desc" : @"asc"; - public SearchBeatmapSetsRequest(string query, RulesetInfo ruleset, RankStatus rankStatus = RankStatus.Any, DirectSortCriteria sortCriteria = DirectSortCriteria.Ranked, SortDirection direction = SortDirection.Descending) + public SearchBeatmapSetsRequest(string query, RulesetInfo ruleset, BeatmapSearchCategory searchCategory = BeatmapSearchCategory.Any, DirectSortCriteria sortCriteria = DirectSortCriteria.Ranked, SortDirection direction = SortDirection.Descending) { this.query = System.Uri.EscapeDataString(query); this.ruleset = ruleset; - this.rankStatus = rankStatus; + this.searchCategory = searchCategory; this.sortCriteria = sortCriteria; this.direction = direction; } // ReSharper disable once ImpureMethodCallOnReadonlyValueField - protected override string Target => $@"beatmapsets/search?q={query}&m={ruleset.ID ?? 0}&s={(int)rankStatus}&sort={sortCriteria.ToString().ToLower()}_{directionString}"; + protected override string Target => $@"beatmapsets/search?q={query}&m={ruleset.ID ?? 0}&s={(int)searchCategory}&sort={sortCriteria.ToString().ToLower()}_{directionString}"; + } + + public enum BeatmapSearchCategory + { + Any = 7, + [Description("Ranked & Approved")] + RankedApproved = 0, + Approved = 1, + Loved = 8, + Favourites = 2, + Qualified = 3, + Pending = 4, + Graveyard = 5, + [Description("My Maps")] + MyMaps = 6, } } diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index d1cf372067..f69135e5c9 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -60,6 +60,8 @@ namespace osu.Game private BeatmapSetOverlay beatmapSetOverlay; + private ScreenshotManager screenshotManager; + public virtual Storage GetStorageForStableInstall() => null; private Intro intro @@ -194,12 +196,16 @@ namespace osu.Game } Beatmap.Value = BeatmapManager.GetWorkingBeatmap(s.Beatmap); + Beatmap.Value.Mods.Value = s.Mods; menu.Push(new PlayerLoader(new ReplayPlayer(s.Replay))); } protected override void LoadComplete() { + // this needs to be cached before base.LoadComplete as it is used by CursorOverrideContainer. + dependencies.Cache(screenshotManager = new ScreenshotManager()); + base.LoadComplete(); // The next time this is updated is in UpdateAfterChildren, which occurs too late and results @@ -243,7 +249,8 @@ namespace osu.Game loadComponentSingleFile(volume = new VolumeOverlay(), overlayContent.Add); loadComponentSingleFile(onscreenDisplay = new OnScreenDisplay(), Add); - loadComponentSingleFile(new ScreenshotManager(), Add); + + loadComponentSingleFile(screenshotManager, Add); //overlay elements loadComponentSingleFile(direct = new DirectOverlay { Depth = -1 }, mainContent.Add); @@ -439,7 +446,7 @@ namespace osu.Game sensitivity.Value = 1; sensitivity.Disabled = true; - frameworkConfig.Set(FrameworkSetting.ActiveInputHandlers, string.Empty); + frameworkConfig.Set(FrameworkSetting.IgnoredInputHandlers, string.Empty); frameworkConfig.GetBindable(FrameworkSetting.ConfineMouseMode).SetDefault(); return true; case GlobalAction.ToggleToolbar: diff --git a/osu.Game/Overlays/BeatmapSetOverlay.cs b/osu.Game/Overlays/BeatmapSetOverlay.cs index 93e10751d9..d2cf0a6ef1 100644 --- a/osu.Game/Overlays/BeatmapSetOverlay.cs +++ b/osu.Game/Overlays/BeatmapSetOverlay.cs @@ -40,10 +40,10 @@ namespace osu.Game.Overlays public BeatmapSetOverlay() { - FirstWaveColour = OsuColour.Gray(0.4f); - SecondWaveColour = OsuColour.Gray(0.3f); - ThirdWaveColour = OsuColour.Gray(0.2f); - FourthWaveColour = OsuColour.Gray(0.1f); + Waves.FirstWaveColour = OsuColour.Gray(0.4f); + Waves.SecondWaveColour = OsuColour.Gray(0.3f); + Waves.ThirdWaveColour = OsuColour.Gray(0.2f); + Waves.FourthWaveColour = OsuColour.Gray(0.1f); Anchor = Anchor.TopCentre; Origin = Anchor.TopCentre; @@ -123,14 +123,14 @@ namespace osu.Game.Overlays protected override void PopIn() { base.PopIn(); - FadeEdgeEffectTo(0.25f, APPEAR_DURATION, Easing.In); + FadeEdgeEffectTo(0.25f, WaveContainer.APPEAR_DURATION, Easing.In); } protected override void PopOut() { base.PopOut(); header.Details.StopPreview(); - FadeEdgeEffectTo(0, DISAPPEAR_DURATION, Easing.Out); + FadeEdgeEffectTo(0, WaveContainer.DISAPPEAR_DURATION, Easing.Out); } protected override bool OnClick(InputState state) diff --git a/osu.Game/Overlays/Direct/FilterControl.cs b/osu.Game/Overlays/Direct/FilterControl.cs index c77994efb2..8883dfdebb 100644 --- a/osu.Game/Overlays/Direct/FilterControl.cs +++ b/osu.Game/Overlays/Direct/FilterControl.cs @@ -7,15 +7,15 @@ using osu.Framework.Allocation; using osu.Framework.Configuration; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Graphics.Containers; +using osu.Game.Online.API.Requests; using osu.Game.Overlays.SearchableList; using osu.Game.Rulesets; namespace osu.Game.Overlays.Direct { - public class FilterControl : SearchableListFilterControl + public class FilterControl : SearchableListFilterControl { public readonly Bindable Ruleset = new Bindable(); private FillFlowContainer modeButtons; diff --git a/osu.Game/Overlays/Direct/PlayButton.cs b/osu.Game/Overlays/Direct/PlayButton.cs index 9f36d5acb7..44e24d8157 100644 --- a/osu.Game/Overlays/Direct/PlayButton.cs +++ b/osu.Game/Overlays/Direct/PlayButton.cs @@ -173,8 +173,9 @@ namespace osu.Game.Overlays.Direct if (trackLoader != d) return; Preview = d?.Preview; - Playing.TriggerChange(); + updatePreviewTrack(Playing); loading = false; + Add(trackLoader); }); } diff --git a/osu.Game/Overlays/DirectOverlay.cs b/osu.Game/Overlays/DirectOverlay.cs index 6c9433836a..f437546888 100644 --- a/osu.Game/Overlays/DirectOverlay.cs +++ b/osu.Game/Overlays/DirectOverlay.cs @@ -22,7 +22,7 @@ using OpenTK.Graphics; namespace osu.Game.Overlays { - public class DirectOverlay : SearchableListOverlay + public class DirectOverlay : SearchableListOverlay { private const float panel_padding = 10f; @@ -40,7 +40,7 @@ namespace osu.Game.Overlays protected override Color4 TrianglesColourDark => OsuColour.FromHex(@"3f5265"); protected override SearchableListHeader CreateHeader() => new Header(); - protected override SearchableListFilterControl CreateFilterControl() => new FilterControl(); + protected override SearchableListFilterControl CreateFilterControl() => new FilterControl(); private IEnumerable beatmapSets; @@ -89,10 +89,10 @@ namespace osu.Game.Overlays // osu!direct colours are not part of the standard palette - FirstWaveColour = OsuColour.FromHex(@"19b0e2"); - SecondWaveColour = OsuColour.FromHex(@"2280a2"); - ThirdWaveColour = OsuColour.FromHex(@"005774"); - FourthWaveColour = OsuColour.FromHex(@"003a4e"); + Waves.FirstWaveColour = OsuColour.FromHex(@"19b0e2"); + Waves.SecondWaveColour = OsuColour.FromHex(@"2280a2"); + Waves.ThirdWaveColour = OsuColour.FromHex(@"005774"); + Waves.FourthWaveColour = OsuColour.FromHex(@"003a4e"); ScrollFlow.Children = new Drawable[] { diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index 48c38c467e..632c00d1fd 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -18,6 +18,7 @@ using System.Linq; using osu.Framework.Audio; using osu.Framework.Audio.Sample; using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics.Containers; using osu.Game.Rulesets; using osu.Game.Graphics.UserInterface; @@ -113,14 +114,14 @@ namespace osu.Game.Overlays.Mods { base.PopOut(); - footerContainer.MoveToX(footerContainer.DrawSize.X, DISAPPEAR_DURATION, Easing.InSine); - footerContainer.FadeOut(DISAPPEAR_DURATION, Easing.InSine); + footerContainer.MoveToX(footerContainer.DrawSize.X, WaveContainer.DISAPPEAR_DURATION, Easing.InSine); + footerContainer.FadeOut(WaveContainer.DISAPPEAR_DURATION, Easing.InSine); foreach (ModSection section in ModSectionsContainer.Children) { - section.ButtonsContainer.TransformSpacingTo(new Vector2(100f, 0f), DISAPPEAR_DURATION, Easing.InSine); - section.ButtonsContainer.MoveToX(100f, DISAPPEAR_DURATION, Easing.InSine); - section.ButtonsContainer.FadeOut(DISAPPEAR_DURATION, Easing.InSine); + section.ButtonsContainer.TransformSpacingTo(new Vector2(100f, 0f), WaveContainer.DISAPPEAR_DURATION, Easing.InSine); + section.ButtonsContainer.MoveToX(100f, WaveContainer.DISAPPEAR_DURATION, Easing.InSine); + section.ButtonsContainer.FadeOut(WaveContainer.DISAPPEAR_DURATION, Easing.InSine); } } @@ -128,14 +129,14 @@ namespace osu.Game.Overlays.Mods { base.PopIn(); - footerContainer.MoveToX(0, APPEAR_DURATION, Easing.OutQuint); - footerContainer.FadeIn(APPEAR_DURATION, Easing.OutQuint); + footerContainer.MoveToX(0, WaveContainer.APPEAR_DURATION, Easing.OutQuint); + footerContainer.FadeIn(WaveContainer.APPEAR_DURATION, Easing.OutQuint); foreach (ModSection section in ModSectionsContainer.Children) { - section.ButtonsContainer.TransformSpacingTo(new Vector2(50f, 0f), APPEAR_DURATION, Easing.OutQuint); - section.ButtonsContainer.MoveToX(0, APPEAR_DURATION, Easing.OutQuint); - section.ButtonsContainer.FadeIn(APPEAR_DURATION, Easing.OutQuint); + section.ButtonsContainer.TransformSpacingTo(new Vector2(50f, 0f), WaveContainer.APPEAR_DURATION, Easing.OutQuint); + section.ButtonsContainer.MoveToX(0, WaveContainer.APPEAR_DURATION, Easing.OutQuint); + section.ButtonsContainer.FadeIn(WaveContainer.APPEAR_DURATION, Easing.OutQuint); } } @@ -181,14 +182,12 @@ namespace osu.Game.Overlays.Mods public ModSelectOverlay() { - FirstWaveColour = OsuColour.FromHex(@"19b0e2"); - SecondWaveColour = OsuColour.FromHex(@"2280a2"); - ThirdWaveColour = OsuColour.FromHex(@"005774"); - FourthWaveColour = OsuColour.FromHex(@"003a4e"); + Waves.FirstWaveColour = OsuColour.FromHex(@"19b0e2"); + Waves.SecondWaveColour = OsuColour.FromHex(@"2280a2"); + Waves.ThirdWaveColour = OsuColour.FromHex(@"005774"); + Waves.FourthWaveColour = OsuColour.FromHex(@"003a4e"); Height = 510; - Content.RelativeSizeAxes = Axes.X; - Content.AutoSizeAxes = Axes.Y; Children = new Drawable[] { new Container diff --git a/osu.Game/Overlays/Profile/Header/BadgeContainer.cs b/osu.Game/Overlays/Profile/Header/BadgeContainer.cs new file mode 100644 index 0000000000..291db45e97 --- /dev/null +++ b/osu.Game/Overlays/Profile/Header/BadgeContainer.cs @@ -0,0 +1,195 @@ +// 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.Cursor; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Graphics.Textures; +using osu.Framework.Input; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Users; +using OpenTK; + +namespace osu.Game.Overlays.Profile.Header +{ + public class BadgeContainer : Container + { + private static readonly Vector2 badge_size = new Vector2(86, 40); + private static readonly MarginPadding outer_padding = new MarginPadding(3); + + private OsuSpriteText badgeCountText; + private FillFlowContainer badgeFlowContainer; + private FillFlowContainer outerBadgeContainer; + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + Child = new Container + { + Masking = true, + CornerRadius = 4, + AutoSizeAxes = Axes.Both, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = colours.Gray3 + }, + outerBadgeContainer = new OuterBadgeContainer(onOuterHover, onOuterHoverLost) + { + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + Direction = FillDirection.Vertical, + Padding = outer_padding, + Width = DrawableBadge.DRAWABLE_BADGE_SIZE.X + outer_padding.TotalHorizontal, + AutoSizeAxes = Axes.Y, + Children = new Drawable[] + { + badgeCountText = new OsuSpriteText + { + Alpha = 0, + TextSize = 12, + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + Font = "Exo2.0-Regular" + }, + new Container + { + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + AutoSizeAxes = Axes.Both, + Child = badgeFlowContainer = new FillFlowContainer + { + Direction = FillDirection.Horizontal, + AutoSizeAxes = Axes.Both, + } + } + } + }, + } + }; + + Scheduler.AddDelayed(rotateBadges, 3000, true); + } + + private void rotateBadges() + { + if (outerBadgeContainer.IsHovered) return; + + visibleBadge = (visibleBadge + 1) % badgeCount; + + badgeFlowContainer.MoveToX(-DrawableBadge.DRAWABLE_BADGE_SIZE.X * visibleBadge, 500, Easing.InOutQuad); + } + + private int visibleBadge; + private int badgeCount; + + public void ShowBadges(Badge[] badges) + { + switch (badges.Length) + { + case 0: + Hide(); + return; + case 1: + badgeCountText.Hide(); + break; + default: + badgeCountText.Show(); + badgeCountText.Text = $"{badges.Length} badges"; + break; + } + + Show(); + badgeCount = badges.Length; + visibleBadge = 0; + + badgeFlowContainer.Clear(); + foreach (var badge in badges) + { + LoadComponentAsync(new DrawableBadge(badge) + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + }, badgeFlowContainer.Add); + } + } + + private void onOuterHover() + { + badgeFlowContainer.ClearTransforms(); + badgeFlowContainer.X = 0; + badgeFlowContainer.Direction = FillDirection.Full; + outerBadgeContainer.AutoSizeAxes = Axes.Both; + + badgeFlowContainer.MaximumSize = new Vector2(ChildSize.X, float.MaxValue); + } + + private void onOuterHoverLost() + { + badgeFlowContainer.X = -DrawableBadge.DRAWABLE_BADGE_SIZE.X * visibleBadge; + badgeFlowContainer.Direction = FillDirection.Horizontal; + outerBadgeContainer.AutoSizeAxes = Axes.Y; + outerBadgeContainer.Width = DrawableBadge.DRAWABLE_BADGE_SIZE.X + outer_padding.TotalHorizontal; + } + + private class OuterBadgeContainer : FillFlowContainer + { + private readonly Action hoverAction; + private readonly Action hoverLostAction; + + public OuterBadgeContainer(Action hoverAction, Action hoverLostAction) + { + this.hoverAction = hoverAction; + this.hoverLostAction = hoverLostAction; + } + + protected override bool OnHover(InputState state) + { + hoverAction(); + return true; + } + + protected override void OnHoverLost(InputState state) => hoverLostAction(); + } + + private class DrawableBadge : Container, IHasTooltip + { + public static readonly Vector2 DRAWABLE_BADGE_SIZE = badge_size + outer_padding.Total; + + private readonly Badge badge; + + public DrawableBadge(Badge badge) + { + this.badge = badge; + Padding = outer_padding; + Size = DRAWABLE_BADGE_SIZE; + } + + [BackgroundDependencyLoader] + private void load(TextureStore textures) + { + Child = new Sprite + { + FillMode = FillMode.Fit, + RelativeSizeAxes = Axes.Both, + Texture = textures.Get(badge.ImageUrl), + }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + Child.FadeInFromZero(200); + } + + public string TooltipText => badge.Description; + } + } +} diff --git a/osu.Game/Overlays/Profile/RankGraph.cs b/osu.Game/Overlays/Profile/Header/RankGraph.cs similarity index 99% rename from osu.Game/Overlays/Profile/RankGraph.cs rename to osu.Game/Overlays/Profile/Header/RankGraph.cs index 72dd4352f6..2c70507536 100644 --- a/osu.Game/Overlays/Profile/RankGraph.cs +++ b/osu.Game/Overlays/Profile/Header/RankGraph.cs @@ -2,9 +2,10 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using System; +using System.Collections.Generic; using System.Linq; -using OpenTK; using osu.Framework.Allocation; +using osu.Framework.Configuration; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; @@ -14,10 +15,9 @@ using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; using osu.Game.Users; -using System.Collections.Generic; -using osu.Framework.Configuration; +using OpenTK; -namespace osu.Game.Overlays.Profile +namespace osu.Game.Overlays.Profile.Header { public class RankGraph : Container { diff --git a/osu.Game/Overlays/Profile/SupporterIcon.cs b/osu.Game/Overlays/Profile/Header/SupporterIcon.cs similarity index 98% rename from osu.Game/Overlays/Profile/SupporterIcon.cs rename to osu.Game/Overlays/Profile/Header/SupporterIcon.cs index e8d52bf50e..37ad63464c 100644 --- a/osu.Game/Overlays/Profile/SupporterIcon.cs +++ b/osu.Game/Overlays/Profile/Header/SupporterIcon.cs @@ -1,7 +1,6 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using OpenTK; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -9,8 +8,9 @@ using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Shapes; using osu.Game.Graphics; using osu.Game.Graphics.Backgrounds; +using OpenTK; -namespace osu.Game.Overlays.Profile +namespace osu.Game.Overlays.Profile.Header { public class SupporterIcon : CircularContainer, IHasTooltip { diff --git a/osu.Game/Overlays/Profile/ProfileHeader.cs b/osu.Game/Overlays/Profile/ProfileHeader.cs index 4a88431cc4..ec0e45d5ca 100644 --- a/osu.Game/Overlays/Profile/ProfileHeader.cs +++ b/osu.Game/Overlays/Profile/ProfileHeader.cs @@ -17,6 +17,7 @@ using osu.Framework.Graphics.Textures; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; +using osu.Game.Overlays.Profile.Header; using osu.Game.Users; namespace osu.Game.Overlays.Profile @@ -35,6 +36,7 @@ namespace osu.Game.Overlays.Profile private readonly GradeBadge gradeSSPlus, gradeSS, gradeSPlus, gradeS, gradeA; private readonly Box colourBar; private readonly DrawableFlag countryFlag; + private readonly BadgeContainer badgeContainer; private const float cover_height = 350; private const float info_height = 150; @@ -42,6 +44,7 @@ namespace osu.Game.Overlays.Profile private const float avatar_size = 110; private const float level_position = 30; private const float level_height = 60; + private const float stats_width = 280; public ProfileHeader(User user) { @@ -66,9 +69,9 @@ namespace osu.Game.Overlays.Profile { Anchor = Anchor.BottomLeft, Origin = Anchor.BottomLeft, - X = UserProfileOverlay.CONTENT_X_MARGIN, - Y = -20, - AutoSizeAxes = Axes.Both, + Padding = new MarginPadding { Left = UserProfileOverlay.CONTENT_X_MARGIN, Bottom = 20, Right = stats_width + UserProfileOverlay.CONTENT_X_MARGIN }, + AutoSizeAxes = Axes.Y, + RelativeSizeAxes = Axes.X, Children = new Drawable[] { new UpdateableAvatar @@ -116,7 +119,15 @@ namespace osu.Game.Overlays.Profile Height = 20 } } - } + }, + badgeContainer = new BadgeContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Origin = Anchor.BottomLeft, + Margin = new MarginPadding { Bottom = 5 }, + Alpha = 0, + }, } }, colourBar = new Box @@ -156,7 +167,7 @@ namespace osu.Game.Overlays.Profile { X = -UserProfileOverlay.CONTENT_X_MARGIN, RelativeSizeAxes = Axes.Y, - Width = 280, + Width = stats_width, Anchor = Anchor.TopRight, Origin = Anchor.TopRight, Children = new Drawable[] @@ -417,6 +428,8 @@ namespace osu.Game.Overlays.Profile rankGraph.User.Value = user; } + + badgeContainer.ShowBadges(user.Badges); } private void tryAddInfoRightLine(FontAwesome icon, string str, string url = null) diff --git a/osu.Game/Overlays/Profile/Sections/Beatmaps/PaginatedBeatmapContainer.cs b/osu.Game/Overlays/Profile/Sections/Beatmaps/PaginatedBeatmapContainer.cs index 842a13f8da..3fec9d8697 100644 --- a/osu.Game/Overlays/Profile/Sections/Beatmaps/PaginatedBeatmapContainer.cs +++ b/osu.Game/Overlays/Profile/Sections/Beatmaps/PaginatedBeatmapContainer.cs @@ -1,6 +1,7 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +using System; using OpenTK; using osu.Framework.Configuration; using osu.Framework.Graphics; @@ -19,6 +20,8 @@ namespace osu.Game.Overlays.Profile.Sections.Beatmaps private DirectPanel currentlyPlaying; + public event Action BeganPlayingPreview; + public PaginatedBeatmapContainer(BeatmapSetType type, Bindable user, string header, string missing = "None... yet.") : base(user, header, missing) { @@ -56,17 +59,25 @@ namespace osu.Game.Overlays.Profile.Sections.Beatmaps panel.PreviewPlaying.ValueChanged += isPlaying => { - if (!isPlaying) return; + StopPlayingPreview(); - if (currentlyPlaying != null && currentlyPlaying != panel) - currentlyPlaying.PreviewPlaying.Value = false; - - currentlyPlaying = panel; + if (isPlaying) + { + BeganPlayingPreview?.Invoke(this); + currentlyPlaying = panel; + } }; } }; Api.Queue(req); } + + public void StopPlayingPreview() + { + if (currentlyPlaying == null) return; + currentlyPlaying.PreviewPlaying.Value = false; + currentlyPlaying = null; + } } } diff --git a/osu.Game/Overlays/Profile/Sections/BeatmapsSection.cs b/osu.Game/Overlays/Profile/Sections/BeatmapsSection.cs index 367d096c16..92abd20f93 100644 --- a/osu.Game/Overlays/Profile/Sections/BeatmapsSection.cs +++ b/osu.Game/Overlays/Profile/Sections/BeatmapsSection.cs @@ -1,6 +1,7 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +using System.Linq; using osu.Game.Online.API.Requests; using osu.Game.Overlays.Profile.Sections.Beatmaps; @@ -21,6 +22,15 @@ namespace osu.Game.Overlays.Profile.Sections new PaginatedBeatmapContainer(BeatmapSetType.Unranked, User, "Pending Beatmaps"), new PaginatedBeatmapContainer(BeatmapSetType.Graveyard, User, "Graveyarded Beatmaps"), }; + + foreach (var paginatedBeatmapContainer in Children.OfType()) + { + paginatedBeatmapContainer.BeganPlayingPreview += _ => + { + foreach (var bc in Children.OfType()) + bc.StopPlayingPreview(); + }; + } } } } diff --git a/osu.Game/Overlays/Settings/Sections/Graphics/DetailSettings.cs b/osu.Game/Overlays/Settings/Sections/Graphics/DetailSettings.cs index a78cb29468..54049bfb1f 100644 --- a/osu.Game/Overlays/Settings/Sections/Graphics/DetailSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Graphics/DetailSettings.cs @@ -30,6 +30,11 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics { LabelText = "Screenshot format", Bindable = config.GetBindable(OsuSetting.ScreenshotFormat) + }, + new SettingsCheckbox + { + LabelText = "Show menu cursor in screenshots", + Bindable = config.GetBindable(OsuSetting.ScreenshotCaptureMenuCursor) } }; } diff --git a/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs b/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs index c4fced922f..51a624330b 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs @@ -15,7 +15,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input protected override string Header => "Mouse"; private readonly BindableBool rawInputToggle = new BindableBool(); - private Bindable activeInputHandlers; + private Bindable ignoredInputHandler; private SensitivitySetting sensitivity; [BackgroundDependencyLoader] @@ -61,20 +61,18 @@ namespace osu.Game.Overlays.Settings.Sections.Input const string raw_mouse_handler = @"OpenTKRawMouseHandler"; const string standard_mouse_handler = @"OpenTKMouseHandler"; - activeInputHandlers.Value = enabled ? - activeInputHandlers.Value.Replace(standard_mouse_handler, raw_mouse_handler) : - activeInputHandlers.Value.Replace(raw_mouse_handler, standard_mouse_handler); + ignoredInputHandler.Value = enabled ? standard_mouse_handler : raw_mouse_handler; }; - activeInputHandlers = config.GetBindable(FrameworkSetting.ActiveInputHandlers); - activeInputHandlers.ValueChanged += handlers => + ignoredInputHandler = config.GetBindable(FrameworkSetting.IgnoredInputHandlers); + ignoredInputHandler.ValueChanged += handler => { - bool raw = handlers.Contains("Raw"); + bool raw = !handler.Contains("Raw"); rawInputToggle.Value = raw; sensitivity.Bindable.Disabled = !raw; }; - activeInputHandlers.TriggerChange(); + ignoredInputHandler.TriggerChange(); } private class SensitivitySetting : SettingsSlider diff --git a/osu.Game/Overlays/SocialOverlay.cs b/osu.Game/Overlays/SocialOverlay.cs index d224d4c92d..222035ab65 100644 --- a/osu.Game/Overlays/SocialOverlay.cs +++ b/osu.Game/Overlays/SocialOverlay.cs @@ -48,10 +48,10 @@ namespace osu.Game.Overlays public SocialOverlay() { - FirstWaveColour = OsuColour.FromHex(@"cb5fa0"); - SecondWaveColour = OsuColour.FromHex(@"b04384"); - ThirdWaveColour = OsuColour.FromHex(@"9b2b6e"); - FourthWaveColour = OsuColour.FromHex(@"6d214d"); + Waves.FirstWaveColour = OsuColour.FromHex(@"cb5fa0"); + Waves.SecondWaveColour = OsuColour.FromHex(@"b04384"); + Waves.ThirdWaveColour = OsuColour.FromHex(@"9b2b6e"); + Waves.FourthWaveColour = OsuColour.FromHex(@"6d214d"); Add(loading = new LoadingAnimation()); diff --git a/osu.Game/Overlays/UserProfileOverlay.cs b/osu.Game/Overlays/UserProfileOverlay.cs index c6e8c60f92..a4dd0c9ec3 100644 --- a/osu.Game/Overlays/UserProfileOverlay.cs +++ b/osu.Game/Overlays/UserProfileOverlay.cs @@ -35,10 +35,10 @@ namespace osu.Game.Overlays public UserProfileOverlay() { - FirstWaveColour = OsuColour.Gray(0.4f); - SecondWaveColour = OsuColour.Gray(0.3f); - ThirdWaveColour = OsuColour.Gray(0.2f); - FourthWaveColour = OsuColour.Gray(0.1f); + Waves.FirstWaveColour = OsuColour.Gray(0.4f); + Waves.SecondWaveColour = OsuColour.Gray(0.3f); + Waves.ThirdWaveColour = OsuColour.Gray(0.2f); + Waves.FourthWaveColour = OsuColour.Gray(0.1f); RelativeSizeAxes = Axes.Both; RelativePositionAxes = Axes.Both; @@ -64,13 +64,13 @@ namespace osu.Game.Overlays protected override void PopIn() { base.PopIn(); - FadeEdgeEffectTo(0.5f, APPEAR_DURATION, Easing.In); + FadeEdgeEffectTo(0.5f, WaveContainer.APPEAR_DURATION, Easing.In); } protected override void PopOut() { base.PopOut(); - FadeEdgeEffectTo(0, DISAPPEAR_DURATION, Easing.Out); + FadeEdgeEffectTo(0, WaveContainer.DISAPPEAR_DURATION, Easing.Out); } public void ShowUser(long userId) diff --git a/osu.Game/Overlays/WaveOverlayContainer.cs b/osu.Game/Overlays/WaveOverlayContainer.cs index 6a083a77e5..97f52d88f7 100644 --- a/osu.Game/Overlays/WaveOverlayContainer.cs +++ b/osu.Game/Overlays/WaveOverlayContainer.cs @@ -1,203 +1,37 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using OpenTK.Graphics; -using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics; -using System; -using osu.Framework.Graphics.Shapes; using osu.Game.Graphics.Containers; namespace osu.Game.Overlays { public abstract class WaveOverlayContainer : OsuFocusedOverlayContainer { - protected const float APPEAR_DURATION = 800; - protected const float DISAPPEAR_DURATION = 500; - - private const Easing easing_show = Easing.OutSine; - private const Easing easing_hide = Easing.InSine; - - private readonly Wave firstWave; - private readonly Wave secondWave; - private readonly Wave thirdWave; - private readonly Wave fourthWave; - - private readonly Container wavesContainer; - - private readonly Container contentContainer; + protected readonly WaveContainer Waves; protected override bool BlockPassThroughKeyboard => true; - - protected override Container Content => contentContainer; - - protected Color4 FirstWaveColour - { - get - { - return firstWave.Colour; - } - set - { - if (firstWave.Colour == value) return; - firstWave.Colour = value; - } - } - - protected Color4 SecondWaveColour - { - get - { - return secondWave.Colour; - } - set - { - if (secondWave.Colour == value) return; - secondWave.Colour = value; - } - } - - protected Color4 ThirdWaveColour - { - get - { - return thirdWave.Colour; - } - set - { - if (thirdWave.Colour == value) return; - thirdWave.Colour = value; - } - } - - protected Color4 FourthWaveColour - { - get - { - return fourthWave.Colour; - } - set - { - if (fourthWave.Colour == value) return; - fourthWave.Colour = value; - } - } + protected override Container Content => Waves; protected WaveOverlayContainer() { - Masking = true; - - AddInternal(wavesContainer = new Container - { - RelativeSizeAxes = Axes.X, - Origin = Anchor.TopCentre, - Anchor = Anchor.TopCentre, - Masking = true, - Children = new[] - { - firstWave = new Wave - { - Rotation = 13, - FinalPosition = -930, - }, - secondWave = new Wave - { - Origin = Anchor.TopRight, - Anchor = Anchor.TopRight, - Rotation = -7, - FinalPosition = -560, - }, - thirdWave = new Wave - { - Rotation = 4, - FinalPosition = -390, - }, - fourthWave = new Wave - { - Origin = Anchor.TopRight, - Anchor = Anchor.TopRight, - Rotation = -2, - FinalPosition = -220, - }, - }, - }); - - AddInternal(contentContainer = new Container + AddInternal(Waves = new WaveContainer { RelativeSizeAxes = Axes.Both, - Anchor = Anchor.BottomCentre, - Origin = Anchor.BottomCentre, }); } protected override void PopIn() { base.PopIn(); - - foreach (var w in wavesContainer.Children) - w.State = Visibility.Visible; - - this.FadeIn(100, Easing.OutQuint); - contentContainer.MoveToY(0, APPEAR_DURATION, Easing.OutQuint); - - this.FadeIn(100, Easing.OutQuint); + Waves.Show(); } protected override void PopOut() { base.PopOut(); - - this.FadeOut(DISAPPEAR_DURATION, Easing.InQuint); - contentContainer.MoveToY(DrawHeight * 2f, DISAPPEAR_DURATION, Easing.In); - - foreach (var w in wavesContainer.Children) - w.State = Visibility.Hidden; - - this.FadeOut(DISAPPEAR_DURATION, Easing.InQuint); - } - - protected override void UpdateAfterChildren() - { - base.UpdateAfterChildren(); - - // This is done as an optimization, such that invisible parts of the waves - // are masked away, and thus do not consume fill rate. - wavesContainer.Height = Math.Max(0, DrawHeight - (contentContainer.DrawHeight - contentContainer.Y)); - } - - private class Wave : VisibilityContainer - { - public float FinalPosition; - - protected override bool StartHidden => true; - - public Wave() - { - RelativeSizeAxes = Axes.X; - Width = 1.5f; - Masking = true; - EdgeEffect = new EdgeEffectParameters - { - Type = EdgeEffectType.Shadow, - Colour = Color4.Black.Opacity(50), - Radius = 20f, - }; - - Child = new Box { RelativeSizeAxes = Axes.Both }; - } - - protected override void Update() - { - base.Update(); - - // We can not use RelativeSizeAxes for Height, because the height - // of our parent diminishes as the content moves up. - Height = Parent.Parent.DrawSize.Y * 1.5f; - } - - protected override void PopIn() => this.MoveToY(FinalPosition, APPEAR_DURATION, easing_show); - protected override void PopOut() => this.MoveToY(Parent.Parent.DrawSize.Y, DISAPPEAR_DURATION, easing_hide); + Waves.Hide(); } } } diff --git a/osu.Game/Rulesets/Ruleset.cs b/osu.Game/Rulesets/Ruleset.cs index c2af4d566c..cd1d030afe 100644 --- a/osu.Game/Rulesets/Ruleset.cs +++ b/osu.Game/Rulesets/Ruleset.cs @@ -14,6 +14,7 @@ using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Replays.Types; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; +using osu.Game.Beatmaps.Legacy; namespace osu.Game.Rulesets { @@ -33,6 +34,13 @@ namespace osu.Game.Rulesets public abstract IEnumerable GetModsFor(ModType type); + /// + /// Converts mods from legacy enum values. Do not override if you're not a legacy ruleset. + /// + /// The legacy enum which will be converted + /// An enumerable of constructed s + public virtual IEnumerable ConvertLegacyMods(LegacyMods mods) => new Mod[] { }; + public Mod GetAutoplayMod() => GetAllMods().First(mod => mod is ModAutoplay); protected Ruleset(RulesetInfo rulesetInfo = null) diff --git a/osu.Game/Rulesets/Scoring/Legacy/LegacyScoreParser.cs b/osu.Game/Rulesets/Scoring/Legacy/LegacyScoreParser.cs index 5ee009ba98..239f200e29 100644 --- a/osu.Game/Rulesets/Scoring/Legacy/LegacyScoreParser.cs +++ b/osu.Game/Rulesets/Scoring/Legacy/LegacyScoreParser.cs @@ -9,6 +9,8 @@ using osu.Game.Rulesets.Replays; using osu.Game.Rulesets.Replays.Legacy; using osu.Game.Users; using SharpCompress.Compressors.LZMA; +using osu.Game.Beatmaps.Legacy; +using System.Linq; namespace osu.Game.Rulesets.Scoring.Legacy { @@ -64,7 +66,7 @@ namespace osu.Game.Rulesets.Scoring.Legacy /* score.Perfect = */ sr.ReadBoolean(); /* score.EnabledMods = (Mods)*/ - sr.ReadInt32(); + score.Mods = currentRuleset.ConvertLegacyMods((LegacyMods)sr.ReadInt32()).ToArray(); /* score.HpGraphString = */ sr.ReadString(); /* score.Date = */ diff --git a/osu.Game/Screens/Menu/Button.cs b/osu.Game/Screens/Menu/Button.cs index 33e423a558..542ddd2c92 100644 --- a/osu.Game/Screens/Menu/Button.cs +++ b/osu.Game/Screens/Menu/Button.cs @@ -222,7 +222,7 @@ namespace osu.Game.Screens.Menu boxHoverLayer.FadeOut(800, Easing.OutExpo); } - public override bool HandleKeyboardInput => state != ButtonState.Exploded; + public override bool HandleKeyboardInput => state == ButtonState.Expanded; public override bool HandleMouseInput => state != ButtonState.Exploded && box.Scale.X >= 0.8f; protected override void Update() diff --git a/osu.Game/Screens/Select/Leaderboards/Leaderboard.cs b/osu.Game/Screens/Select/Leaderboards/Leaderboard.cs index 3a6ab8f84b..9dae8fb273 100644 --- a/osu.Game/Screens/Select/Leaderboards/Leaderboard.cs +++ b/osu.Game/Screens/Select/Leaderboards/Leaderboard.cs @@ -39,8 +39,9 @@ namespace osu.Game.Screens.Select.Leaderboards private readonly LoadingAnimation loading; - private IEnumerable scores; + private ScheduledDelegate showScoresDelegate; + private IEnumerable scores; public IEnumerable Scores { get { return scores; } @@ -59,29 +60,34 @@ namespace osu.Game.Screens.Select.Leaderboards // ensure placeholder is hidden when displaying scores PlaceholderState = PlaceholderState.Successful; - // schedule because we may not be loaded yet (LoadComponentAsync complains). - Schedule(() => + var flow = scrollFlow = new FillFlowContainer { - LoadComponentAsync(new FillFlowContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Spacing = new Vector2(0f, 5f), - Padding = new MarginPadding { Top = 10, Bottom = 5 }, - ChildrenEnumerable = scores.Select((s, index) => new LeaderboardScore(s, index + 1) { Action = () => ScoreSelected?.Invoke(s) }) - }, f => - { - scrollContainer.Add(scrollFlow = f); + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Spacing = new Vector2(0f, 5f), + Padding = new MarginPadding { Top = 10, Bottom = 5 }, + ChildrenEnumerable = scores.Select((s, index) => new LeaderboardScore(s, index + 1) { Action = () => ScoreSelected?.Invoke(s) }) + }; - int i = 0; - foreach (var s in f.Children) - { - using (s.BeginDelayedSequence(i++ * 50, true)) - s.Show(); - } + // schedule because we may not be loaded yet (LoadComponentAsync complains). + showScoresDelegate?.Cancel(); + if (!IsLoaded) + showScoresDelegate = Schedule(showScores); + else + showScores(); - scrollContainer.ScrollTo(0f, false); - }); + void showScores() => LoadComponentAsync(flow, _ => + { + scrollContainer.Add(flow); + + int i = 0; + foreach (var s in flow.Children) + { + using (s.BeginDelayedSequence(i++ * 50, true)) + s.Show(); + } + + scrollContainer.ScrollTo(0f, false); }); } } @@ -103,6 +109,10 @@ namespace osu.Game.Screens.Select.Leaderboards private PlaceholderState placeholderState; + /// + /// Update the placeholder visibility. + /// Setting this to anything other than PlaceholderState.Successful will cancel all existing retrieval requests and hide scores. + /// protected PlaceholderState PlaceholderState { get { return placeholderState; } @@ -250,43 +260,45 @@ namespace osu.Game.Screens.Select.Leaderboards loading.Show(); getScoresRequest = new GetScoresRequest(Beatmap, osuGame?.Ruleset.Value ?? Beatmap.Ruleset, Scope); - getScoresRequest.Success += r => + getScoresRequest.Success += r => Schedule(() => { Scores = r.Scores; PlaceholderState = Scores.Any() ? PlaceholderState.Successful : PlaceholderState.NoScores; - }; - getScoresRequest.Failure += onUpdateFailed; + }); + + getScoresRequest.Failure += e => Schedule(() => + { + if (e is OperationCanceledException) + return; + + PlaceholderState = PlaceholderState.NetworkFailure; + Logger.Error(e, @"Couldn't fetch beatmap scores!"); + }); api.Queue(getScoresRequest); } - private void onUpdateFailed(Exception e) - { - if (e is OperationCanceledException) - return; - - PlaceholderState = PlaceholderState.NetworkFailure; - Logger.Error(e, @"Couldn't fetch beatmap scores!"); - } + private Placeholder currentPlaceholder; private void replacePlaceholder(Placeholder placeholder) { - var existingPlaceholder = placeholderContainer.Children.LastOrDefault() as Placeholder; - - if (placeholder != null && placeholder.Equals(existingPlaceholder)) + if (placeholder != null && placeholder.Equals(currentPlaceholder)) return; - existingPlaceholder?.FadeOut(150, Easing.OutQuint).Expire(); + currentPlaceholder?.FadeOut(150, Easing.OutQuint).Expire(); if (placeholder == null) + { + currentPlaceholder = null; return; + } - Scores = null; - - placeholderContainer.Add(placeholder); + placeholderContainer.Child = placeholder; placeholder.ScaleTo(0.8f).Then().ScaleTo(1, fade_duration * 3, Easing.OutQuint); placeholder.FadeInFromZero(fade_duration, Easing.OutQuint); + + currentPlaceholder = placeholder; } protected override void Update() diff --git a/osu.Game/Tests/Visual/OsuTestCase.cs b/osu.Game/Tests/Visual/OsuTestCase.cs index decf0c9bdb..02f425c296 100644 --- a/osu.Game/Tests/Visual/OsuTestCase.cs +++ b/osu.Game/Tests/Visual/OsuTestCase.cs @@ -1,7 +1,6 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using System; using System.IO; using System.Reflection; using osu.Framework.Testing; @@ -10,28 +9,27 @@ namespace osu.Game.Tests.Visual { public abstract class OsuTestCase : TestCase { - public override void RunTest() - { - using (var host = new CleanRunHeadlessGameHost($"test-{Guid.NewGuid()}", realtime: false)) - host.Run(new OsuTestCaseTestRunner(this)); - } + protected override ITestCaseTestRunner CreateRunner() => new OsuTestCaseTestRunner(); - public class OsuTestCaseTestRunner : OsuGameBase + public class OsuTestCaseTestRunner : OsuGameBase, ITestCaseTestRunner { - private readonly OsuTestCase testCase; - protected override string MainResourceFile => File.Exists(base.MainResourceFile) ? base.MainResourceFile : Assembly.GetExecutingAssembly().Location; - public OsuTestCaseTestRunner(OsuTestCase testCase) + private readonly TestCaseTestRunner.TestRunner runner; + + public OsuTestCaseTestRunner() { - this.testCase = testCase; + runner = new TestCaseTestRunner.TestRunner(); } protected override void LoadComplete() { base.LoadComplete(); - Add(new TestCaseTestRunner.TestRunner(testCase)); + + Add(runner); } + + public void RunTestBlocking(TestCase test) => runner.RunTestBlocking(test); } } } diff --git a/osu.Game/Users/Badge.cs b/osu.Game/Users/Badge.cs new file mode 100644 index 0000000000..25ef8ffdf4 --- /dev/null +++ b/osu.Game/Users/Badge.cs @@ -0,0 +1,20 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using Newtonsoft.Json; + +namespace osu.Game.Users +{ + public class Badge + { + [JsonProperty("awarded_at")] + public DateTimeOffset AwardedAt; + + [JsonProperty("description")] + public string Description; + + [JsonProperty("image_url")] + public string ImageUrl; + } +} diff --git a/osu.Game/Users/User.cs b/osu.Game/Users/User.cs index 441e4ffd28..b983b639f0 100644 --- a/osu.Game/Users/User.cs +++ b/osu.Game/Users/User.cs @@ -137,6 +137,9 @@ namespace osu.Game.Users [JsonProperty(@"rankHistory")] public RankHistoryData RankHistory; + [JsonProperty("badges")] + public Badge[] Badges; + public override string ToString() => Username; } } diff --git a/osu.Game/Users/UserPanel.cs b/osu.Game/Users/UserPanel.cs index 424d7b7a8f..bcb91c1955 100644 --- a/osu.Game/Users/UserPanel.cs +++ b/osu.Game/Users/UserPanel.cs @@ -17,7 +17,7 @@ using osu.Framework.Graphics.UserInterface; using osu.Game.Graphics.UserInterface; using osu.Framework.Graphics.Cursor; using osu.Game.Graphics.Containers; -using osu.Game.Overlays.Profile; +using osu.Game.Overlays.Profile.Header; namespace osu.Game.Users { diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 367c3490d6..1a75f1979a 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -20,7 +20,7 @@ - + \ No newline at end of file diff --git a/osu.TestProject.props b/osu.TestProject.props index cb841f1940..afdf895eac 100644 --- a/osu.TestProject.props +++ b/osu.TestProject.props @@ -15,7 +15,7 @@ - +