diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderInput.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderInput.cs index b6fc9821a4..33d79d9cbc 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderInput.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderInput.cs @@ -44,6 +44,7 @@ namespace osu.Game.Rulesets.Osu.Tests private const double time_during_slide_2 = 3000; private const double time_during_slide_3 = 3500; private const double time_during_slide_4 = 3800; + private const double time_slider_end = 4000; private List judgementResults; private bool allJudgedFired; @@ -284,6 +285,48 @@ namespace osu.Game.Rulesets.Osu.Tests AddAssert("Tracking acquired", assertMidSliderJudgements); } + /// + /// Scenario: + /// - Press a key on the slider head + /// - While holding the key, move cursor close to the edge of tracking area + /// - Keep the cursor on the edge of tracking area until the slider ends + /// Expected Result: + /// A passing test case will have the slider track the cursor throughout the whole test. + /// + [Test] + public void TestTrackingAreaEdge() + { + performTest(new List + { + new OsuReplayFrame { Position = new Vector2(0, 0), Actions = { OsuAction.LeftButton }, Time = time_slider_start }, + new OsuReplayFrame { Position = new Vector2(0, OsuHitObject.OBJECT_RADIUS * 1.19f), Actions = { OsuAction.LeftButton }, Time = time_slider_start + 100 }, + new OsuReplayFrame { Position = new Vector2(slider_path_length, OsuHitObject.OBJECT_RADIUS * 1.199f), Actions = { OsuAction.LeftButton }, Time = time_slider_end }, + }); + + AddAssert("Tracking kept", assertGreatJudge); + } + + /// + /// Scenario: + /// - Press a key on the slider head + /// - While holding the key, move cursor just outside the tracking area + /// - Keep the cursor just outside the tracking area until the slider ends + /// Expected Result: + /// A passing test case will have the slider drop the tracking on frame 2. + /// + [Test] + public void TestTrackingAreaOutsideEdge() + { + performTest(new List + { + new OsuReplayFrame { Position = new Vector2(0, 0), Actions = { OsuAction.LeftButton }, Time = time_slider_start }, + new OsuReplayFrame { Position = new Vector2(0, OsuHitObject.OBJECT_RADIUS * 1.21f), Actions = { OsuAction.LeftButton }, Time = time_slider_start + 100 }, + new OsuReplayFrame { Position = new Vector2(slider_path_length, OsuHitObject.OBJECT_RADIUS * 1.201f), Actions = { OsuAction.LeftButton }, Time = time_slider_end }, + }); + + AddAssert("Tracking dropped", assertMidSliderJudgementFail); + } + private bool assertGreatJudge() => judgementResults.Last().Type == HitResult.Great; private bool assertHeadMissTailTracked() => judgementResults[^2].Type == HitResult.Great && judgementResults.First().Type == HitResult.Miss; @@ -294,6 +337,8 @@ namespace osu.Game.Rulesets.Osu.Tests private ScoreAccessibleReplayPlayer currentPlayer; + private const float slider_path_length = 25; + private void performTest(List frames) { AddStep("load player", () => @@ -309,8 +354,8 @@ namespace osu.Game.Rulesets.Osu.Tests Path = new SliderPath(PathType.PerfectCurve, new[] { Vector2.Zero, - new Vector2(25, 0), - }, 25), + new Vector2(slider_path_length, 0), + }, slider_path_length), } }, BeatmapInfo = diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointConnection.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointConnection.cs index 6c4fbbac17..a5e89210f6 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointConnection.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointConnection.cs @@ -23,7 +23,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections /// /// The start time of . /// - public readonly Bindable StartTime = new Bindable(); + public readonly Bindable StartTime = new BindableDouble(); /// /// The which s will exit from. diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs index 3162f4b379..4ef63bb2a0 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs @@ -22,7 +22,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables private readonly IBindable positionBindable = new Bindable(); private readonly IBindable stackHeightBindable = new Bindable(); - private readonly IBindable scaleBindable = new Bindable(); + private readonly IBindable scaleBindable = new BindableFloat(); public OsuAction? HitAction => HitArea.HitAction; diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableRepeatPoint.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableRepeatPoint.cs index 20b31c68f2..8fdcd060e7 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableRepeatPoint.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableRepeatPoint.cs @@ -37,7 +37,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables InternalChild = scaleContainer = new ReverseArrowPiece(); } - private readonly IBindable scaleBindable = new Bindable(); + private readonly IBindable scaleBindable = new BindableFloat(); [BackgroundDependencyLoader] private void load() diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs index cd3c572ba0..7403649184 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs @@ -37,7 +37,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables private readonly IBindable positionBindable = new Bindable(); private readonly IBindable stackHeightBindable = new Bindable(); - private readonly IBindable scaleBindable = new Bindable(); + private readonly IBindable scaleBindable = new BindableFloat(); public DrawableSlider(Slider s) : base(s) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTick.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTick.cs index 9d4d9958a1..60b5c335d6 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTick.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTick.cs @@ -52,7 +52,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables }; } - private readonly IBindable scaleBindable = new Bindable(); + private readonly IBindable scaleBindable = new BindableFloat(); [BackgroundDependencyLoader] private void load() diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBall.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBall.cs index ef7b077480..b89b0cafc4 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBall.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBall.cs @@ -23,7 +23,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces public Func GetInitialHitAction; private readonly Slider slider; - public readonly Drawable FollowCircle; + private readonly Drawable followCircle; + private readonly Drawable trackingArea; private readonly DrawableSlider drawableSlider; public SliderBall(Slider slider, DrawableSlider drawableSlider = null) @@ -38,7 +39,14 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces Children = new[] { - FollowCircle = new FollowCircleContainer + // This is separate from the visible followcircle to ensure consistent internal tracking area (needed to match osu-stable) + trackingArea = new CircularContainer + { + Origin = Anchor.Centre, + Anchor = Anchor.Centre, + RelativeSizeAxes = Axes.Both + }, + followCircle = new FollowCircleContainer { Origin = Anchor.Centre, Anchor = Anchor.Centre, @@ -95,8 +103,10 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces tracking = value; - FollowCircle.ScaleTo(tracking ? 2f : 1, 300, Easing.OutQuint); - FollowCircle.FadeTo(tracking ? 1f : 0, 300, Easing.OutQuint); + // Tracking area is bigger than the visible followcircle and scales instantly to match osu-stable + trackingArea.ScaleTo(tracking ? 2.4f : 1f); + followCircle.ScaleTo(tracking ? 2f : 1f, 300, Easing.OutQuint); + followCircle.FadeTo(tracking ? 1f : 0, 300, Easing.OutQuint); } } @@ -149,7 +159,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces // in valid time range Time.Current >= slider.StartTime && Time.Current < slider.EndTime && // in valid position range - lastScreenSpaceMousePosition.HasValue && FollowCircle.ReceivePositionalInputAt(lastScreenSpaceMousePosition.Value) && + lastScreenSpaceMousePosition.HasValue && trackingArea.ReceivePositionalInputAt(lastScreenSpaceMousePosition.Value) && // valid action (actions?.Any(isValidTrackingAction) ?? false); } diff --git a/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs b/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs index 0ba712a83f..15af141c99 100644 --- a/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs +++ b/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs @@ -57,7 +57,7 @@ namespace osu.Game.Rulesets.Osu.Objects public double Radius => OBJECT_RADIUS * Scale; - public readonly Bindable ScaleBindable = new Bindable(1); + public readonly Bindable ScaleBindable = new BindableFloat(1); public float Scale { diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorContainer.cs b/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorContainer.cs index a463542e64..79b5d1b7f8 100644 --- a/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorContainer.cs +++ b/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorContainer.cs @@ -57,7 +57,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor autoCursorScale = config.GetBindable(OsuSetting.AutoCursorSize); autoCursorScale.ValueChanged += _ => calculateScale(); - CursorScale = new Bindable(); + CursorScale = new BindableFloat(); CursorScale.ValueChanged += e => ActiveCursor.Scale = cursorTrail.Scale = new Vector2(e.NewValue); calculateScale(); diff --git a/osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs b/osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs index ca3030db3e..abba444c73 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs @@ -50,7 +50,7 @@ namespace osu.Game.Rulesets.Osu.UI { Add(localCursorContainer = new OsuCursorContainer()); - localCursorScale = new Bindable(); + localCursorScale = new BindableFloat(); localCursorScale.BindTo(localCursorContainer.CursorScale); localCursorScale.BindValueChanged(scale => cursorScaleContainer.Scale = new Vector2(scale.NewValue), true); } diff --git a/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs b/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs index 589ec7e8aa..29bcc7df9e 100644 --- a/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs +++ b/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs @@ -302,8 +302,8 @@ namespace osu.Game.Tests.Visual.Background } public readonly Bindable DimEnabled = new Bindable(); - public readonly Bindable DimLevel = new Bindable(); - public readonly Bindable BlurLevel = new Bindable(); + public readonly Bindable DimLevel = new BindableDouble(); + public readonly Bindable BlurLevel = new BindableDouble(); public new BeatmapCarousel Carousel => base.Carousel; diff --git a/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs index 1561d0d11d..19bdaff6ff 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs @@ -38,9 +38,21 @@ namespace osu.Game.Tests.Visual.Online private TestChatOverlay chatOverlay; private ChannelManager channelManager; - private readonly Channel channel1 = new Channel(new User()) { Name = "test really long username", Topic = "Topic for channel 1" }; - private readonly Channel channel2 = new Channel(new User()) { Name = "test2", Topic = "Topic for channel 2" }; - private readonly Channel channel3 = new Channel(new User()) { Name = "channel with no topic" }; + private readonly List channels; + + private Channel channel1 => channels[0]; + private Channel channel2 => channels[1]; + + public TestSceneChatOverlay() + { + channels = Enumerable.Range(1, 10) + .Select(index => new Channel(new User()) + { + Name = $"Channel no. {index}", + Topic = index == 3 ? null : $"We talk about the number {index} here" + }) + .ToList(); + } [SetUp] public void Setup() @@ -49,7 +61,7 @@ namespace osu.Game.Tests.Visual.Online { ChannelManagerContainer container; - Child = container = new ChannelManagerContainer(new List { channel1, channel2, channel3 }) + Child = container = new ChannelManagerContainer(channels) { RelativeSizeAxes = Axes.Both, }; @@ -103,7 +115,7 @@ namespace osu.Game.Tests.Visual.Online [Test] public void TestSearchInSelector() { - AddStep("search for 'test2'", () => chatOverlay.ChildrenOfType().First().Text = "test2"); + AddStep("search for 'no. 2'", () => chatOverlay.ChildrenOfType().First().Text = "no. 2"); AddUntilStep("only channel 2 visible", () => { var listItems = chatOverlay.ChildrenOfType().Where(c => c.IsPresent); @@ -111,6 +123,36 @@ namespace osu.Game.Tests.Visual.Online }); } + [Test] + public void TestChannelShortcutKeys() + { + AddStep("join 10 channels", () => channels.ForEach(channel => channelManager.JoinChannel(channel))); + AddStep("close channel selector", () => + { + InputManager.PressKey(Key.Escape); + InputManager.ReleaseKey(Key.Escape); + }); + AddUntilStep("wait for close", () => chatOverlay.SelectionOverlayState == Visibility.Hidden); + + for (int zeroBasedIndex = 0; zeroBasedIndex < 10; ++zeroBasedIndex) + { + var oneBasedIndex = zeroBasedIndex + 1; + var targetNumberKey = oneBasedIndex % 10; + var targetChannel = channels[zeroBasedIndex]; + AddStep($"press Alt+{targetNumberKey}", () => pressChannelHotkey(targetNumberKey)); + AddAssert($"channel #{oneBasedIndex} is selected", () => channelManager.CurrentChannel.Value == targetChannel); + } + } + + private void pressChannelHotkey(int number) + { + var channelKey = Key.Number0 + number; + InputManager.PressKey(Key.AltLeft); + InputManager.PressKey(channelKey); + InputManager.ReleaseKey(Key.AltLeft); + InputManager.ReleaseKey(channelKey); + } + private void clickDrawable(Drawable d) { InputManager.MoveMouseTo(d); diff --git a/osu.Game.Tests/Visual/Online/TestSceneCommentsContainer.cs b/osu.Game.Tests/Visual/Online/TestSceneCommentsContainer.cs index 3deb9cb1fa..9c526c4f81 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneCommentsContainer.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneCommentsContainer.cs @@ -25,7 +25,8 @@ namespace osu.Game.Tests.Visual.Online typeof(SortTabControl), typeof(ShowChildrenButton), typeof(DeletedCommentsCounter), - typeof(VotePill) + typeof(VotePill), + typeof(CommentsPage), }; protected override bool UseOnlineAPI => true; diff --git a/osu.Game.Tests/Visual/Online/TestSceneCommentsPage.cs b/osu.Game.Tests/Visual/Online/TestSceneCommentsPage.cs new file mode 100644 index 0000000000..1217ce6b42 --- /dev/null +++ b/osu.Game.Tests/Visual/Online/TestSceneCommentsPage.cs @@ -0,0 +1,162 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using osu.Game.Overlays.Comments; +using osu.Game.Overlays; +using osu.Framework.Allocation; +using osu.Game.Online.API.Requests.Responses; +using osu.Game.Users; +using osu.Game.Graphics.UserInterface; +using osu.Framework.Bindables; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics; +using osuTK; + +namespace osu.Game.Tests.Visual.Online +{ + public class TestSceneCommentsPage : OsuTestScene + { + public override IReadOnlyList RequiredTypes => new[] + { + typeof(DrawableComment), + typeof(CommentsPage), + }; + + [Cached] + private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Purple); + + private readonly BindableBool showDeleted = new BindableBool(); + private readonly Container content; + + public TestSceneCommentsPage() + { + Add(new FillFlowContainer + { + AutoSizeAxes = Axes.Y, + RelativeSizeAxes = Axes.X, + Direction = FillDirection.Vertical, + Spacing = new Vector2(0, 10), + Children = new Drawable[] + { + new Container + { + AutoSizeAxes = Axes.Y, + Width = 200, + Child = new OsuCheckbox + { + Current = showDeleted, + LabelText = @"Show Deleted" + } + }, + content = new Container + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + } + } + }); + + AddStep("load comments", () => createPage(comment_bundle)); + AddStep("load empty comments", () => createPage(empty_comment_bundle)); + } + + private void createPage(CommentBundle commentBundle) + { + content.Clear(); + content.Add(new CommentsPage(commentBundle) + { + ShowDeleted = { BindTarget = showDeleted } + }); + } + + private static readonly CommentBundle empty_comment_bundle = new CommentBundle + { + Comments = new List(), + Total = 0, + }; + + private static readonly CommentBundle comment_bundle = new CommentBundle + { + Comments = new List + { + new Comment + { + Id = 1, + Message = "Simple test comment", + LegacyName = "TestUser1", + CreatedAt = DateTimeOffset.Now, + VotesCount = 5 + }, + new Comment + { + Id = 2, + Message = "This comment has been deleted :( but visible for admins", + LegacyName = "TestUser2", + CreatedAt = DateTimeOffset.Now, + DeletedAt = DateTimeOffset.Now, + VotesCount = 5 + }, + new Comment + { + Id = 3, + Message = "This comment is a top level", + LegacyName = "TestUser3", + CreatedAt = DateTimeOffset.Now, + RepliesCount = 2, + }, + new Comment + { + Id = 4, + ParentId = 3, + Message = "And this is a reply", + RepliesCount = 1, + LegacyName = "TestUser1", + CreatedAt = DateTimeOffset.Now, + }, + new Comment + { + Id = 15, + ParentId = 4, + Message = "Reply to reply", + LegacyName = "TestUser1", + CreatedAt = DateTimeOffset.Now, + }, + new Comment + { + Id = 6, + ParentId = 3, + LegacyName = "TestUser11515", + CreatedAt = DateTimeOffset.Now, + DeletedAt = DateTimeOffset.Now, + }, + new Comment + { + Id = 5, + Message = "This comment is voted and edited", + LegacyName = "BigBrainUser", + CreatedAt = DateTimeOffset.Now, + EditedAt = DateTimeOffset.Now, + VotesCount = 1000, + EditedById = 1, + } + }, + IncludedComments = new List(), + UserVotes = new List + { + 5 + }, + Users = new List + { + new User + { + Id = 1, + Username = "Good_Admin" + } + }, + TopLevelCount = 4, + Total = 7 + }; + } +} diff --git a/osu.Game.Tests/Visual/Online/TestSceneHistoricalSection.cs b/osu.Game.Tests/Visual/Online/TestSceneHistoricalSection.cs index 1b7a2160d0..d098ea8b16 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneHistoricalSection.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneHistoricalSection.cs @@ -26,7 +26,6 @@ namespace osu.Game.Tests.Visual.Online typeof(HistoricalSection), typeof(PaginatedMostPlayedBeatmapContainer), typeof(DrawableMostPlayedBeatmap), - typeof(DrawableProfileRow) }; [Cached] diff --git a/osu.Game.Tests/Visual/Online/TestSceneProfileRulesetSelector.cs b/osu.Game.Tests/Visual/Online/TestSceneProfileRulesetSelector.cs index 1f5ba67e03..826624f686 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneProfileRulesetSelector.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneProfileRulesetSelector.cs @@ -11,6 +11,8 @@ using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Taiko; using osu.Game.Users; using osu.Framework.Bindables; +using osu.Game.Overlays; +using osu.Framework.Allocation; namespace osu.Game.Tests.Visual.Online { @@ -22,10 +24,13 @@ namespace osu.Game.Tests.Visual.Online typeof(ProfileRulesetTabItem), }; + [Cached] + private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Green); + public TestSceneProfileRulesetSelector() { ProfileRulesetSelector selector; - Bindable user = new Bindable(); + var user = new Bindable(); Child = selector = new ProfileRulesetSelector { diff --git a/osu.Game.Tests/Visual/Online/TestSceneRankGraph.cs b/osu.Game.Tests/Visual/Online/TestSceneRankGraph.cs index c70cc4ae4e..8f7e7498a9 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneRankGraph.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneRankGraph.cs @@ -4,11 +4,13 @@ using System; using System.Collections.Generic; using NUnit.Framework; +using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Game.Graphics; using osu.Game.Graphics.UserInterface; +using osu.Game.Overlays; using osu.Game.Overlays.Profile.Header.Components; using osu.Game.Users; using osuTK; @@ -24,6 +26,9 @@ namespace osu.Game.Tests.Visual.Online typeof(LineGraph) }; + [Cached] + private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Pink); + public TestSceneRankGraph() { RankGraph graph; diff --git a/osu.Game.Tests/Visual/Online/TestSceneUserProfileRecentSection.cs b/osu.Game.Tests/Visual/Online/TestSceneUserProfileRecentSection.cs index f022425bf6..06091f3c81 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneUserProfileRecentSection.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneUserProfileRecentSection.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.Linq; using NUnit.Framework; +using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; @@ -12,6 +13,7 @@ using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Online.API.Requests; using osu.Game.Online.API.Requests.Responses; +using osu.Game.Overlays; using osu.Game.Overlays.Profile.Sections; using osu.Game.Overlays.Profile.Sections.Recent; @@ -28,6 +30,9 @@ namespace osu.Game.Tests.Visual.Online typeof(MedalIcon) }; + [Cached] + private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Green); + public TestSceneUserProfileRecentSection() { Children = new Drawable[] @@ -131,6 +136,22 @@ namespace osu.Game.Tests.Visual.Online Beatmap = dummyBeatmap, }, new APIRecentActivity + { + User = dummyUser, + Type = RecentActivityType.Rank, + Rank = 1, + Mode = "vitaru", + Beatmap = dummyBeatmap, + }, + new APIRecentActivity + { + User = dummyUser, + Type = RecentActivityType.Rank, + Rank = 1, + Mode = "fruits", + Beatmap = dummyBeatmap, + }, + new APIRecentActivity { User = dummyUser, Type = RecentActivityType.RankLost, diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneAdvancedStats.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneAdvancedStats.cs new file mode 100644 index 0000000000..3d3517ada4 --- /dev/null +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneAdvancedStats.cs @@ -0,0 +1,175 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Linq; +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Testing; +using osu.Game.Beatmaps; +using osu.Game.Graphics; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Mods; +using osu.Game.Screens.Select.Details; +using osuTK.Graphics; + +namespace osu.Game.Tests.Visual.SongSelect +{ + [System.ComponentModel.Description("Advanced beatmap statistics display")] + public class TestSceneAdvancedStats : OsuTestScene + { + private TestAdvancedStats advancedStats; + + [Resolved] + private RulesetStore rulesets { get; set; } + + [Resolved] + private OsuColour colours { get; set; } + + [SetUp] + public void Setup() => Schedule(() => Child = advancedStats = new TestAdvancedStats + { + Width = 500 + }); + + private BeatmapInfo exampleBeatmapInfo => new BeatmapInfo + { + RulesetID = 0, + Ruleset = rulesets.AvailableRulesets.First(), + BaseDifficulty = new BeatmapDifficulty + { + CircleSize = 7.2f, + DrainRate = 3, + OverallDifficulty = 5.7f, + ApproachRate = 3.5f + }, + StarDifficulty = 4.5f + }; + + [Test] + public void TestNoMod() + { + AddStep("set beatmap", () => advancedStats.Beatmap = exampleBeatmapInfo); + + AddStep("no mods selected", () => SelectedMods.Value = Array.Empty()); + + AddAssert("first bar text is Circle Size", () => advancedStats.ChildrenOfType().First().Text == "Circle Size"); + AddAssert("circle size bar is white", () => barIsWhite(advancedStats.FirstValue)); + AddAssert("HP drain bar is white", () => barIsWhite(advancedStats.HpDrain)); + AddAssert("accuracy bar is white", () => barIsWhite(advancedStats.Accuracy)); + AddAssert("approach rate bar is white", () => barIsWhite(advancedStats.ApproachRate)); + } + + [Test] + public void TestManiaFirstBarText() + { + AddStep("set beatmap", () => advancedStats.Beatmap = new BeatmapInfo + { + Ruleset = rulesets.GetRuleset(3), + BaseDifficulty = new BeatmapDifficulty + { + CircleSize = 5, + DrainRate = 4.3f, + OverallDifficulty = 4.5f, + ApproachRate = 3.1f + }, + StarDifficulty = 8 + }); + + AddAssert("first bar text is Key Count", () => advancedStats.ChildrenOfType().First().Text == "Key Count"); + } + + [Test] + public void TestEasyMod() + { + AddStep("set beatmap", () => advancedStats.Beatmap = exampleBeatmapInfo); + + AddStep("select EZ mod", () => + { + var ruleset = advancedStats.Beatmap.Ruleset.CreateInstance(); + SelectedMods.Value = new[] { ruleset.GetAllMods().OfType().Single() }; + }); + + AddAssert("circle size bar is blue", () => barIsBlue(advancedStats.FirstValue)); + AddAssert("HP drain bar is blue", () => barIsBlue(advancedStats.HpDrain)); + AddAssert("accuracy bar is blue", () => barIsBlue(advancedStats.Accuracy)); + AddAssert("approach rate bar is blue", () => barIsBlue(advancedStats.ApproachRate)); + } + + [Test] + public void TestHardRockMod() + { + AddStep("set beatmap", () => advancedStats.Beatmap = exampleBeatmapInfo); + + AddStep("select HR mod", () => + { + var ruleset = advancedStats.Beatmap.Ruleset.CreateInstance(); + SelectedMods.Value = new[] { ruleset.GetAllMods().OfType().Single() }; + }); + + AddAssert("circle size bar is red", () => barIsRed(advancedStats.FirstValue)); + AddAssert("HP drain bar is red", () => barIsRed(advancedStats.HpDrain)); + AddAssert("accuracy bar is red", () => barIsRed(advancedStats.Accuracy)); + AddAssert("approach rate bar is red", () => barIsRed(advancedStats.ApproachRate)); + } + + [Test] + public void TestUnchangedDifficultyAdjustMod() + { + AddStep("set beatmap", () => advancedStats.Beatmap = exampleBeatmapInfo); + + AddStep("select unchanged Difficulty Adjust mod", () => + { + var ruleset = advancedStats.Beatmap.Ruleset.CreateInstance(); + var difficultyAdjustMod = ruleset.GetAllMods().OfType().Single(); + difficultyAdjustMod.ReadFromDifficulty(advancedStats.Beatmap.BaseDifficulty); + SelectedMods.Value = new[] { difficultyAdjustMod }; + }); + + AddAssert("circle size bar is white", () => barIsWhite(advancedStats.FirstValue)); + AddAssert("HP drain bar is white", () => barIsWhite(advancedStats.HpDrain)); + AddAssert("accuracy bar is white", () => barIsWhite(advancedStats.Accuracy)); + AddAssert("approach rate bar is white", () => barIsWhite(advancedStats.ApproachRate)); + } + + [Test] + public void TestChangedDifficultyAdjustMod() + { + AddStep("set beatmap", () => advancedStats.Beatmap = exampleBeatmapInfo); + + AddStep("select changed Difficulty Adjust mod", () => + { + var ruleset = advancedStats.Beatmap.Ruleset.CreateInstance(); + var difficultyAdjustMod = ruleset.GetAllMods().OfType().Single(); + var originalDifficulty = advancedStats.Beatmap.BaseDifficulty; + var adjustedDifficulty = new BeatmapDifficulty + { + CircleSize = originalDifficulty.CircleSize, + DrainRate = originalDifficulty.DrainRate - 0.5f, + OverallDifficulty = originalDifficulty.OverallDifficulty, + ApproachRate = originalDifficulty.ApproachRate + 2.2f, + }; + difficultyAdjustMod.ReadFromDifficulty(adjustedDifficulty); + SelectedMods.Value = new[] { difficultyAdjustMod }; + }); + + AddAssert("circle size bar is white", () => barIsWhite(advancedStats.FirstValue)); + AddAssert("drain rate bar is blue", () => barIsBlue(advancedStats.HpDrain)); + AddAssert("accuracy bar is white", () => barIsWhite(advancedStats.Accuracy)); + AddAssert("approach rate bar is red", () => barIsRed(advancedStats.ApproachRate)); + } + + private bool barIsWhite(AdvancedStats.StatisticRow row) => row.ModBar.AccentColour == Color4.White; + private bool barIsBlue(AdvancedStats.StatisticRow row) => row.ModBar.AccentColour == colours.BlueDark; + private bool barIsRed(AdvancedStats.StatisticRow row) => row.ModBar.AccentColour == colours.Red; + + private class TestAdvancedStats : AdvancedStats + { + public new StatisticRow FirstValue => base.FirstValue; + public new StatisticRow HpDrain => base.HpDrain; + public new StatisticRow Accuracy => base.Accuracy; + public new StatisticRow ApproachRate => base.ApproachRate; + } + } +} diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapDetails.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapDetails.cs index 6aa5a76490..acf037198f 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapDetails.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapDetails.cs @@ -3,14 +3,8 @@ using System.Linq; using NUnit.Framework; -using osu.Framework.Allocation; using osu.Framework.Graphics; -using osu.Framework.Testing; using osu.Game.Beatmaps; -using osu.Game.Graphics; -using osu.Game.Graphics.UserInterface; -using osu.Game.Rulesets; -using osu.Game.Rulesets.Mods; using osu.Game.Screens.Select; namespace osu.Game.Tests.Visual.SongSelect @@ -180,27 +174,5 @@ namespace osu.Game.Tests.Visual.SongSelect OnlineBeatmapID = 162, }); } - - [Resolved] - private RulesetStore rulesets { get; set; } - - [Resolved] - private OsuColour colours { get; set; } - - [Test] - public void TestModAdjustments() - { - TestAllMetrics(); - - Ruleset ruleset = rulesets.AvailableRulesets.First().CreateInstance(); - - AddStep("with EZ mod", () => SelectedMods.Value = new[] { ruleset.GetAllMods().First(m => m is ModEasy) }); - - AddAssert("first bar coloured blue", () => details.ChildrenOfType().Skip(1).First().AccentColour == colours.BlueDark); - - AddStep("with HR mod", () => SelectedMods.Value = new[] { ruleset.GetAllMods().First(m => m is ModHardRock) }); - - AddAssert("first bar coloured red", () => details.ChildrenOfType().Skip(1).First().AccentColour == colours.Red); - } } } diff --git a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs index c01dee2959..80192b9ebc 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs @@ -23,6 +23,7 @@ using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Mods; using osu.Game.Rulesets.Taiko; +using osu.Game.Screens.Play; using osu.Game.Screens.Select; using osu.Game.Screens.Select.Carousel; using osu.Game.Screens.Select.Filter; @@ -77,7 +78,6 @@ namespace osu.Game.Tests.Visual.SongSelect private OsuConfigManager config; - [SetUpSteps] public override void SetUpSteps() { base.SetUpSteps(); @@ -426,6 +426,31 @@ namespace osu.Game.Tests.Visual.SongSelect AddAssert("start not requested", () => !startRequested); } + [Test] + public void TestAutoplayViaCtrlEnter() + { + addRulesetImportStep(0); + + createSongSelect(); + + AddStep("press ctrl+enter", () => + { + InputManager.PressKey(Key.ControlLeft); + InputManager.PressKey(Key.Enter); + + InputManager.ReleaseKey(Key.ControlLeft); + InputManager.ReleaseKey(Key.Enter); + }); + + AddUntilStep("wait for player", () => Stack.CurrentScreen is PlayerLoader); + + AddAssert("autoplay enabled", () => songSelect.Mods.Value.FirstOrDefault() is ModAutoplay); + + AddUntilStep("wait for return to ss", () => songSelect.IsCurrentScreen()); + + AddAssert("mod disabled", () => songSelect.Mods.Value.Count == 0); + } + [Test] public void TestHideSetSelectsCorrectBeatmap() { diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneOverlayRulesetSelector.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneOverlayRulesetSelector.cs new file mode 100644 index 0000000000..8a98127793 --- /dev/null +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneOverlayRulesetSelector.cs @@ -0,0 +1,82 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Graphics; +using System; +using System.Collections.Generic; +using osu.Game.Rulesets.Catch; +using osu.Game.Rulesets.Mania; +using osu.Game.Rulesets.Osu; +using osu.Game.Rulesets.Taiko; +using osu.Framework.Bindables; +using osu.Game.Overlays; +using osu.Game.Rulesets; +using NUnit.Framework; +using osu.Framework.Graphics.Containers; +using osuTK; +using osu.Framework.Allocation; + +namespace osu.Game.Tests.Visual.UserInterface +{ + public class TestSceneOverlayRulesetSelector : OsuTestScene + { + public override IReadOnlyList RequiredTypes => new[] + { + typeof(OverlayRulesetSelector), + typeof(OverlayRulesetTabItem), + }; + + private readonly OverlayRulesetSelector selector; + private readonly Bindable ruleset = new Bindable(); + + public TestSceneOverlayRulesetSelector() + { + Add(new FillFlowContainer + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, + Spacing = new Vector2(0, 5), + Children = new[] + { + new ColourProvidedContainer(OverlayColourScheme.Green, selector = new OverlayRulesetSelector { Current = ruleset }), + new ColourProvidedContainer(OverlayColourScheme.Blue, new OverlayRulesetSelector { Current = ruleset }), + new ColourProvidedContainer(OverlayColourScheme.Orange, new OverlayRulesetSelector { Current = ruleset }), + new ColourProvidedContainer(OverlayColourScheme.Pink, new OverlayRulesetSelector { Current = ruleset }), + new ColourProvidedContainer(OverlayColourScheme.Purple, new OverlayRulesetSelector { Current = ruleset }), + new ColourProvidedContainer(OverlayColourScheme.Red, new OverlayRulesetSelector { Current = ruleset }), + } + }); + } + + private class ColourProvidedContainer : Container + { + [Cached] + private readonly OverlayColourProvider colourProvider; + + public ColourProvidedContainer(OverlayColourScheme colourScheme, OverlayRulesetSelector rulesetSelector) + { + colourProvider = new OverlayColourProvider(colourScheme); + AutoSizeAxes = Axes.Both; + Add(rulesetSelector); + } + } + + [Test] + public void TestSelection() + { + AddStep("Select osu!", () => ruleset.Value = new OsuRuleset().RulesetInfo); + AddAssert("Check osu! selected", () => selector.Current.Value.Equals(new OsuRuleset().RulesetInfo)); + + AddStep("Select mania", () => ruleset.Value = new ManiaRuleset().RulesetInfo); + AddAssert("Check mania selected", () => selector.Current.Value.Equals(new ManiaRuleset().RulesetInfo)); + + AddStep("Select taiko", () => ruleset.Value = new TaikoRuleset().RulesetInfo); + AddAssert("Check taiko selected", () => selector.Current.Value.Equals(new TaikoRuleset().RulesetInfo)); + + AddStep("Select catch", () => ruleset.Value = new CatchRuleset().RulesetInfo); + AddAssert("Check catch selected", () => selector.Current.Value.Equals(new CatchRuleset().RulesetInfo)); + } + } +} diff --git a/osu.Game/Graphics/DrawableDate.cs b/osu.Game/Graphics/DrawableDate.cs index 925c7981e0..0224c77ee8 100644 --- a/osu.Game/Graphics/DrawableDate.cs +++ b/osu.Game/Graphics/DrawableDate.cs @@ -29,9 +29,9 @@ namespace osu.Game.Graphics } } - public DrawableDate(DateTimeOffset date, float textSize = OsuFont.DEFAULT_FONT_SIZE) + public DrawableDate(DateTimeOffset date, float textSize = OsuFont.DEFAULT_FONT_SIZE, bool italic = true) { - Font = OsuFont.GetFont(weight: FontWeight.Regular, size: textSize, italics: true); + Font = OsuFont.GetFont(weight: FontWeight.Regular, size: textSize, italics: italic); Date = date; } diff --git a/osu.Game/Graphics/UserInterface/ShowMoreButton.cs b/osu.Game/Graphics/UserInterface/ShowMoreButton.cs index 4931a6aed6..c9cd9f1158 100644 --- a/osu.Game/Graphics/UserInterface/ShowMoreButton.cs +++ b/osu.Game/Graphics/UserInterface/ShowMoreButton.cs @@ -40,13 +40,14 @@ namespace osu.Game.Graphics.UserInterface public ShowMoreButton() { - AutoSizeAxes = Axes.Both; + Height = 30; + Width = 140; } protected override Drawable CreateContent() => new CircularContainer { Masking = true, - Size = new Vector2(140, 30), + RelativeSizeAxes = Axes.Both, Children = new Drawable[] { background = new Box diff --git a/osu.Game/Online/API/Requests/Responses/Comment.cs b/osu.Game/Online/API/Requests/Responses/Comment.cs index 5510e9afff..3e38c3067b 100644 --- a/osu.Game/Online/API/Requests/Responses/Comment.cs +++ b/osu.Game/Online/API/Requests/Responses/Comment.cs @@ -6,8 +6,6 @@ using osu.Game.Users; using System; using System.Collections.Generic; using System.Linq; -using System.Net; -using System.Text.RegularExpressions; namespace osu.Game.Online.API.Requests.Responses { @@ -70,12 +68,10 @@ namespace osu.Game.Online.API.Requests.Responses public bool IsDeleted => DeletedAt.HasValue; - public bool HasMessage => !string.IsNullOrEmpty(MessageHtml); + public bool HasMessage => !string.IsNullOrEmpty(Message); public bool IsVoted { get; set; } - public string GetMessage => HasMessage ? WebUtility.HtmlDecode(Regex.Replace(MessageHtml, @"<(.|\n)*?>", string.Empty)) : string.Empty; - public int DeletedChildrenCount => ChildComments.Count(c => c.IsDeleted); } } diff --git a/osu.Game/Online/API/Requests/Responses/CommentBundle.cs b/osu.Game/Online/API/Requests/Responses/CommentBundle.cs index 8db5d8d6ad..9b3ef8b6e5 100644 --- a/osu.Game/Online/API/Requests/Responses/CommentBundle.cs +++ b/osu.Game/Online/API/Requests/Responses/CommentBundle.cs @@ -47,17 +47,18 @@ namespace osu.Game.Online.API.Requests.Responses [JsonProperty(@"included_comments")] public List IncludedComments { get; set; } + private List userVotes; + [JsonProperty(@"user_votes")] - private List userVotes + public List UserVotes { - set => value.ForEach(v => + get => userVotes; + set { - Comments.ForEach(c => - { - if (v == c.Id) - c.IsVoted = true; - }); - }); + userVotes = value; + + Comments.ForEach(c => c.IsVoted = value.Contains(c.Id)); + } } private List users; diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index dc20d2abf6..ff3dee55af 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -386,7 +386,7 @@ namespace osu.Game Beatmap.Value = BeatmapManager.GetWorkingBeatmap(databasedBeatmap); screen.Push(new ReplayPlayerLoader(databasedScore)); - }, new[] { typeof(PlaySongSelect) }); + }, validScreens: new[] { typeof(PlaySongSelect) }); } protected virtual Loader CreateLoader() => new Loader(); diff --git a/osu.Game/Overlays/ChatOverlay.cs b/osu.Game/Overlays/ChatOverlay.cs index 2c0fa49b5d..f2aef0995f 100644 --- a/osu.Game/Overlays/ChatOverlay.cs +++ b/osu.Game/Overlays/ChatOverlay.cs @@ -321,8 +321,10 @@ namespace osu.Game.Overlays private void selectTab(int index) { - var channel = ChannelTabControl.Items.Skip(index).FirstOrDefault(); - if (channel != null && !(channel is ChannelSelectorTabItem.ChannelSelectorTabChannel)) + var channel = ChannelTabControl.Items + .Where(tab => !(tab is ChannelSelectorTabItem.ChannelSelectorTabChannel)) + .ElementAtOrDefault(index); + if (channel != null) ChannelTabControl.Current.Value = channel; } diff --git a/osu.Game/Overlays/Comments/CommentsContainer.cs b/osu.Game/Overlays/Comments/CommentsContainer.cs index e641cd8ddf..8761b88b1e 100644 --- a/osu.Game/Overlays/Comments/CommentsContainer.cs +++ b/osu.Game/Overlays/Comments/CommentsContainer.cs @@ -68,6 +68,7 @@ namespace osu.Game.Overlays.Comments }, new Container { + Name = @"Footer", RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, Children = new Drawable[] @@ -154,6 +155,7 @@ namespace osu.Game.Overlays.Comments { currentPage = 1; deletedCommentsCounter.Count.Value = 0; + moreButton.Show(); moreButton.IsLoading = true; content.Clear(); } @@ -162,25 +164,10 @@ namespace osu.Game.Overlays.Comments { loadCancellation = new CancellationTokenSource(); - var page = new FillFlowContainer + LoadComponentAsync(new CommentsPage(response) { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Direction = FillDirection.Vertical, - }; - - foreach (var c in response.Comments) - { - if (c.IsTopLevel) - { - page.Add(new DrawableComment(c) - { - ShowDeleted = { BindTarget = ShowDeleted } - }); - } - } - - LoadComponentAsync(page, loaded => + ShowDeleted = { BindTarget = ShowDeleted } + }, loaded => { content.Add(loaded); @@ -194,10 +181,12 @@ namespace osu.Game.Overlays.Comments moreButton.Current.Value = response.TopLevelCount - loadedTopLevelComments; moreButton.IsLoading = false; } + else + { + moreButton.Hide(); + } commentCounter.Current.Value = response.Total; - - moreButton.FadeTo(response.HasMore ? 1 : 0); }, loadCancellation.Token); } diff --git a/osu.Game/Overlays/Comments/CommentsPage.cs b/osu.Game/Overlays/Comments/CommentsPage.cs new file mode 100644 index 0000000000..153ce676eb --- /dev/null +++ b/osu.Game/Overlays/Comments/CommentsPage.cs @@ -0,0 +1,92 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics; +using osu.Framework.Bindables; +using osu.Game.Online.API.Requests.Responses; +using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics.Sprites; +using System.Linq; + +namespace osu.Game.Overlays.Comments +{ + public class CommentsPage : CompositeDrawable + { + public readonly BindableBool ShowDeleted = new BindableBool(); + + private readonly CommentBundle commentBundle; + + public CommentsPage(CommentBundle commentBundle) + { + this.commentBundle = commentBundle; + } + + [BackgroundDependencyLoader] + private void load(OverlayColourProvider colourProvider) + { + FillFlowContainer flow; + + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + + AddRangeInternal(new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = colourProvider.Background5 + }, + flow = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + } + }); + + if (!commentBundle.Comments.Any()) + { + flow.Add(new NoCommentsPlaceholder()); + return; + } + + foreach (var c in commentBundle.Comments) + { + if (c.IsTopLevel) + { + flow.Add(new DrawableComment(c) + { + ShowDeleted = { BindTarget = ShowDeleted } + }); + } + } + } + + private class NoCommentsPlaceholder : CompositeDrawable + { + [BackgroundDependencyLoader] + private void load(OverlayColourProvider colourProvider) + { + Height = 80; + RelativeSizeAxes = Axes.X; + AddRangeInternal(new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = colourProvider.Background4 + }, + new OsuSpriteText + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Margin = new MarginPadding { Left = 50 }, + Text = @"No comments yet." + } + }); + } + } + } +} diff --git a/osu.Game/Overlays/Comments/CommentsShowMoreButton.cs b/osu.Game/Overlays/Comments/CommentsShowMoreButton.cs index ab65c9c63a..d2ff7ecb1f 100644 --- a/osu.Game/Overlays/Comments/CommentsShowMoreButton.cs +++ b/osu.Game/Overlays/Comments/CommentsShowMoreButton.cs @@ -14,6 +14,8 @@ namespace osu.Game.Overlays.Comments [BackgroundDependencyLoader] private void load(OverlayColourProvider colourProvider) { + Height = 20; + IdleColour = colourProvider.Background2; HoverColour = colourProvider.Background1; ChevronIconColour = colourProvider.Foreground1; diff --git a/osu.Game/Overlays/Comments/DrawableComment.cs b/osu.Game/Overlays/Comments/DrawableComment.cs index 0f217f057d..70ea478814 100644 --- a/osu.Game/Overlays/Comments/DrawableComment.cs +++ b/osu.Game/Overlays/Comments/DrawableComment.cs @@ -213,7 +213,7 @@ namespace osu.Game.Overlays.Comments if (comment.HasMessage) { - var formattedSource = MessageFormatter.FormatText(comment.GetMessage); + var formattedSource = MessageFormatter.FormatText(comment.Message); message.AddLinks(formattedSource.Text, formattedSource.Links); } @@ -343,7 +343,7 @@ namespace osu.Game.Overlays.Comments if (parentComment == null) return string.Empty; - return parentComment.HasMessage ? parentComment.GetMessage : parentComment.IsDeleted ? @"deleted" : string.Empty; + return parentComment.HasMessage ? parentComment.Message : parentComment.IsDeleted ? @"deleted" : string.Empty; } } } diff --git a/osu.Game/Overlays/MusicController.cs b/osu.Game/Overlays/MusicController.cs index 19f06e99f1..7c7daf6eb9 100644 --- a/osu.Game/Overlays/MusicController.cs +++ b/osu.Game/Overlays/MusicController.cs @@ -196,7 +196,7 @@ namespace osu.Game.Overlays if (!instant) queuedDirection = TrackChangeDirection.Next; - var playable = BeatmapSets.SkipWhile(i => i.ID != current.BeatmapSetInfo.ID).Skip(1).FirstOrDefault() ?? BeatmapSets.FirstOrDefault(); + var playable = BeatmapSets.SkipWhile(i => i.ID != current.BeatmapSetInfo.ID).ElementAtOrDefault(1) ?? BeatmapSets.FirstOrDefault(); if (playable != null) { diff --git a/osu.Game/Overlays/OverlayRulesetSelector.cs b/osu.Game/Overlays/OverlayRulesetSelector.cs new file mode 100644 index 0000000000..b73d38eeb3 --- /dev/null +++ b/osu.Game/Overlays/OverlayRulesetSelector.cs @@ -0,0 +1,28 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.UserInterface; +using osu.Game.Rulesets; +using osuTK; + +namespace osu.Game.Overlays +{ + public class OverlayRulesetSelector : RulesetSelector + { + public OverlayRulesetSelector() + { + AutoSizeAxes = Axes.Both; + } + + protected override TabItem CreateTabItem(RulesetInfo value) => new OverlayRulesetTabItem(value); + + protected override TabFillFlowContainer CreateTabFlow() => new TabFillFlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(25, 0), + }; + } +} diff --git a/osu.Game/Overlays/OverlayRulesetTabItem.cs b/osu.Game/Overlays/OverlayRulesetTabItem.cs new file mode 100644 index 0000000000..f8a9d14f62 --- /dev/null +++ b/osu.Game/Overlays/OverlayRulesetTabItem.cs @@ -0,0 +1,93 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.UserInterface; +using osu.Framework.Input.Events; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; +using osu.Game.Rulesets; +using osuTK.Graphics; +using osuTK; +using osu.Framework.Allocation; + +namespace osu.Game.Overlays +{ + public class OverlayRulesetTabItem : TabItem + { + private Color4 accentColour; + + protected virtual Color4 AccentColour + { + get => accentColour; + set + { + accentColour = value; + text.FadeColour(value, 120, Easing.OutQuint); + } + } + + protected override Container Content { get; } + + [Resolved] + private OverlayColourProvider colourProvider { get; set; } + + private readonly OsuSpriteText text; + + public OverlayRulesetTabItem(RulesetInfo value) + : base(value) + { + AutoSizeAxes = Axes.Both; + + AddRangeInternal(new Drawable[] + { + Content = new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(3, 0), + Child = text = new OsuSpriteText + { + Origin = Anchor.Centre, + Anchor = Anchor.Centre, + Text = value.Name, + } + }, + new HoverClickSounds() + }); + + Enabled.Value = true; + } + + [BackgroundDependencyLoader] + private void load() + { + updateState(); + } + + protected override bool OnHover(HoverEvent e) + { + base.OnHover(e); + updateState(); + return true; + } + + protected override void OnHoverLost(HoverLostEvent e) + { + base.OnHoverLost(e); + updateState(); + } + + protected override void OnActivated() => updateState(); + + protected override void OnDeactivated() => updateState(); + + private void updateState() + { + text.Font = text.Font.With(weight: Active.Value ? FontWeight.Bold : FontWeight.Medium); + AccentColour = IsHovered || Active.Value ? Color4.White : colourProvider.Highlight1; + } + } +} diff --git a/osu.Game/Overlays/Profile/Header/BottomHeaderContainer.cs b/osu.Game/Overlays/Profile/Header/BottomHeaderContainer.cs index b6c6f33678..c27b5f4b4a 100644 --- a/osu.Game/Overlays/Profile/Header/BottomHeaderContainer.cs +++ b/osu.Game/Overlays/Profile/Header/BottomHeaderContainer.cs @@ -1,4 +1,4 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. using System; @@ -82,7 +82,7 @@ namespace osu.Game.Overlays.Profile.Header else { topLinkContainer.AddText("Joined "); - topLinkContainer.AddText(new DrawableDate(user.JoinDate), embolden); + topLinkContainer.AddText(new DrawableDate(user.JoinDate, italic: false), embolden); } addSpacer(topLinkContainer); @@ -95,7 +95,7 @@ namespace osu.Game.Overlays.Profile.Header else if (user.LastVisit.HasValue) { topLinkContainer.AddText("Last seen "); - topLinkContainer.AddText(new DrawableDate(user.LastVisit.Value), embolden); + topLinkContainer.AddText(new DrawableDate(user.LastVisit.Value, italic: false), embolden); addSpacer(topLinkContainer); } @@ -111,34 +111,42 @@ namespace osu.Game.Overlays.Profile.Header topLinkContainer.AddText("Contributed "); topLinkContainer.AddLink($@"{user.PostCount:#,##0} forum posts", $"https://osu.ppy.sh/users/{user.Id}/posts", creationParameters: embolden); - string websiteWithoutProtcol = user.Website; + string websiteWithoutProtocol = user.Website; - if (!string.IsNullOrEmpty(websiteWithoutProtcol)) + if (!string.IsNullOrEmpty(websiteWithoutProtocol)) { - if (Uri.TryCreate(websiteWithoutProtcol, UriKind.Absolute, out var uri)) + if (Uri.TryCreate(websiteWithoutProtocol, UriKind.Absolute, out var uri)) { - websiteWithoutProtcol = uri.Host + uri.PathAndQuery + uri.Fragment; - websiteWithoutProtcol = websiteWithoutProtcol.TrimEnd('/'); + websiteWithoutProtocol = uri.Host + uri.PathAndQuery + uri.Fragment; + websiteWithoutProtocol = websiteWithoutProtocol.TrimEnd('/'); } } - tryAddInfo(FontAwesome.Solid.MapMarker, user.Location); - tryAddInfo(OsuIcon.Heart, user.Interests); - tryAddInfo(FontAwesome.Solid.Suitcase, user.Occupation); - bottomLinkContainer.NewLine(); + bool anyInfoAdded = false; + + anyInfoAdded |= tryAddInfo(FontAwesome.Solid.MapMarker, user.Location); + anyInfoAdded |= tryAddInfo(OsuIcon.Heart, user.Interests); + anyInfoAdded |= tryAddInfo(FontAwesome.Solid.Suitcase, user.Occupation); + + if (anyInfoAdded) + bottomLinkContainer.NewLine(); + if (!string.IsNullOrEmpty(user.Twitter)) - tryAddInfo(FontAwesome.Brands.Twitter, "@" + user.Twitter, $@"https://twitter.com/{user.Twitter}"); - tryAddInfo(FontAwesome.Brands.Discord, user.Discord); - tryAddInfo(FontAwesome.Brands.Skype, user.Skype, @"skype:" + user.Skype + @"?chat"); - tryAddInfo(FontAwesome.Brands.Lastfm, user.Lastfm, $@"https://last.fm/users/{user.Lastfm}"); - tryAddInfo(FontAwesome.Solid.Link, websiteWithoutProtcol, user.Website); + anyInfoAdded |= tryAddInfo(FontAwesome.Brands.Twitter, "@" + user.Twitter, $@"https://twitter.com/{user.Twitter}"); + anyInfoAdded |= tryAddInfo(FontAwesome.Brands.Discord, user.Discord); + anyInfoAdded |= tryAddInfo(FontAwesome.Brands.Skype, user.Skype, @"skype:" + user.Skype + @"?chat"); + anyInfoAdded |= tryAddInfo(FontAwesome.Brands.Lastfm, user.Lastfm, $@"https://last.fm/users/{user.Lastfm}"); + anyInfoAdded |= tryAddInfo(FontAwesome.Solid.Link, websiteWithoutProtocol, user.Website); + + // If no information was added to the bottomLinkContainer, hide it to avoid unwanted padding + bottomLinkContainer.Alpha = anyInfoAdded ? 1 : 0; } private void addSpacer(OsuTextFlowContainer textFlow) => textFlow.AddArbitraryDrawable(new Container { Width = 15 }); - private void tryAddInfo(IconUsage icon, string content, string link = null) + private bool tryAddInfo(IconUsage icon, string content, string link = null) { - if (string.IsNullOrEmpty(content)) return; + if (string.IsNullOrEmpty(content)) return false; // newlines could be contained in API returned user content. content = content.Replace("\n", " "); @@ -155,6 +163,7 @@ namespace osu.Game.Overlays.Profile.Header bottomLinkContainer.AddText(" " + content, embolden); addSpacer(bottomLinkContainer); + return true; } private void embolden(SpriteText text) => text.Font = text.Font.With(weight: FontWeight.Bold); diff --git a/osu.Game/Overlays/Profile/Header/Components/LevelProgressBar.cs b/osu.Game/Overlays/Profile/Header/Components/LevelProgressBar.cs index a73ce56a2b..c97df3bc4d 100644 --- a/osu.Game/Overlays/Profile/Header/Components/LevelProgressBar.cs +++ b/osu.Game/Overlays/Profile/Header/Components/LevelProgressBar.cs @@ -29,7 +29,7 @@ namespace osu.Game.Overlays.Profile.Header.Components } [BackgroundDependencyLoader] - private void load(OsuColour colours) + private void load(OverlayColourProvider colourProvider) { InternalChildren = new Drawable[] { @@ -42,7 +42,7 @@ namespace osu.Game.Overlays.Profile.Header.Components RelativeSizeAxes = Axes.Both, BackgroundColour = Color4.Black, Direction = BarDirection.LeftToRight, - AccentColour = colours.Yellow + AccentColour = colourProvider.Highlight1 } }, levelProgressText = new OsuSpriteText diff --git a/osu.Game/Overlays/Profile/Header/Components/ProfileHeaderButton.cs b/osu.Game/Overlays/Profile/Header/Components/ProfileHeaderButton.cs index ddcf011277..e14d73dd98 100644 --- a/osu.Game/Overlays/Profile/Header/Components/ProfileHeaderButton.cs +++ b/osu.Game/Overlays/Profile/Header/Components/ProfileHeaderButton.cs @@ -2,12 +2,11 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; +using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; -using osu.Game.Graphics; using osu.Game.Graphics.Containers; -using osuTK.Graphics; namespace osu.Game.Overlays.Profile.Header.Components { @@ -24,9 +23,6 @@ namespace osu.Game.Overlays.Profile.Header.Components { AutoSizeAxes = Axes.X; - IdleColour = Color4.Black; - HoverColour = OsuColour.Gray(0.1f); - base.Content.Add(new CircularContainer { Masking = true, @@ -47,5 +43,12 @@ namespace osu.Game.Overlays.Profile.Header.Components } }); } + + [BackgroundDependencyLoader] + private void load(OverlayColourProvider colourProvider) + { + IdleColour = colourProvider.Background6; + HoverColour = colourProvider.Background5; + } } } diff --git a/osu.Game/Overlays/Profile/Header/Components/ProfileRulesetSelector.cs b/osu.Game/Overlays/Profile/Header/Components/ProfileRulesetSelector.cs index 2c9a3dd5f9..41a3ee8ad6 100644 --- a/osu.Game/Overlays/Profile/Header/Components/ProfileRulesetSelector.cs +++ b/osu.Game/Overlays/Profile/Header/Components/ProfileRulesetSelector.cs @@ -1,63 +1,29 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using osu.Framework.Allocation; using osu.Framework.Bindables; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.UserInterface; -using osu.Game.Graphics; using osu.Game.Rulesets; using osu.Game.Users; -using osuTK; -using osuTK.Graphics; namespace osu.Game.Overlays.Profile.Header.Components { - public class ProfileRulesetSelector : RulesetSelector + public class ProfileRulesetSelector : OverlayRulesetSelector { - private Color4 accentColour = Color4.White; - public readonly Bindable User = new Bindable(); - public ProfileRulesetSelector() - { - TabContainer.Masking = false; - TabContainer.Spacing = new Vector2(10, 0); - AutoSizeAxes = Axes.Both; - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - accentColour = colours.Seafoam; - - foreach (TabItem tabItem in TabContainer) - ((ProfileRulesetTabItem)tabItem).AccentColour = accentColour; - } - protected override void LoadComplete() { base.LoadComplete(); - User.BindValueChanged(u => SetDefaultRuleset(Rulesets.GetRuleset(u.NewValue?.PlayMode ?? "osu")), true); } public void SetDefaultRuleset(RulesetInfo ruleset) { - foreach (TabItem tabItem in TabContainer) + foreach (var tabItem in TabContainer) ((ProfileRulesetTabItem)tabItem).IsDefault = ((ProfileRulesetTabItem)tabItem).Value.ID == ruleset.ID; } - protected override TabItem CreateTabItem(RulesetInfo value) => new ProfileRulesetTabItem(value) - { - AccentColour = accentColour - }; - - protected override TabFillFlowContainer CreateTabFlow() => new TabFillFlowContainer - { - Direction = FillDirection.Horizontal, - AutoSizeAxes = Axes.Both, - }; + protected override TabItem CreateTabItem(RulesetInfo value) => new ProfileRulesetTabItem(value); } } diff --git a/osu.Game/Overlays/Profile/Header/Components/ProfileRulesetTabItem.cs b/osu.Game/Overlays/Profile/Header/Components/ProfileRulesetTabItem.cs index b5170ea3a2..3d20fba542 100644 --- a/osu.Game/Overlays/Profile/Header/Components/ProfileRulesetTabItem.cs +++ b/osu.Game/Overlays/Profile/Header/Components/ProfileRulesetTabItem.cs @@ -2,40 +2,15 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; -using osu.Framework.Graphics.UserInterface; -using osu.Framework.Input.Events; -using osu.Game.Graphics; -using osu.Game.Graphics.Sprites; -using osu.Game.Graphics.UserInterface; using osu.Game.Rulesets; using osuTK; using osuTK.Graphics; namespace osu.Game.Overlays.Profile.Header.Components { - public class ProfileRulesetTabItem : TabItem, IHasAccentColour + public class ProfileRulesetTabItem : OverlayRulesetTabItem { - private readonly OsuSpriteText text; - private readonly SpriteIcon icon; - - private Color4 accentColour; - - public Color4 AccentColour - { - get => accentColour; - set - { - if (accentColour == value) - return; - - accentColour = value; - - updateState(); - } - } - private bool isDefault; public bool IsDefault @@ -52,74 +27,30 @@ namespace osu.Game.Overlays.Profile.Header.Components } } + protected override Color4 AccentColour + { + get => base.AccentColour; + set + { + base.AccentColour = value; + icon.FadeColour(value, 120, Easing.OutQuint); + } + } + + private readonly SpriteIcon icon; + public ProfileRulesetTabItem(RulesetInfo value) : base(value) { - AutoSizeAxes = Axes.Both; - - Children = new Drawable[] + Add(icon = new SpriteIcon { - new FillFlowContainer - { - AutoSizeAxes = Axes.Both, - Origin = Anchor.BottomLeft, - Anchor = Anchor.BottomLeft, - Direction = FillDirection.Horizontal, - Spacing = new Vector2(3, 0), - Children = new Drawable[] - { - text = new OsuSpriteText - { - Origin = Anchor.Centre, - Anchor = Anchor.Centre, - Text = value.Name, - }, - icon = new SpriteIcon - { - Origin = Anchor.Centre, - Anchor = Anchor.Centre, - Alpha = 0, - AlwaysPresent = true, - Icon = FontAwesome.Solid.Star, - Size = new Vector2(12), - }, - } - }, - new HoverClickSounds() - }; - } - - protected override bool OnHover(HoverEvent e) - { - base.OnHover(e); - updateState(); - return true; - } - - protected override void OnHoverLost(HoverLostEvent e) - { - base.OnHoverLost(e); - updateState(); - } - - protected override void OnActivated() => updateState(); - - protected override void OnDeactivated() => updateState(); - - private void updateState() - { - text.Font = text.Font.With(weight: Active.Value ? FontWeight.Bold : FontWeight.Medium); - - if (IsHovered || Active.Value) - { - text.FadeColour(Color4.White, 120, Easing.InQuad); - icon.FadeColour(Color4.White, 120, Easing.InQuad); - } - else - { - text.FadeColour(AccentColour, 120, Easing.InQuad); - icon.FadeColour(AccentColour, 120, Easing.InQuad); - } + Origin = Anchor.Centre, + Anchor = Anchor.Centre, + Alpha = 0, + AlwaysPresent = true, + Icon = FontAwesome.Solid.Star, + Size = new Vector2(12), + }); } } } diff --git a/osu.Game/Overlays/Profile/Header/Components/RankGraph.cs b/osu.Game/Overlays/Profile/Header/Components/RankGraph.cs index 250b345db7..ffc060b3f1 100644 --- a/osu.Game/Overlays/Profile/Header/Components/RankGraph.cs +++ b/osu.Game/Overlays/Profile/Header/Components/RankGraph.cs @@ -167,9 +167,9 @@ namespace osu.Game.Overlays.Profile.Header.Components } [BackgroundDependencyLoader] - private void load(OsuColour colours) + private void load(OverlayColourProvider colourProvider, OsuColour colours) { - ballBg.Colour = colours.GreySeafoamDarker; + ballBg.Colour = colourProvider.Background5; movingBall.BorderColour = line.Colour = colours.Yellow; } @@ -270,7 +270,9 @@ namespace osu.Game.Overlays.Profile.Header.Components [BackgroundDependencyLoader] private void load(OsuColour colours) { - background.Colour = colours.GreySeafoamDark; + // Temporary colour since it's currently impossible to change it without bugs (see https://github.com/ppy/osu-framework/issues/3231) + // If above is fixed, this should use OverlayColourProvider + background.Colour = colours.Gray1; } public bool SetContent(object content) diff --git a/osu.Game/Overlays/Profile/Sections/BeatmapMetadataContainer.cs b/osu.Game/Overlays/Profile/Sections/BeatmapMetadataContainer.cs index 13b547eed3..67a976fe6f 100644 --- a/osu.Game/Overlays/Profile/Sections/BeatmapMetadataContainer.cs +++ b/osu.Game/Overlays/Profile/Sections/BeatmapMetadataContainer.cs @@ -10,7 +10,7 @@ using osu.Game.Graphics.Containers; namespace osu.Game.Overlays.Profile.Sections { /// - /// Display artist/title/mapper information, commonly used as the left portion of a profile or score display row (see ). + /// Display artist/title/mapper information, commonly used as the left portion of a profile or score display row. /// public abstract class BeatmapMetadataContainer : OsuHoverContainer { diff --git a/osu.Game/Overlays/Profile/Sections/DrawableProfileRow.cs b/osu.Game/Overlays/Profile/Sections/DrawableProfileRow.cs deleted file mode 100644 index 03ee29d0c2..0000000000 --- a/osu.Game/Overlays/Profile/Sections/DrawableProfileRow.cs +++ /dev/null @@ -1,124 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using osu.Framework.Allocation; -using osu.Framework.Extensions.Color4Extensions; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Effects; -using osu.Framework.Graphics.Shapes; -using osu.Framework.Input.Events; -using osu.Game.Graphics; -using osuTK; -using osuTK.Graphics; - -namespace osu.Game.Overlays.Profile.Sections -{ - public abstract class DrawableProfileRow : Container - { - private const int fade_duration = 200; - - private Box underscoreLine; - private Box coloredBackground; - private Container background; - - /// - /// A visual element displayed to the left of content. - /// - protected abstract Drawable CreateLeftVisual(); - - protected FillFlowContainer LeftFlowContainer { get; private set; } - protected FillFlowContainer RightFlowContainer { get; private set; } - - protected override Container Content { get; } - - protected DrawableProfileRow() - { - RelativeSizeAxes = Axes.X; - Height = 60; - - Content = new Container - { - RelativeSizeAxes = Axes.Both, - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - Width = 0.97f, - }; - } - - [BackgroundDependencyLoader(true)] - private void load(OsuColour colour) - { - InternalChildren = new Drawable[] - { - background = new Container - { - RelativeSizeAxes = Axes.Both, - Masking = true, - CornerRadius = 3, - Alpha = 0, - EdgeEffect = new EdgeEffectParameters - { - Type = EdgeEffectType.Shadow, - Offset = new Vector2(0f, 1f), - Radius = 1f, - Colour = Color4.Black.Opacity(0.2f), - }, - Child = coloredBackground = new Box { RelativeSizeAxes = Axes.Both } - }, - Content, - underscoreLine = new Box - { - Anchor = Anchor.BottomCentre, - Origin = Anchor.BottomCentre, - RelativeSizeAxes = Axes.X, - Height = 1, - }, - new FillFlowContainer - { - RelativeSizeAxes = Axes.Both, - Direction = FillDirection.Horizontal, - Children = new[] - { - CreateLeftVisual(), - LeftFlowContainer = new FillFlowContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Margin = new MarginPadding { Left = 10 }, - Direction = FillDirection.Vertical, - }, - } - }, - RightFlowContainer = new FillFlowContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Anchor = Anchor.CentreRight, - Origin = Anchor.CentreRight, - Direction = FillDirection.Vertical, - }, - }; - - coloredBackground.Colour = underscoreLine.Colour = colour.Gray4; - } - - protected override bool OnClick(ClickEvent e) => true; - - protected override bool OnHover(HoverEvent e) - { - background.FadeIn(fade_duration, Easing.OutQuint); - underscoreLine.FadeOut(fade_duration, Easing.OutQuint); - return true; - } - - protected override void OnHoverLost(HoverLostEvent e) - { - background.FadeOut(fade_duration, Easing.OutQuint); - underscoreLine.FadeIn(fade_duration, Easing.OutQuint); - base.OnHoverLost(e); - } - } -} diff --git a/osu.Game/Overlays/Profile/Sections/ProfileItemContainer.cs b/osu.Game/Overlays/Profile/Sections/ProfileItemContainer.cs index 717ec4fb1a..f65c909155 100644 --- a/osu.Game/Overlays/Profile/Sections/ProfileItemContainer.cs +++ b/osu.Game/Overlays/Profile/Sections/ProfileItemContainer.cs @@ -44,8 +44,8 @@ namespace osu.Game.Overlays.Profile.Sections [BackgroundDependencyLoader] private void load(OverlayColourProvider colourProvider) { - background.Colour = idleColour = colourProvider.Background4; - hoverColour = colourProvider.Background3; + background.Colour = idleColour = colourProvider.Background3; + hoverColour = colourProvider.Background2; } protected override bool OnHover(HoverEvent e) diff --git a/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileScore.cs b/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileScore.cs index 5196bef48d..c9f787bb26 100644 --- a/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileScore.cs +++ b/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileScore.cs @@ -1,10 +1,10 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using System.Linq; using JetBrains.Annotations; using osu.Framework.Allocation; -using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; @@ -16,14 +16,16 @@ using osu.Game.Online.Leaderboards; using osu.Game.Rulesets.UI; using osu.Game.Scoring; using osuTK; -using osuTK.Graphics; namespace osu.Game.Overlays.Profile.Sections.Ranks { public class DrawableProfileScore : CompositeDrawable { + private const int height = 40; private const int performance_width = 80; - private const int content_padding = 10; + + private const float performance_background_shear = 0.45f; + private static readonly float performance_background_width = performance_width + (height / 4f * MathF.Tan(performance_background_shear)); protected readonly ScoreInfo Score; @@ -38,7 +40,7 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks Score = score; RelativeSizeAxes = Axes.X; - Height = 40; + Height = height; } [BackgroundDependencyLoader] @@ -51,7 +53,7 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks new Container { RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding { Left = content_padding, Right = performance_width + content_padding }, + Padding = new MarginPadding { Left = 10, Right = performance_width + 30 }, Children = new Drawable[] { new FillFlowContainer @@ -142,20 +144,26 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks { new Box { - RelativeSizeAxes = Axes.Both, - Size = new Vector2(1, 0.5f), - Colour = Color4.Black.Opacity(0.5f), - Shear = new Vector2(-0.45f, 0), + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + RelativeSizeAxes = Axes.Y, + Width = performance_background_width, + Height = 0.5f, + Colour = colourProvider.Background4, + Shear = new Vector2(-performance_background_shear, 0), EdgeSmoothness = new Vector2(2, 0), }, new Box { - RelativeSizeAxes = Axes.Both, + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + RelativeSizeAxes = Axes.Y, RelativePositionAxes = Axes.Y, - Size = new Vector2(1, -0.5f), + Width = performance_background_width, + Height = -0.5f, Position = new Vector2(0, 1), - Colour = Color4.Black.Opacity(0.5f), - Shear = new Vector2(0.45f, 0), + Colour = colourProvider.Background4, + Shear = new Vector2(performance_background_shear, 0), EdgeSmoothness = new Vector2(2, 0), }, createDrawablePerformance().With(d => diff --git a/osu.Game/Overlays/Profile/Sections/Recent/DrawableRecentActivity.cs b/osu.Game/Overlays/Profile/Sections/Recent/DrawableRecentActivity.cs index 4e856845ac..8782e82642 100644 --- a/osu.Game/Overlays/Profile/Sections/Recent/DrawableRecentActivity.cs +++ b/osu.Game/Overlays/Profile/Sections/Recent/DrawableRecentActivity.cs @@ -1,9 +1,11 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Linq; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Online.API; @@ -11,12 +13,19 @@ using osu.Game.Online.API.Requests; using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Chat; using osu.Game.Online.Leaderboards; +using osu.Game.Rulesets; namespace osu.Game.Overlays.Profile.Sections.Recent { - public class DrawableRecentActivity : DrawableProfileRow + public class DrawableRecentActivity : CompositeDrawable { - private IAPIProvider api; + private const int font_size = 14; + + [Resolved] + private IAPIProvider api { get; set; } + + [Resolved] + private RulesetStore rulesets { get; set; } private readonly APIRecentActivity activity; @@ -28,139 +37,191 @@ namespace osu.Game.Overlays.Profile.Sections.Recent } [BackgroundDependencyLoader] - private void load(IAPIProvider api) + private void load(OverlayColourProvider colourProvider) { - this.api = api; - - LeftFlowContainer.Padding = new MarginPadding { Left = 10, Right = 160 }; - - LeftFlowContainer.Add(content = new LinkFlowContainer + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + AddInternal(new GridContainer { - AutoSizeAxes = Axes.Y, RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + ColumnDimensions = new[] + { + new Dimension(GridSizeMode.Absolute, size: 28), + new Dimension(), + new Dimension(GridSizeMode.AutoSize) + }, + RowDimensions = new[] + { + new Dimension(GridSizeMode.AutoSize) + }, + Content = new[] + { + new Drawable[] + { + new Container + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Child = createIcon().With(icon => + { + icon.Anchor = Anchor.Centre; + icon.Origin = Anchor.Centre; + }) + }, + content = new LinkFlowContainer(t => t.Font = OsuFont.GetFont(size: font_size)) + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + AutoSizeAxes = Axes.Y, + RelativeSizeAxes = Axes.X, + }, + new DrawableDate(activity.CreatedAt) + { + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + Colour = colourProvider.Foreground1, + Font = OsuFont.GetFont(size: font_size), + } + } + } }); - RightFlowContainer.Add(new DrawableDate(activity.CreatedAt) - { - Font = OsuFont.GetFont(size: 13), - Colour = OsuColour.Gray(0xAA), - Anchor = Anchor.TopRight, - Origin = Anchor.TopRight, - }); - - var formatted = createMessage(); - - content.AddLinks(formatted.Text, formatted.Links); + createMessage(); } - protected override Drawable CreateLeftVisual() + private Drawable createIcon() { switch (activity.Type) { case RecentActivityType.Rank: return new UpdateableRank(activity.ScoreRank) { - RelativeSizeAxes = Axes.Y, - Width = 60, + RelativeSizeAxes = Axes.X, + Height = 11, FillMode = FillMode.Fit, + Margin = new MarginPadding { Top = 2 } }; case RecentActivityType.Achievement: return new DelayedLoadWrapper(new MedalIcon(activity.Achievement.Slug) { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, RelativeSizeAxes = Axes.Both, FillMode = FillMode.Fit, }) { - RelativeSizeAxes = Axes.Y, - Width = 60, + RelativeSizeAxes = Axes.X, + Width = 0.5f, + Height = 18 }; default: - return new Container - { - RelativeSizeAxes = Axes.Y, - Width = 60, - FillMode = FillMode.Fit, - }; + return Empty(); } } - private string toAbsoluteUrl(string url) => $"{api.Endpoint}{url}"; - - private MessageFormatter.MessageFormatterResult createMessage() + private void createMessage() { - string userLinkTemplate() => $"[{toAbsoluteUrl(activity.User?.Url)} {activity.User?.Username}]"; - string beatmapLinkTemplate() => $"[{toAbsoluteUrl(activity.Beatmap?.Url)} {activity.Beatmap?.Title}]"; - string beatmapsetLinkTemplate() => $"[{toAbsoluteUrl(activity.Beatmapset?.Url)} {activity.Beatmapset?.Title}]"; - - string message; - switch (activity.Type) { case RecentActivityType.Achievement: - message = $"{userLinkTemplate()} unlocked the {activity.Achievement.Name} medal!"; + addUserLink(); + addText($" unlocked the \"{activity.Achievement.Name}\" medal!"); break; case RecentActivityType.BeatmapPlaycount: - message = $"{beatmapLinkTemplate()} has been played {activity.Count} times!"; + addBeatmapLink(); + addText($" has been played {activity.Count} times!"); break; case RecentActivityType.BeatmapsetApprove: - message = $"{beatmapsetLinkTemplate()} has been {activity.Approval.ToString().ToLowerInvariant()}!"; + addBeatmapsetLink(); + addText($" has been {activity.Approval.ToString().ToLowerInvariant()}!"); break; case RecentActivityType.BeatmapsetDelete: - message = $"{beatmapsetLinkTemplate()} has been deleted."; + addBeatmapsetLink(); + addText(" has been deleted."); break; case RecentActivityType.BeatmapsetRevive: - message = $"{beatmapsetLinkTemplate()} has been revived from eternal slumber by {userLinkTemplate()}."; + addBeatmapsetLink(); + addText(" has been revived from eternal slumber by "); + addUserLink(); break; case RecentActivityType.BeatmapsetUpdate: - message = $"{userLinkTemplate()} has updated the beatmap {beatmapsetLinkTemplate()}!"; + addUserLink(); + addText(" has updated the beatmap "); + addBeatmapsetLink(); break; case RecentActivityType.BeatmapsetUpload: - message = $"{userLinkTemplate()} has submitted a new beatmap {beatmapsetLinkTemplate()}!"; + addUserLink(); + addText(" has submitted a new beatmap "); + addBeatmapsetLink(); break; case RecentActivityType.Medal: // apparently this shouldn't exist look at achievement instead (https://github.com/ppy/osu-web/blob/master/resources/assets/coffee/react/profile-page/recent-activity.coffee#L111) - message = string.Empty; break; case RecentActivityType.Rank: - message = $"{userLinkTemplate()} achieved rank #{activity.Rank} on {beatmapLinkTemplate()} ({activity.Mode}!)"; + addUserLink(); + addText($" achieved rank #{activity.Rank} on "); + addBeatmapLink(); + addText($" ({getRulesetName()})"); break; case RecentActivityType.RankLost: - message = $"{userLinkTemplate()} has lost first place on {beatmapLinkTemplate()} ({activity.Mode}!)"; + addUserLink(); + addText(" has lost first place on "); + addBeatmapLink(); + addText($" ({getRulesetName()})"); break; case RecentActivityType.UserSupportAgain: - message = $"{userLinkTemplate()} has once again chosen to support osu! - thanks for your generosity!"; + addUserLink(); + addText(" has once again chosen to support osu! - thanks for your generosity!"); break; case RecentActivityType.UserSupportFirst: - message = $"{userLinkTemplate()} has become an osu!supporter - thanks for your generosity!"; + addUserLink(); + addText(" has become an osu!supporter - thanks for your generosity!"); break; case RecentActivityType.UserSupportGift: - message = $"{userLinkTemplate()} has received the gift of osu!supporter!"; + addUserLink(); + addText(" has received the gift of osu!supporter!"); break; case RecentActivityType.UsernameChange: - message = $"{activity.User?.PreviousUsername} has changed their username to {userLinkTemplate()}!"; - break; - - default: - message = string.Empty; + addText($"{activity.User?.PreviousUsername} has changed their username to "); + addUserLink(); break; } - - return MessageFormatter.FormatText(message); } + + private string getRulesetName() => + rulesets.AvailableRulesets.FirstOrDefault(r => r.ShortName == activity.Mode)?.Name ?? activity.Mode; + + private void addUserLink() + => content.AddLink(activity.User?.Username, LinkAction.OpenUserProfile, getLinkArgument(activity.User?.Url), creationParameters: t => t.Font = getLinkFont(FontWeight.Bold)); + + private void addBeatmapLink() + => content.AddLink(activity.Beatmap?.Title, LinkAction.OpenBeatmap, getLinkArgument(activity.Beatmap?.Url), creationParameters: t => t.Font = getLinkFont()); + + private void addBeatmapsetLink() + => content.AddLink(activity.Beatmapset?.Title, LinkAction.OpenBeatmapSet, getLinkArgument(activity.Beatmapset?.Url), creationParameters: t => t.Font = getLinkFont()); + + private string getLinkArgument(string url) => MessageFormatter.GetLinkDetails($"{api.Endpoint}{url}").Argument; + + private FontUsage getLinkFont(FontWeight fontWeight = FontWeight.Regular) + => OsuFont.GetFont(size: font_size, weight: fontWeight, italics: true); + + private void addText(string text) + => content.AddText(text, t => t.Font = OsuFont.GetFont(size: font_size, weight: FontWeight.SemiBold)); } } diff --git a/osu.Game/Overlays/Profile/Sections/Recent/MedalIcon.cs b/osu.Game/Overlays/Profile/Sections/Recent/MedalIcon.cs index 4563510046..0c1f8b2e92 100644 --- a/osu.Game/Overlays/Profile/Sections/Recent/MedalIcon.cs +++ b/osu.Game/Overlays/Profile/Sections/Recent/MedalIcon.cs @@ -23,8 +23,7 @@ namespace osu.Game.Overlays.Profile.Sections.Recent Child = sprite = new Sprite { - Height = 40, - Width = 40, + RelativeSizeAxes = Axes.Both, Anchor = Anchor.Centre, Origin = Anchor.Centre, }; diff --git a/osu.Game/Overlays/Profile/Sections/Recent/PaginatedRecentActivityContainer.cs b/osu.Game/Overlays/Profile/Sections/Recent/PaginatedRecentActivityContainer.cs index 3f9d4dc93e..a37f398272 100644 --- a/osu.Game/Overlays/Profile/Sections/Recent/PaginatedRecentActivityContainer.cs +++ b/osu.Game/Overlays/Profile/Sections/Recent/PaginatedRecentActivityContainer.cs @@ -8,6 +8,7 @@ using osu.Framework.Bindables; using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.API; using System.Collections.Generic; +using osuTK; namespace osu.Game.Overlays.Profile.Sections.Recent { @@ -16,7 +17,8 @@ namespace osu.Game.Overlays.Profile.Sections.Recent public PaginatedRecentActivityContainer(Bindable user, string header, string missing) : base(user, header, missing) { - ItemsPerPage = 5; + ItemsPerPage = 10; + ItemsContainer.Spacing = new Vector2(0, 8); } protected override APIRequest> CreateRequest() => diff --git a/osu.Game/Overlays/Toolbar/ToolbarRulesetSelector.cs b/osu.Game/Overlays/Toolbar/ToolbarRulesetSelector.cs index 8f2dbce6f7..422bf00c6d 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarRulesetSelector.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarRulesetSelector.cs @@ -99,7 +99,7 @@ namespace osu.Game.Overlays.Toolbar { int requested = e.Key - Key.Number1; - RulesetInfo found = Rulesets.AvailableRulesets.Skip(requested).FirstOrDefault(); + RulesetInfo found = Rulesets.AvailableRulesets.ElementAtOrDefault(requested); if (found != null) Current.Value = found; return true; diff --git a/osu.Game/Overlays/UserProfileOverlay.cs b/osu.Game/Overlays/UserProfileOverlay.cs index 6f0d96c226..045a52a0c7 100644 --- a/osu.Game/Overlays/UserProfileOverlay.cs +++ b/osu.Game/Overlays/UserProfileOverlay.cs @@ -67,7 +67,7 @@ namespace osu.Game.Overlays RelativeSizeAxes = Axes.X, Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, - Height = 30 + Height = 34 }; Add(new Box diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index ab8dccb9dd..5ac1d4ecc4 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -154,7 +154,7 @@ namespace osu.Game.Rulesets.Edit { if (e.Key >= Key.Number1 && e.Key <= Key.Number9) { - var item = toolboxCollection.Items.Skip(e.Key - Key.Number1).FirstOrDefault(); + var item = toolboxCollection.Items.ElementAtOrDefault(e.Key - Key.Number1); if (item != null) { diff --git a/osu.Game/Rulesets/Objects/HitObject.cs b/osu.Game/Rulesets/Objects/HitObject.cs index bd96441ebb..c09844e0c6 100644 --- a/osu.Game/Rulesets/Objects/HitObject.cs +++ b/osu.Game/Rulesets/Objects/HitObject.cs @@ -34,7 +34,7 @@ namespace osu.Game.Rulesets.Objects /// public event Action DefaultsApplied; - public readonly Bindable StartTimeBindable = new Bindable(); + public readonly Bindable StartTimeBindable = new BindableDouble(); /// /// The time at which the HitObject starts. diff --git a/osu.Game/Screens/Backgrounds/BackgroundScreenBeatmap.cs b/osu.Game/Screens/Backgrounds/BackgroundScreenBeatmap.cs index 3ced9ee753..50fd127093 100644 --- a/osu.Game/Screens/Backgrounds/BackgroundScreenBeatmap.cs +++ b/osu.Game/Screens/Backgrounds/BackgroundScreenBeatmap.cs @@ -36,7 +36,7 @@ namespace osu.Game.Screens.Backgrounds /// /// The amount of blur to be applied in addition to user-specified blur. /// - public readonly Bindable BlurAmount = new Bindable(); + public readonly Bindable BlurAmount = new BindableFloat(); internal readonly IBindable IsBreakTime = new Bindable(); @@ -119,7 +119,7 @@ namespace osu.Game.Screens.Backgrounds /// /// Used in contexts where there can potentially be both user and screen-specified blurring occuring at the same time, such as in /// - public readonly Bindable BlurAmount = new Bindable(); + public readonly Bindable BlurAmount = new BindableFloat(); public Background Background { diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineTickDisplay.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineTickDisplay.cs index f9b92c0504..36ee976bf7 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineTickDisplay.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineTickDisplay.cs @@ -44,7 +44,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline for (var i = 0; i < beatmap.ControlPointInfo.TimingPoints.Count; i++) { var point = beatmap.ControlPointInfo.TimingPoints[i]; - var until = beatmap.ControlPointInfo.TimingPoints.Count < i + 1 ? beatmap.ControlPointInfo.TimingPoints[i + 1].Time : working.Value.Track.Length; + var until = i + 1 < beatmap.ControlPointInfo.TimingPoints.Count ? beatmap.ControlPointInfo.TimingPoints[i + 1].Time : working.Value.Track.Length; int beat = 0; diff --git a/osu.Game/Screens/Select/Details/AdvancedStats.cs b/osu.Game/Screens/Select/Details/AdvancedStats.cs index 56c400e869..7ab91677a9 100644 --- a/osu.Game/Screens/Select/Details/AdvancedStats.cs +++ b/osu.Game/Screens/Select/Details/AdvancedStats.cs @@ -16,6 +16,7 @@ using System.Collections.Generic; using osu.Game.Rulesets.Mods; using System.Linq; using osu.Framework.Threading; +using osu.Framework.Utils; using osu.Game.Configuration; using osu.Game.Overlays.Settings; @@ -26,7 +27,8 @@ namespace osu.Game.Screens.Select.Details [Resolved] private IBindable> mods { get; set; } - private readonly StatisticRow firstValue, hpDrain, accuracy, approachRate, starDifficulty; + protected readonly StatisticRow FirstValue, HpDrain, Accuracy, ApproachRate; + private readonly StatisticRow starDifficulty; private BeatmapInfo beatmap; @@ -52,10 +54,10 @@ namespace osu.Game.Screens.Select.Details Spacing = new Vector2(4f), Children = new[] { - firstValue = new StatisticRow(), //circle size/key amount - hpDrain = new StatisticRow { Title = "HP Drain" }, - accuracy = new StatisticRow { Title = "Accuracy" }, - approachRate = new StatisticRow { Title = "Approach Rate" }, + FirstValue = new StatisticRow(), //circle size/key amount + HpDrain = new StatisticRow { Title = "HP Drain" }, + Accuracy = new StatisticRow { Title = "Accuracy" }, + ApproachRate = new StatisticRow { Title = "Approach Rate" }, starDifficulty = new StatisticRow(10, true) { Title = "Star Difficulty" }, }, }; @@ -122,24 +124,24 @@ namespace osu.Game.Screens.Select.Details case 3: // Account for mania differences locally for now // Eventually this should be handled in a more modular way, allowing rulesets to return arbitrary difficulty attributes - firstValue.Title = "Key Count"; - firstValue.Value = (baseDifficulty?.CircleSize ?? 0, null); + FirstValue.Title = "Key Count"; + FirstValue.Value = (baseDifficulty?.CircleSize ?? 0, null); break; default: - firstValue.Title = "Circle Size"; - firstValue.Value = (baseDifficulty?.CircleSize ?? 0, adjustedDifficulty?.CircleSize); + FirstValue.Title = "Circle Size"; + FirstValue.Value = (baseDifficulty?.CircleSize ?? 0, adjustedDifficulty?.CircleSize); break; } starDifficulty.Value = ((float)(Beatmap?.StarDifficulty ?? 0), null); - hpDrain.Value = (baseDifficulty?.DrainRate ?? 0, adjustedDifficulty?.DrainRate); - accuracy.Value = (baseDifficulty?.OverallDifficulty ?? 0, adjustedDifficulty?.OverallDifficulty); - approachRate.Value = (baseDifficulty?.ApproachRate ?? 0, adjustedDifficulty?.ApproachRate); + HpDrain.Value = (baseDifficulty?.DrainRate ?? 0, adjustedDifficulty?.DrainRate); + Accuracy.Value = (baseDifficulty?.OverallDifficulty ?? 0, adjustedDifficulty?.OverallDifficulty); + ApproachRate.Value = (baseDifficulty?.ApproachRate ?? 0, adjustedDifficulty?.ApproachRate); } - private class StatisticRow : Container, IHasAccentColour + public class StatisticRow : Container, IHasAccentColour { private const float value_width = 25; private const float name_width = 70; @@ -147,7 +149,8 @@ namespace osu.Game.Screens.Select.Details private readonly float maxValue; private readonly bool forceDecimalPlaces; private readonly OsuSpriteText name, valueText; - private readonly Bar bar, modBar; + private readonly Bar bar; + public readonly Bar ModBar; [Resolved] private OsuColour colours { get; set; } @@ -173,14 +176,14 @@ namespace osu.Game.Screens.Select.Details bar.Length = value.baseValue / maxValue; valueText.Text = (value.adjustedValue ?? value.baseValue).ToString(forceDecimalPlaces ? "0.00" : "0.##"); - modBar.Length = (value.adjustedValue ?? 0) / maxValue; + ModBar.Length = (value.adjustedValue ?? 0) / maxValue; - if (value.adjustedValue > value.baseValue) - modBar.AccentColour = valueText.Colour = colours.Red; + if (Precision.AlmostEquals(value.baseValue, value.adjustedValue ?? value.baseValue, 0.05f)) + ModBar.AccentColour = valueText.Colour = Color4.White; + else if (value.adjustedValue > value.baseValue) + ModBar.AccentColour = valueText.Colour = colours.Red; else if (value.adjustedValue < value.baseValue) - modBar.AccentColour = valueText.Colour = colours.BlueDark; - else - modBar.AccentColour = valueText.Colour = Color4.White; + ModBar.AccentColour = valueText.Colour = colours.BlueDark; } } @@ -217,7 +220,7 @@ namespace osu.Game.Screens.Select.Details BackgroundColour = Color4.White.Opacity(0.5f), Padding = new MarginPadding { Left = name_width + 10, Right = value_width + 10 }, }, - modBar = new Bar + ModBar = new Bar { Origin = Anchor.CentreLeft, Anchor = Anchor.CentreLeft, diff --git a/osu.Game/Screens/Select/FilterControl.cs b/osu.Game/Screens/Select/FilterControl.cs index c851b201d7..6a03cfb68e 100644 --- a/osu.Game/Screens/Select/FilterControl.cs +++ b/osu.Game/Screens/Select/FilterControl.cs @@ -149,8 +149,8 @@ namespace osu.Game.Screens.Select private readonly IBindable ruleset = new Bindable(); private readonly Bindable showConverted = new Bindable(); - private readonly Bindable minimumStars = new Bindable(); - private readonly Bindable maximumStars = new Bindable(); + private readonly Bindable minimumStars = new BindableDouble(); + private readonly Bindable maximumStars = new BindableDouble(); public readonly Box Background; diff --git a/osu.Game/Screens/Select/PlaySongSelect.cs b/osu.Game/Screens/Select/PlaySongSelect.cs index 9368bac69f..18f13c5e1d 100644 --- a/osu.Game/Screens/Select/PlaySongSelect.cs +++ b/osu.Game/Screens/Select/PlaySongSelect.cs @@ -33,6 +33,8 @@ namespace osu.Game.Screens.Select public override void OnResuming(IScreen last) { + base.OnResuming(last); + player = null; if (removeAutoModOnResume) @@ -41,8 +43,6 @@ namespace osu.Game.Screens.Select ModSelect.DeselectTypes(new[] { autoType }, true); removeAutoModOnResume = false; } - - base.OnResuming(last); } protected override bool OnStart() diff --git a/osu.Game/Tests/Visual/ScrollingTestContainer.cs b/osu.Game/Tests/Visual/ScrollingTestContainer.cs index bdad3d278c..161ebe7030 100644 --- a/osu.Game/Tests/Visual/ScrollingTestContainer.cs +++ b/osu.Game/Tests/Visual/ScrollingTestContainer.cs @@ -47,7 +47,7 @@ namespace osu.Game.Tests.Visual public readonly Bindable Direction = new Bindable(); IBindable IScrollingInfo.Direction => Direction; - public readonly Bindable TimeRange = new Bindable(1000) { Value = 1000 }; + public readonly Bindable TimeRange = new BindableDouble(1000) { Value = 1000 }; IBindable IScrollingInfo.TimeRange => TimeRange; public readonly TestScrollAlgorithm Algorithm = new TestScrollAlgorithm();