From 38dcd42d0891fd3c2961277a32661ab7f4d2a974 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Thu, 17 Oct 2019 12:35:12 +0300 Subject: [PATCH 01/28] Parse voted comments --- .../Online/API/Requests/Responses/Comment.cs | 2 ++ .../API/Requests/Responses/CommentBundle.cs | 20 ++++++++++++++++ osu.Game/Overlays/Comments/DrawableComment.cs | 24 ++++++++++++++----- 3 files changed, 40 insertions(+), 6 deletions(-) diff --git a/osu.Game/Online/API/Requests/Responses/Comment.cs b/osu.Game/Online/API/Requests/Responses/Comment.cs index 29abaa74e5..5510e9afff 100644 --- a/osu.Game/Online/API/Requests/Responses/Comment.cs +++ b/osu.Game/Online/API/Requests/Responses/Comment.cs @@ -72,6 +72,8 @@ namespace osu.Game.Online.API.Requests.Responses public bool HasMessage => !string.IsNullOrEmpty(MessageHtml); + 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 7063581605..f910c738ac 100644 --- a/osu.Game/Online/API/Requests/Responses/CommentBundle.cs +++ b/osu.Game/Online/API/Requests/Responses/CommentBundle.cs @@ -47,6 +47,26 @@ namespace osu.Game.Online.API.Requests.Responses [JsonProperty(@"included_comments")] public List IncludedComments { get; set; } + private List userVotes; + + [JsonProperty(@"user_votes")] + public List UserVotes + { + get => userVotes; + set + { + userVotes = value; + value.ForEach(v => + { + Comments.ForEach(c => + { + if (v == c.Id) + c.IsVoted = true; + }); + }); + } + } + private List users; [JsonProperty(@"users")] diff --git a/osu.Game/Overlays/Comments/DrawableComment.cs b/osu.Game/Overlays/Comments/DrawableComment.cs index 89abda92cf..3e9c6a5eca 100644 --- a/osu.Game/Overlays/Comments/DrawableComment.cs +++ b/osu.Game/Overlays/Comments/DrawableComment.cs @@ -15,6 +15,8 @@ using osu.Framework.Bindables; using osu.Framework.Graphics.Shapes; using System.Linq; using osu.Game.Online.Chat; +using osu.Framework.Allocation; +using osu.Game.Graphics.Sprites; namespace osu.Game.Overlays.Comments { @@ -81,7 +83,7 @@ namespace osu.Game.Overlays.Comments Spacing = new Vector2(5, 0), Children = new Drawable[] { - votePill = new VotePill(comment.VotesCount) + votePill = new VotePill(comment) { Anchor = Anchor.Centre, Origin = Anchor.Centre, @@ -336,28 +338,38 @@ namespace osu.Game.Overlays.Comments private class VotePill : CircularContainer { - public VotePill(int count) + private readonly Box background; + private readonly Comment comment; + + public VotePill(Comment comment) { + this.comment = comment; + AutoSizeAxes = Axes.X; Height = 20; Masking = true; Children = new Drawable[] { - new Box + background = new Box { RelativeSizeAxes = Axes.Both, - Colour = OsuColour.Gray(0.05f) }, - new SpriteText + new OsuSpriteText { Anchor = Anchor.Centre, Origin = Anchor.Centre, Margin = new MarginPadding { Horizontal = margin }, Font = OsuFont.GetFont(size: 14), - Text = $"+{count}" + Text = $"+{comment.VotesCount}" } }; } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + background.Colour = comment.IsVoted ? colours.GreenLight : OsuColour.Gray(0.05f); + } } } } From 1f28c00594daa535678f651a5d16501347653648 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Thu, 17 Oct 2019 13:10:28 +0300 Subject: [PATCH 02/28] UI implementation --- osu.Game/Overlays/Comments/DrawableComment.cs | 68 +++++++++++++++++-- 1 file changed, 62 insertions(+), 6 deletions(-) diff --git a/osu.Game/Overlays/Comments/DrawableComment.cs b/osu.Game/Overlays/Comments/DrawableComment.cs index 3e9c6a5eca..d1cae6592a 100644 --- a/osu.Game/Overlays/Comments/DrawableComment.cs +++ b/osu.Game/Overlays/Comments/DrawableComment.cs @@ -17,6 +17,10 @@ using System.Linq; using osu.Game.Online.Chat; using osu.Framework.Allocation; using osu.Game.Graphics.Sprites; +using osuTK.Graphics; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Input.Events; +using osu.Game.Graphics.UserInterface; namespace osu.Game.Overlays.Comments { @@ -57,7 +61,7 @@ namespace osu.Game.Overlays.Comments { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, - Padding = new MarginPadding(margin), + Padding = new MarginPadding(margin) { Left = margin + 5 }, Child = content = new GridContainer { RelativeSizeAxes = Axes.X, @@ -336,10 +340,15 @@ namespace osu.Game.Overlays.Comments } } - private class VotePill : CircularContainer + private class VotePill : Container, IHasAccentColour { + public Color4 AccentColour { get; set; } + private readonly Box background; + private readonly Box hoverLayer; private readonly Comment comment; + private readonly CircularContainer borderContainer; + private readonly SpriteText sideNumber; public VotePill(Comment comment) { @@ -347,12 +356,24 @@ namespace osu.Game.Overlays.Comments AutoSizeAxes = Axes.X; Height = 20; - Masking = true; Children = new Drawable[] { - background = new Box + borderContainer = new CircularContainer { RelativeSizeAxes = Axes.Both, + Masking = true, + Children = new Drawable[] + { + background = new Box + { + RelativeSizeAxes = Axes.Both + }, + hoverLayer = new Box + { + RelativeSizeAxes = Axes.Both, + Alpha = 0 + } + } }, new OsuSpriteText { @@ -361,14 +382,49 @@ namespace osu.Game.Overlays.Comments Margin = new MarginPadding { Horizontal = margin }, Font = OsuFont.GetFont(size: 14), Text = $"+{comment.VotesCount}" - } + }, + sideNumber = new SpriteText + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreRight, + Text = "+1", + Font = OsuFont.GetFont(size: 14), + Margin = new MarginPadding { Right = 3 }, + Alpha = 0, + }, + new HoverClickSounds(), }; } [BackgroundDependencyLoader] private void load(OsuColour colours) { - background.Colour = comment.IsVoted ? colours.GreenLight : OsuColour.Gray(0.05f); + AccentColour = borderContainer.BorderColour = sideNumber.Colour = colours.GreenLight; + background.Colour = comment.IsVoted ? AccentColour : OsuColour.Gray(0.05f); + hoverLayer.Colour = Color4.Black.Opacity(0.5f); + } + + protected override bool OnHover(HoverEvent e) + { + if (comment.IsVoted) + hoverLayer.Show(); + else + sideNumber.Show(); + + borderContainer.BorderThickness = 3; + return base.OnHover(e); + } + + protected override void OnHoverLost(HoverLostEvent e) + { + base.OnHoverLost(e); + + if (comment.IsVoted) + hoverLayer.Hide(); + else + sideNumber.Hide(); + + borderContainer.BorderThickness = 0; } } } From d3a8dfd5ff6287ba616b31030a9e5c643cffb5ad Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Thu, 17 Oct 2019 13:57:17 +0300 Subject: [PATCH 03/28] Implement LoadingButton component --- .../Graphics/UserInterface/LoadingButton.cs | 96 +++++++++++++ .../Graphics/UserInterface/ShowMoreButton.cs | 129 ++++++------------ 2 files changed, 135 insertions(+), 90 deletions(-) create mode 100644 osu.Game/Graphics/UserInterface/LoadingButton.cs diff --git a/osu.Game/Graphics/UserInterface/LoadingButton.cs b/osu.Game/Graphics/UserInterface/LoadingButton.cs new file mode 100644 index 0000000000..1557a90c4a --- /dev/null +++ b/osu.Game/Graphics/UserInterface/LoadingButton.cs @@ -0,0 +1,96 @@ +// 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.Input.Events; +using osu.Game.Graphics.Containers; +using osuTK; + +namespace osu.Game.Graphics.UserInterface +{ + public abstract class LoadingButton : OsuHoverContainer + { + private const float fade_duration = 200; + + private bool isLoading; + + public bool IsLoading + { + get => isLoading; + set + { + isLoading = value; + + Enabled.Value = !isLoading; + + if (value) + { + loading.Show(); + content.FadeOut(fade_duration, Easing.OutQuint); + OnLoadingStart(); + } + else + { + loading.Hide(); + content.FadeIn(fade_duration, Easing.OutQuint); + OnLoadingFinished(); + } + } + } + + public Vector2 LoadingAnimationSize + { + get => loading.Size; + set => loading.Size = value; + } + + private readonly Container background; + private readonly LoadingAnimation loading; + private readonly Drawable content; + + public LoadingButton() + { + Child = background = CreateBackground(); + + background.AddRange(new Drawable[] + { + content = CreateContent(), + loading = new LoadingAnimation + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(12) + } + }); + } + + protected override bool OnClick(ClickEvent e) + { + if (!Enabled.Value) + return false; + + try + { + return base.OnClick(e); + } + finally + { + // run afterwards as this will disable this button. + IsLoading = true; + } + } + + protected virtual void OnLoadingStart() + { + } + + protected virtual void OnLoadingFinished() + { + } + + protected abstract Container CreateBackground(); + + protected abstract Drawable CreateContent(); + } +} diff --git a/osu.Game/Graphics/UserInterface/ShowMoreButton.cs b/osu.Game/Graphics/UserInterface/ShowMoreButton.cs index 5296b9dd7f..b6b87c43f2 100644 --- a/osu.Game/Graphics/UserInterface/ShowMoreButton.cs +++ b/osu.Game/Graphics/UserInterface/ShowMoreButton.cs @@ -5,8 +5,6 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; -using osu.Framework.Input.Events; -using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osuTK; using osuTK.Graphics; @@ -14,10 +12,8 @@ using System.Collections.Generic; namespace osu.Game.Graphics.UserInterface { - public class ShowMoreButton : OsuHoverContainer + public class ShowMoreButton : LoadingButton { - private const float fade_duration = 200; - private Color4 chevronIconColour; protected Color4 ChevronIconColour @@ -32,100 +28,53 @@ namespace osu.Game.Graphics.UserInterface set => text.Text = value; } - private bool isLoading; - - public bool IsLoading - { - get => isLoading; - set - { - isLoading = value; - - Enabled.Value = !isLoading; - - if (value) - { - loading.Show(); - content.FadeOut(fade_duration, Easing.OutQuint); - } - else - { - loading.Hide(); - content.FadeIn(fade_duration, Easing.OutQuint); - } - } - } - - private readonly Box background; - private readonly LoadingAnimation loading; - private readonly FillFlowContainer content; - private readonly ChevronIcon leftChevron; - private readonly ChevronIcon rightChevron; - private readonly SpriteText text; - protected override IEnumerable EffectTargets => new[] { background }; + private ChevronIcon leftChevron; + private ChevronIcon rightChevron; + private SpriteText text; + private Box background; + public ShowMoreButton() { AutoSizeAxes = Axes.Both; + AddInternal(new CircularContainer + { + Masking = true, + Size = new Vector2(140, 30) + }); + } + + protected override Container CreateBackground() => new CircularContainer + { + Masking = true, + Size = new Vector2(140, 30), + Child = background = new Box + { + RelativeSizeAxes = Axes.Both, + } + }; + + protected override Drawable CreateContent() => new FillFlowContainer + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(7), Children = new Drawable[] { - new CircularContainer + leftChevron = new ChevronIcon(), + text = new OsuSpriteText { - Masking = true, - Size = new Vector2(140, 30), - Children = new Drawable[] - { - background = new Box - { - RelativeSizeAxes = Axes.Both, - }, - content = new FillFlowContainer - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Horizontal, - Spacing = new Vector2(7), - Children = new Drawable[] - { - leftChevron = new ChevronIcon(), - text = new OsuSpriteText - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Font = OsuFont.GetFont(size: 12, weight: FontWeight.Bold), - Text = "show more".ToUpper(), - }, - rightChevron = new ChevronIcon(), - } - }, - loading = new LoadingAnimation - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Size = new Vector2(12) - }, - } - } - }; - } - - protected override bool OnClick(ClickEvent e) - { - if (!Enabled.Value) - return false; - - try - { - return base.OnClick(e); + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Font = OsuFont.GetFont(size: 12, weight: FontWeight.Bold), + Text = "show more".ToUpper(), + }, + rightChevron = new ChevronIcon(), } - finally - { - // run afterwards as this will disable this button. - IsLoading = true; - } - } + }; private class ChevronIcon : SpriteIcon { From a437ff74ccf17f9e5bd3d42af78f053a62495e6c Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Thu, 17 Oct 2019 14:18:31 +0300 Subject: [PATCH 04/28] Move VotePill to it's own file --- .../Online/TestSceneCommentsContainer.cs | 3 +- .../Graphics/UserInterface/ShowMoreButton.cs | 5 - osu.Game/Overlays/Comments/DrawableComment.cs | 94 ------------ osu.Game/Overlays/Comments/VotePill.cs | 135 ++++++++++++++++++ 4 files changed, 137 insertions(+), 100 deletions(-) create mode 100644 osu.Game/Overlays/Comments/VotePill.cs diff --git a/osu.Game.Tests/Visual/Online/TestSceneCommentsContainer.cs b/osu.Game.Tests/Visual/Online/TestSceneCommentsContainer.cs index 436e80d6f5..86bd0ddd11 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneCommentsContainer.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneCommentsContainer.cs @@ -22,7 +22,8 @@ namespace osu.Game.Tests.Visual.Online typeof(HeaderButton), typeof(SortTabControl), typeof(ShowChildrenButton), - typeof(DeletedChildrenPlaceholder) + typeof(DeletedChildrenPlaceholder), + typeof(VotePill) }; protected override bool UseOnlineAPI => true; diff --git a/osu.Game/Graphics/UserInterface/ShowMoreButton.cs b/osu.Game/Graphics/UserInterface/ShowMoreButton.cs index b6b87c43f2..31e9af55c4 100644 --- a/osu.Game/Graphics/UserInterface/ShowMoreButton.cs +++ b/osu.Game/Graphics/UserInterface/ShowMoreButton.cs @@ -38,11 +38,6 @@ namespace osu.Game.Graphics.UserInterface public ShowMoreButton() { AutoSizeAxes = Axes.Both; - AddInternal(new CircularContainer - { - Masking = true, - Size = new Vector2(140, 30) - }); } protected override Container CreateBackground() => new CircularContainer diff --git a/osu.Game/Overlays/Comments/DrawableComment.cs b/osu.Game/Overlays/Comments/DrawableComment.cs index d1cae6592a..bc53af09d3 100644 --- a/osu.Game/Overlays/Comments/DrawableComment.cs +++ b/osu.Game/Overlays/Comments/DrawableComment.cs @@ -15,12 +15,6 @@ using osu.Framework.Bindables; using osu.Framework.Graphics.Shapes; using System.Linq; using osu.Game.Online.Chat; -using osu.Framework.Allocation; -using osu.Game.Graphics.Sprites; -using osuTK.Graphics; -using osu.Framework.Extensions.Color4Extensions; -using osu.Framework.Input.Events; -using osu.Game.Graphics.UserInterface; namespace osu.Game.Overlays.Comments { @@ -339,93 +333,5 @@ namespace osu.Game.Overlays.Comments return parentComment.HasMessage ? parentComment.GetMessage : parentComment.IsDeleted ? @"deleted" : string.Empty; } } - - private class VotePill : Container, IHasAccentColour - { - public Color4 AccentColour { get; set; } - - private readonly Box background; - private readonly Box hoverLayer; - private readonly Comment comment; - private readonly CircularContainer borderContainer; - private readonly SpriteText sideNumber; - - public VotePill(Comment comment) - { - this.comment = comment; - - AutoSizeAxes = Axes.X; - Height = 20; - Children = new Drawable[] - { - borderContainer = new CircularContainer - { - RelativeSizeAxes = Axes.Both, - Masking = true, - Children = new Drawable[] - { - background = new Box - { - RelativeSizeAxes = Axes.Both - }, - hoverLayer = new Box - { - RelativeSizeAxes = Axes.Both, - Alpha = 0 - } - } - }, - new OsuSpriteText - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Margin = new MarginPadding { Horizontal = margin }, - Font = OsuFont.GetFont(size: 14), - Text = $"+{comment.VotesCount}" - }, - sideNumber = new SpriteText - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreRight, - Text = "+1", - Font = OsuFont.GetFont(size: 14), - Margin = new MarginPadding { Right = 3 }, - Alpha = 0, - }, - new HoverClickSounds(), - }; - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - AccentColour = borderContainer.BorderColour = sideNumber.Colour = colours.GreenLight; - background.Colour = comment.IsVoted ? AccentColour : OsuColour.Gray(0.05f); - hoverLayer.Colour = Color4.Black.Opacity(0.5f); - } - - protected override bool OnHover(HoverEvent e) - { - if (comment.IsVoted) - hoverLayer.Show(); - else - sideNumber.Show(); - - borderContainer.BorderThickness = 3; - return base.OnHover(e); - } - - protected override void OnHoverLost(HoverLostEvent e) - { - base.OnHoverLost(e); - - if (comment.IsVoted) - hoverLayer.Hide(); - else - sideNumber.Hide(); - - borderContainer.BorderThickness = 0; - } - } } } diff --git a/osu.Game/Overlays/Comments/VotePill.cs b/osu.Game/Overlays/Comments/VotePill.cs new file mode 100644 index 0000000000..79b7310c28 --- /dev/null +++ b/osu.Game/Overlays/Comments/VotePill.cs @@ -0,0 +1,135 @@ +// 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.Containers; +using osu.Framework.Graphics; +using osu.Game.Graphics; +using osu.Framework.Graphics.Sprites; +using osu.Game.Online.API.Requests.Responses; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Allocation; +using osu.Game.Graphics.Sprites; +using osuTK.Graphics; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Input.Events; +using osu.Game.Graphics.UserInterface; +using System.Collections.Generic; +using osuTK; + +namespace osu.Game.Overlays.Comments +{ + public class VotePill : LoadingButton, IHasAccentColour + { + public Color4 AccentColour { get; set; } + + protected override IEnumerable EffectTargets => null; + + private readonly Comment comment; + private Box background; + private Box hoverLayer; + private CircularContainer borderContainer; + private SpriteText sideNumber; + private OsuSpriteText votesCounter; + + public VotePill(Comment comment) + { + this.comment = comment; + votesCounter.Text = $"+{comment.VotesCount}"; + + AutoSizeAxes = Axes.X; + Height = 20; + LoadingAnimationSize = new Vector2(10); + + Action = () => + { + + }; + } + + protected override Container CreateBackground() => new Container + { + RelativeSizeAxes = Axes.Y, + AutoSizeAxes = Axes.X, + Children = new Drawable[] + { + borderContainer = new CircularContainer + { + RelativeSizeAxes = Axes.Both, + Masking = true, + Children = new Drawable[] + { + background = new Box + { + RelativeSizeAxes = Axes.Both + }, + hoverLayer = new Box + { + RelativeSizeAxes = Axes.Both, + Alpha = 0 + } + } + }, + sideNumber = new SpriteText + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreRight, + Text = "+1", + Font = OsuFont.GetFont(size: 14), + Margin = new MarginPadding { Right = 3 }, + Alpha = 0, + }, + }, + }; + + protected override Drawable CreateContent() => votesCounter = new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Margin = new MarginPadding { Horizontal = 10 }, + Font = OsuFont.GetFont(size: 14), + AlwaysPresent = true, + }; + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + AccentColour = borderContainer.BorderColour = sideNumber.Colour = colours.GreenLight; + background.Colour = comment.IsVoted ? AccentColour : OsuColour.Gray(0.05f); + hoverLayer.Colour = Color4.Black.Opacity(0.5f); + } + + protected override void OnLoadingStart() + { + sideNumber.Hide(); + borderContainer.BorderThickness = 0; + } + + protected override bool OnHover(HoverEvent e) + { + if (comment.IsVoted) + hoverLayer.Show(); + + if (!IsLoading) + { + borderContainer.BorderThickness = 3; + + if (!comment.IsVoted) + sideNumber.Show(); + } + + return base.OnHover(e); + } + + protected override void OnHoverLost(HoverLostEvent e) + { + base.OnHoverLost(e); + + if (comment.IsVoted) + hoverLayer.Hide(); + else + sideNumber.Hide(); + + borderContainer.BorderThickness = 0; + } + } +} From 42cd4107a0396d8dd06e2acba1ca4ba659f2dd62 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Thu, 17 Oct 2019 15:04:30 +0300 Subject: [PATCH 05/28] Implement CommentVoteRequest and adjust UI --- .../Online/API/Requests/CommentVoteRequest.cs | 36 +++++++ osu.Game/Overlays/Comments/VotePill.cs | 96 ++++++++++++++----- 2 files changed, 106 insertions(+), 26 deletions(-) create mode 100644 osu.Game/Online/API/Requests/CommentVoteRequest.cs diff --git a/osu.Game/Online/API/Requests/CommentVoteRequest.cs b/osu.Game/Online/API/Requests/CommentVoteRequest.cs new file mode 100644 index 0000000000..06a3b1126e --- /dev/null +++ b/osu.Game/Online/API/Requests/CommentVoteRequest.cs @@ -0,0 +1,36 @@ +// 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.IO.Network; +using osu.Game.Online.API.Requests.Responses; +using System.Net.Http; + +namespace osu.Game.Online.API.Requests +{ + public class CommentVoteRequest : APIRequest + { + private readonly long id; + private readonly CommentVoteAction action; + + public CommentVoteRequest(long id, CommentVoteAction action) + { + this.id = id; + this.action = action; + } + + protected override WebRequest CreateWebRequest() + { + var req = base.CreateWebRequest(); + req.Method = action == CommentVoteAction.Vote ? HttpMethod.Post : HttpMethod.Delete; + return req; + } + + protected override string Target => $@"comments/{id}/vote"; + } + + public enum CommentVoteAction + { + Vote, + UnVote + } +} diff --git a/osu.Game/Overlays/Comments/VotePill.cs b/osu.Game/Overlays/Comments/VotePill.cs index 79b7310c28..cd50546b45 100644 --- a/osu.Game/Overlays/Comments/VotePill.cs +++ b/osu.Game/Overlays/Comments/VotePill.cs @@ -15,6 +15,10 @@ using osu.Framework.Input.Events; using osu.Game.Graphics.UserInterface; using System.Collections.Generic; using osuTK; +using osu.Game.Online.API; +using osu.Game.Online.API.Requests; +using osu.Framework.Bindables; +using System.Linq; namespace osu.Game.Overlays.Comments { @@ -24,26 +28,57 @@ namespace osu.Game.Overlays.Comments protected override IEnumerable EffectTargets => null; + [Resolved] + private IAPIProvider api { get; set; } + private readonly Comment comment; private Box background; private Box hoverLayer; private CircularContainer borderContainer; private SpriteText sideNumber; private OsuSpriteText votesCounter; + private CommentVoteRequest request; + + private readonly BindableBool isVoted = new BindableBool(); public VotePill(Comment comment) { this.comment = comment; - votesCounter.Text = $"+{comment.VotesCount}"; + setCount(comment.VotesCount); + + Action = onAction; AutoSizeAxes = Axes.X; Height = 20; LoadingAnimationSize = new Vector2(10); + } - Action = () => - { + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + AccentColour = borderContainer.BorderColour = sideNumber.Colour = colours.GreenLight; + hoverLayer.Colour = Color4.Black.Opacity(0.5f); + } - }; + protected override void LoadComplete() + { + base.LoadComplete(); + isVoted.Value = comment.IsVoted; + isVoted.BindValueChanged(voted => background.Colour = voted.NewValue ? AccentColour : OsuColour.Gray(0.05f), true); + } + + private void onAction() + { + request = new CommentVoteRequest(comment.Id, isVoted.Value ? CommentVoteAction.UnVote : CommentVoteAction.Vote); + request.Success += onSuccess; + api.Queue(request); + } + + private void onSuccess(CommentBundle response) + { + isVoted.Value = !isVoted.Value; + setCount(response.Comments.First().VotesCount); + IsLoading = false; } protected override Container CreateBackground() => new Container @@ -90,46 +125,55 @@ namespace osu.Game.Overlays.Comments AlwaysPresent = true, }; - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - AccentColour = borderContainer.BorderColour = sideNumber.Colour = colours.GreenLight; - background.Colour = comment.IsVoted ? AccentColour : OsuColour.Gray(0.05f); - hoverLayer.Colour = Color4.Black.Opacity(0.5f); - } + protected override void OnLoadingStart() => onHoverLostAction(); - protected override void OnLoadingStart() + protected override void OnLoadingFinished() { - sideNumber.Hide(); - borderContainer.BorderThickness = 0; + if (IsHovered) + onHoverAction(); } protected override bool OnHover(HoverEvent e) { - if (comment.IsVoted) - hoverLayer.Show(); - - if (!IsLoading) - { - borderContainer.BorderThickness = 3; - - if (!comment.IsVoted) - sideNumber.Show(); - } - + onHoverAction(); return base.OnHover(e); } protected override void OnHoverLost(HoverLostEvent e) { + onHoverLostAction(); base.OnHoverLost(e); + } - if (comment.IsVoted) + private void onHoverLostAction() + { + if (isVoted.Value) hoverLayer.Hide(); else sideNumber.Hide(); borderContainer.BorderThickness = 0; } + + private void onHoverAction() + { + if (!IsLoading) + { + borderContainer.BorderThickness = 3; + + if (!isVoted.Value) + sideNumber.Show(); + else + hoverLayer.Show(); + } + } + + private void setCount(int count) => votesCounter.Text = $"+{count}"; + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + request?.Cancel(); + } } } From 6b196a6ce7548b180afebed93adfaf0d5062ef55 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Thu, 17 Oct 2019 15:24:51 +0300 Subject: [PATCH 06/28] CI fixes --- osu.Game/Graphics/UserInterface/LoadingButton.cs | 7 ++++--- osu.Game/Overlays/Comments/DrawableComment.cs | 10 ++++++++-- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/LoadingButton.cs b/osu.Game/Graphics/UserInterface/LoadingButton.cs index 1557a90c4a..46a4c70666 100644 --- a/osu.Game/Graphics/UserInterface/LoadingButton.cs +++ b/osu.Game/Graphics/UserInterface/LoadingButton.cs @@ -45,15 +45,16 @@ namespace osu.Game.Graphics.UserInterface set => loading.Size = value; } - private readonly Container background; private readonly LoadingAnimation loading; private readonly Drawable content; - public LoadingButton() + protected LoadingButton() { + Container background; + Child = background = CreateBackground(); - background.AddRange(new Drawable[] + background.AddRange(new[] { content = CreateContent(), loading = new LoadingAnimation diff --git a/osu.Game/Overlays/Comments/DrawableComment.cs b/osu.Game/Overlays/Comments/DrawableComment.cs index bc53af09d3..b376900b18 100644 --- a/osu.Game/Overlays/Comments/DrawableComment.cs +++ b/osu.Game/Overlays/Comments/DrawableComment.cs @@ -81,11 +81,17 @@ namespace osu.Game.Overlays.Comments Spacing = new Vector2(5, 0), Children = new Drawable[] { - votePill = new VotePill(comment) + new Container { Anchor = Anchor.Centre, Origin = Anchor.Centre, - AlwaysPresent = true, + Width = 40, + AutoSizeAxes = Axes.Y, + Child = votePill = new VotePill(comment) + { + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + } }, new UpdateableAvatar(comment.User) { From a858e713f88e7f009c8a2269a72d41d432357eda Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Thu, 17 Oct 2019 15:40:09 +0300 Subject: [PATCH 07/28] Fix multiple spaces --- osu.Game/Overlays/Comments/DrawableComment.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Comments/DrawableComment.cs b/osu.Game/Overlays/Comments/DrawableComment.cs index b376900b18..3fb9867f0e 100644 --- a/osu.Game/Overlays/Comments/DrawableComment.cs +++ b/osu.Game/Overlays/Comments/DrawableComment.cs @@ -87,7 +87,7 @@ namespace osu.Game.Overlays.Comments Origin = Anchor.Centre, Width = 40, AutoSizeAxes = Axes.Y, - Child = votePill = new VotePill(comment) + Child = votePill = new VotePill(comment) { Anchor = Anchor.CentreRight, Origin = Anchor.CentreRight, From 9ee63a8c1abb260ccd196662c9e1e9705bb9f086 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Thu, 17 Oct 2019 16:28:32 +0300 Subject: [PATCH 08/28] Apply suggested changes --- osu.Game/Online/API/Requests/Responses/CommentBundle.cs | 6 +----- osu.Game/Overlays/Comments/VotePill.cs | 8 ++++---- 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/osu.Game/Online/API/Requests/Responses/CommentBundle.cs b/osu.Game/Online/API/Requests/Responses/CommentBundle.cs index f910c738ac..7db3126ade 100644 --- a/osu.Game/Online/API/Requests/Responses/CommentBundle.cs +++ b/osu.Game/Online/API/Requests/Responses/CommentBundle.cs @@ -47,15 +47,11 @@ namespace osu.Game.Online.API.Requests.Responses [JsonProperty(@"included_comments")] public List IncludedComments { get; set; } - private List userVotes; - [JsonProperty(@"user_votes")] - public List UserVotes + private List userVotes { - get => userVotes; set { - userVotes = value; value.ForEach(v => { Comments.ForEach(c => diff --git a/osu.Game/Overlays/Comments/VotePill.cs b/osu.Game/Overlays/Comments/VotePill.cs index cd50546b45..d8288c8ec4 100644 --- a/osu.Game/Overlays/Comments/VotePill.cs +++ b/osu.Game/Overlays/Comments/VotePill.cs @@ -40,11 +40,11 @@ namespace osu.Game.Overlays.Comments private CommentVoteRequest request; private readonly BindableBool isVoted = new BindableBool(); + private readonly BindableInt votesCount = new BindableInt(); public VotePill(Comment comment) { this.comment = comment; - setCount(comment.VotesCount); Action = onAction; @@ -64,7 +64,9 @@ namespace osu.Game.Overlays.Comments { base.LoadComplete(); isVoted.Value = comment.IsVoted; + votesCount.Value = comment.VotesCount; isVoted.BindValueChanged(voted => background.Colour = voted.NewValue ? AccentColour : OsuColour.Gray(0.05f), true); + votesCount.BindValueChanged(count => votesCounter.Text = $"+{count.NewValue}", true); } private void onAction() @@ -77,7 +79,7 @@ namespace osu.Game.Overlays.Comments private void onSuccess(CommentBundle response) { isVoted.Value = !isVoted.Value; - setCount(response.Comments.First().VotesCount); + votesCount.Value = response.Comments.Single().VotesCount; IsLoading = false; } @@ -168,8 +170,6 @@ namespace osu.Game.Overlays.Comments } } - private void setCount(int count) => votesCounter.Text = $"+{count}"; - protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); From 22511e41e29ab2bea9b01f678296f91224d7d3c8 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Thu, 17 Oct 2019 23:20:01 +0300 Subject: [PATCH 09/28] Use received data to set isVoted bindable --- osu.Game/Overlays/Comments/VotePill.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/Comments/VotePill.cs b/osu.Game/Overlays/Comments/VotePill.cs index d8288c8ec4..d1a78cb368 100644 --- a/osu.Game/Overlays/Comments/VotePill.cs +++ b/osu.Game/Overlays/Comments/VotePill.cs @@ -78,8 +78,9 @@ namespace osu.Game.Overlays.Comments private void onSuccess(CommentBundle response) { - isVoted.Value = !isVoted.Value; - votesCount.Value = response.Comments.Single().VotesCount; + var receivedComment = response.Comments.Single(); + isVoted.Value = receivedComment.IsVoted; + votesCount.Value = receivedComment.VotesCount; IsLoading = false; } From 9daafb46365b369b9a656c52127f89d1e15a0baa Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Fri, 18 Oct 2019 03:06:01 +0300 Subject: [PATCH 10/28] Simplify hover/unhover logic --- osu.Game/Overlays/Comments/VotePill.cs | 24 ++++++++++-------------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/osu.Game/Overlays/Comments/VotePill.cs b/osu.Game/Overlays/Comments/VotePill.cs index d1a78cb368..5eade6fc46 100644 --- a/osu.Game/Overlays/Comments/VotePill.cs +++ b/osu.Game/Overlays/Comments/VotePill.cs @@ -128,7 +128,7 @@ namespace osu.Game.Overlays.Comments AlwaysPresent = true, }; - protected override void OnLoadingStart() => onHoverLostAction(); + protected override void OnLoadingStart() => updateDisplay(); protected override void OnLoadingFinished() { @@ -144,31 +144,27 @@ namespace osu.Game.Overlays.Comments protected override void OnHoverLost(HoverLostEvent e) { - onHoverLostAction(); + updateDisplay(); base.OnHoverLost(e); } - private void onHoverLostAction() + private void updateDisplay() { if (isVoted.Value) - hoverLayer.Hide(); - else + { + hoverLayer.FadeTo(IsHovered ? 1 : 0); sideNumber.Hide(); + } + else + sideNumber.FadeTo(IsHovered ? 1 : 0); - borderContainer.BorderThickness = 0; + borderContainer.BorderThickness = IsHovered ? 3 : 0; } private void onHoverAction() { if (!IsLoading) - { - borderContainer.BorderThickness = 3; - - if (!isVoted.Value) - sideNumber.Show(); - else - hoverLayer.Show(); - } + updateDisplay(); } protected override void Dispose(bool isDisposing) From b310fd9d44503c7f889efc8357b76bb885a03367 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Wed, 23 Oct 2019 13:39:42 +0300 Subject: [PATCH 11/28] Adjust naming inside the LoadingButton --- .../Graphics/UserInterface/LoadingButton.cs | 18 +++++++++--------- .../Graphics/UserInterface/ShowMoreButton.cs | 4 ++-- osu.Game/Overlays/Comments/VotePill.cs | 4 ++-- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/LoadingButton.cs b/osu.Game/Graphics/UserInterface/LoadingButton.cs index 46a4c70666..c177431c78 100644 --- a/osu.Game/Graphics/UserInterface/LoadingButton.cs +++ b/osu.Game/Graphics/UserInterface/LoadingButton.cs @@ -27,13 +27,13 @@ namespace osu.Game.Graphics.UserInterface if (value) { loading.Show(); - content.FadeOut(fade_duration, Easing.OutQuint); + text.FadeOut(fade_duration, Easing.OutQuint); OnLoadingStart(); } else { loading.Hide(); - content.FadeIn(fade_duration, Easing.OutQuint); + text.FadeIn(fade_duration, Easing.OutQuint); OnLoadingFinished(); } } @@ -46,17 +46,17 @@ namespace osu.Game.Graphics.UserInterface } private readonly LoadingAnimation loading; - private readonly Drawable content; + private readonly Drawable text; protected LoadingButton() { - Container background; + Container content; - Child = background = CreateBackground(); + Child = content = CreateContent(); - background.AddRange(new[] + content.AddRange(new[] { - content = CreateContent(), + text = CreateText(), loading = new LoadingAnimation { Anchor = Anchor.Centre, @@ -90,8 +90,8 @@ namespace osu.Game.Graphics.UserInterface { } - protected abstract Container CreateBackground(); + protected abstract Container CreateContent(); - protected abstract Drawable CreateContent(); + protected abstract Drawable CreateText(); } } diff --git a/osu.Game/Graphics/UserInterface/ShowMoreButton.cs b/osu.Game/Graphics/UserInterface/ShowMoreButton.cs index 31e9af55c4..407e102ca5 100644 --- a/osu.Game/Graphics/UserInterface/ShowMoreButton.cs +++ b/osu.Game/Graphics/UserInterface/ShowMoreButton.cs @@ -40,7 +40,7 @@ namespace osu.Game.Graphics.UserInterface AutoSizeAxes = Axes.Both; } - protected override Container CreateBackground() => new CircularContainer + protected override Container CreateContent() => new CircularContainer { Masking = true, Size = new Vector2(140, 30), @@ -50,7 +50,7 @@ namespace osu.Game.Graphics.UserInterface } }; - protected override Drawable CreateContent() => new FillFlowContainer + protected override Drawable CreateText() => new FillFlowContainer { Anchor = Anchor.Centre, Origin = Anchor.Centre, diff --git a/osu.Game/Overlays/Comments/VotePill.cs b/osu.Game/Overlays/Comments/VotePill.cs index 5eade6fc46..532fd8d905 100644 --- a/osu.Game/Overlays/Comments/VotePill.cs +++ b/osu.Game/Overlays/Comments/VotePill.cs @@ -84,7 +84,7 @@ namespace osu.Game.Overlays.Comments IsLoading = false; } - protected override Container CreateBackground() => new Container + protected override Container CreateContent() => new Container { RelativeSizeAxes = Axes.Y, AutoSizeAxes = Axes.X, @@ -119,7 +119,7 @@ namespace osu.Game.Overlays.Comments }, }; - protected override Drawable CreateContent() => votesCounter = new OsuSpriteText + protected override Drawable CreateText() => votesCounter = new OsuSpriteText { Anchor = Anchor.Centre, Origin = Anchor.Centre, From f45f17339c9d6c4d4c7d61020cbcfdd8ad090ac3 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 24 Oct 2019 18:09:20 +0900 Subject: [PATCH 12/28] Implement slider path distance snapping --- .../Blueprints/Sliders/SliderPlacementBlueprint.cs | 11 +++++++++-- osu.Game/Rulesets/Edit/HitObjectComposer.cs | 4 ++++ .../Edit/Compose/Components/DistanceSnapGrid.cs | 7 +++++++ 3 files changed, 20 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs index b7b8d0af88..e1478a062c 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs @@ -33,6 +33,9 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders private PlacementState state; + [Resolved] + private HitObjectComposer composer { get; set; } + public SliderPlacementBlueprint() : base(new Objects.Slider()) { @@ -131,8 +134,12 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders private void updateSlider() { - var newControlPoints = segments.SelectMany(s => s.ControlPoints).Concat(cursor.Yield()).ToArray(); - HitObject.Path = new SliderPath(newControlPoints.Length > 2 ? PathType.Bezier : PathType.Linear, newControlPoints); + Vector2[] newControlPoints = segments.SelectMany(s => s.ControlPoints).Concat(cursor.Yield()).ToArray(); + + var unsnappedPath = new SliderPath(newControlPoints.Length > 2 ? PathType.Bezier : PathType.Linear, newControlPoints); + var snappedDistance = composer.GetSnappedDistance((float)unsnappedPath.Distance); + + HitObject.Path = new SliderPath(newControlPoints.Length > 2 ? PathType.Bezier : PathType.Linear, newControlPoints, snappedDistance); bodyPiece.UpdateFrom(HitObject); headCirclePiece.UpdateFrom(HitObject.HeadCircle); diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index 6396301add..f9d2734e80 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -261,6 +261,8 @@ namespace osu.Game.Rulesets.Edit public override double GetSnappedTime(double startTime, Vector2 position) => distanceSnapGrid?.GetSnapTime(position) ?? startTime; + public override float GetSnappedDistance(float distance) => distanceSnapGrid?.GetSnapDistance(distance) ?? distance; + protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); @@ -313,5 +315,7 @@ namespace osu.Game.Rulesets.Edit public abstract Vector2 GetSnappedPosition(Vector2 position); public abstract double GetSnappedTime(double startTime, Vector2 screenSpacePosition); + + public abstract float GetSnappedDistance(float distance); } } diff --git a/osu.Game/Screens/Edit/Compose/Components/DistanceSnapGrid.cs b/osu.Game/Screens/Edit/Compose/Components/DistanceSnapGrid.cs index 096ff0a6dd..3bedff79f5 100644 --- a/osu.Game/Screens/Edit/Compose/Components/DistanceSnapGrid.cs +++ b/osu.Game/Screens/Edit/Compose/Components/DistanceSnapGrid.cs @@ -128,6 +128,13 @@ namespace osu.Game.Screens.Edit.Compose.Components /// The time at the snapped position. public double GetSnapTime(Vector2 position) => startTime + (position - CentrePosition).Length / Velocity; + /// + /// Snaps a distance by the snap distance of this grid. + /// + /// The distance to snap. + /// The snapped distance. + public float GetSnapDistance(float distance) => (int)(distance / DistanceSpacing) * DistanceSpacing; + /// /// Retrieves the applicable colour for a beat index. /// From d83b9ef0e4078688dd1b27cb243b4c147144de96 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 24 Oct 2019 18:17:54 +0900 Subject: [PATCH 13/28] Rename grid snapping methods --- osu.Game.Rulesets.Osu.Tests/TestSceneOsuDistanceSnapGrid.cs | 4 ++-- osu.Game.Tests/Visual/Editor/TestSceneDistanceSnapGrid.cs | 6 +++--- osu.Game/Rulesets/Edit/HitObjectComposer.cs | 6 +++--- .../Edit/Compose/Components/CircularDistanceSnapGrid.cs | 2 +- .../Screens/Edit/Compose/Components/DistanceSnapGrid.cs | 6 +++--- 5 files changed, 12 insertions(+), 12 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneOsuDistanceSnapGrid.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuDistanceSnapGrid.cs index 6b8daa531f..fddbcea374 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneOsuDistanceSnapGrid.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuDistanceSnapGrid.cs @@ -142,7 +142,7 @@ namespace osu.Game.Rulesets.Osu.Tests private void assertSnappedDistance(float expectedDistance) => AddAssert($"snap distance = {expectedDistance}", () => { - Vector2 snappedPosition = grid.GetSnapPosition(grid.ToLocalSpace(InputManager.CurrentState.Mouse.Position)); + Vector2 snappedPosition = grid.GetSnappedPosition(grid.ToLocalSpace(InputManager.CurrentState.Mouse.Position)); float distance = Vector2.Distance(snappedPosition, grid_position); return Precision.AlmostEquals(expectedDistance, distance); @@ -160,7 +160,7 @@ namespace osu.Game.Rulesets.Osu.Tests Colour = Color4.SlateGray }, grid = new TestOsuDistanceSnapGrid(new HitCircle { Position = grid_position }), - new SnappingCursorContainer { GetSnapPosition = v => grid.GetSnapPosition(grid.ToLocalSpace(v)) } + new SnappingCursorContainer { GetSnapPosition = v => grid.GetSnappedPosition(grid.ToLocalSpace(v)) } }; }); } diff --git a/osu.Game.Tests/Visual/Editor/TestSceneDistanceSnapGrid.cs b/osu.Game.Tests/Visual/Editor/TestSceneDistanceSnapGrid.cs index a9e5930478..54d910fdcf 100644 --- a/osu.Game.Tests/Visual/Editor/TestSceneDistanceSnapGrid.cs +++ b/osu.Game.Tests/Visual/Editor/TestSceneDistanceSnapGrid.cs @@ -106,10 +106,10 @@ namespace osu.Game.Tests.Visual.Editor Vector2 snapPosition = Vector2.Zero; AddStep("get first tick position", () => snapPosition = grid_position + new Vector2((float)beat_length, 0)); - AddAssert("snap time is 1 beat away", () => Precision.AlmostEquals(beat_length, grid.GetSnapTime(snapPosition), 0.01)); + AddAssert("snap time is 1 beat away", () => Precision.AlmostEquals(beat_length, grid.GetSnappedTime(snapPosition), 0.01)); createGrid(g => g.Velocity = 2, "with velocity = 2"); - AddAssert("snap time is now 0.5 beats away", () => Precision.AlmostEquals(beat_length / 2, grid.GetSnapTime(snapPosition), 0.01)); + AddAssert("snap time is now 0.5 beats away", () => Precision.AlmostEquals(beat_length / 2, grid.GetSnappedTime(snapPosition), 0.01)); } private void createGrid(Action func = null, string description = null) @@ -206,7 +206,7 @@ namespace osu.Game.Tests.Visual.Editor protected override float GetVelocity(double time, ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty) => Velocity; - public override Vector2 GetSnapPosition(Vector2 screenSpacePosition) + public override Vector2 GetSnappedPosition(Vector2 screenSpacePosition) => Vector2.Zero; } } diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index f9d2734e80..3c24c3dd1f 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -257,11 +257,11 @@ namespace osu.Game.Rulesets.Edit public void Delete(HitObject hitObject) => EditorBeatmap.Remove(hitObject); - public override Vector2 GetSnappedPosition(Vector2 position) => distanceSnapGrid?.GetSnapPosition(position) ?? position; + public override Vector2 GetSnappedPosition(Vector2 position) => distanceSnapGrid?.GetSnappedPosition(position) ?? position; - public override double GetSnappedTime(double startTime, Vector2 position) => distanceSnapGrid?.GetSnapTime(position) ?? startTime; + public override double GetSnappedTime(double startTime, Vector2 position) => distanceSnapGrid?.GetSnappedTime(position) ?? startTime; - public override float GetSnappedDistance(float distance) => distanceSnapGrid?.GetSnapDistance(distance) ?? distance; + public override float GetSnappedDistance(float distance) => distanceSnapGrid?.GetSnappedDistance(distance) ?? distance; protected override void Dispose(bool isDisposing) { diff --git a/osu.Game/Screens/Edit/Compose/Components/CircularDistanceSnapGrid.cs b/osu.Game/Screens/Edit/Compose/Components/CircularDistanceSnapGrid.cs index a644e51c13..381ae9f927 100644 --- a/osu.Game/Screens/Edit/Compose/Components/CircularDistanceSnapGrid.cs +++ b/osu.Game/Screens/Edit/Compose/Components/CircularDistanceSnapGrid.cs @@ -63,7 +63,7 @@ namespace osu.Game.Screens.Edit.Compose.Components } } - public override Vector2 GetSnapPosition(Vector2 position) + public override Vector2 GetSnappedPosition(Vector2 position) { Vector2 direction = position - CentrePosition; diff --git a/osu.Game/Screens/Edit/Compose/Components/DistanceSnapGrid.cs b/osu.Game/Screens/Edit/Compose/Components/DistanceSnapGrid.cs index 3bedff79f5..50c69ae561 100644 --- a/osu.Game/Screens/Edit/Compose/Components/DistanceSnapGrid.cs +++ b/osu.Game/Screens/Edit/Compose/Components/DistanceSnapGrid.cs @@ -119,21 +119,21 @@ namespace osu.Game.Screens.Edit.Compose.Components /// /// The original position in coordinate space local to this . /// The snapped position in coordinate space local to this . - public abstract Vector2 GetSnapPosition(Vector2 position); + public abstract Vector2 GetSnappedPosition(Vector2 position); /// /// Retrieves the time at a snapped position. /// /// The snapped position in coordinate space local to this . /// The time at the snapped position. - public double GetSnapTime(Vector2 position) => startTime + (position - CentrePosition).Length / Velocity; + public double GetSnappedTime(Vector2 position) => startTime + (position - CentrePosition).Length / Velocity; /// /// Snaps a distance by the snap distance of this grid. /// /// The distance to snap. /// The snapped distance. - public float GetSnapDistance(float distance) => (int)(distance / DistanceSpacing) * DistanceSpacing; + public float GetSnappedDistance(float distance) => (int)(distance / DistanceSpacing) * DistanceSpacing; /// /// Retrieves the applicable colour for a beat index. From a969914d6ed0be5aadee0d3c40e43d0e640a5319 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 24 Oct 2019 18:24:22 +0900 Subject: [PATCH 14/28] Mention coordinate space --- osu.Game/Screens/Edit/Compose/Components/DistanceSnapGrid.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/DistanceSnapGrid.cs b/osu.Game/Screens/Edit/Compose/Components/DistanceSnapGrid.cs index 50c69ae561..250a0abef5 100644 --- a/osu.Game/Screens/Edit/Compose/Components/DistanceSnapGrid.cs +++ b/osu.Game/Screens/Edit/Compose/Components/DistanceSnapGrid.cs @@ -131,7 +131,7 @@ namespace osu.Game.Screens.Edit.Compose.Components /// /// Snaps a distance by the snap distance of this grid. /// - /// The distance to snap. + /// The distance to snap in coordinate space local to this . /// The snapped distance. public float GetSnappedDistance(float distance) => (int)(distance / DistanceSpacing) * DistanceSpacing; From 0af5706db6f02c68a15302cf3b9fc0cf9b3a25a2 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 24 Oct 2019 19:02:59 +0900 Subject: [PATCH 15/28] Snap path during control point movement --- .../Sliders/Components/PathControlPointPiece.cs | 6 ++++-- .../Components/PathControlPointVisualiser.cs | 6 +++++- .../Sliders/SliderPlacementBlueprint.cs | 2 +- .../Sliders/SliderSelectionBlueprint.cs | 17 ++++++++++++++++- 4 files changed, 26 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs index 3aec7c2872..7afb8fcf49 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs @@ -1,6 +1,7 @@ // 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 osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -8,7 +9,6 @@ using osu.Framework.Graphics.Lines; using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Events; using osu.Game.Graphics; -using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Osu.Objects; using osuTK; @@ -16,6 +16,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components { public class PathControlPointPiece : BlueprintPiece { + public Action ControlPointsChanged; + private readonly Slider slider; private readonly int index; @@ -96,7 +98,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components if (isSegmentSeparatorWithPrevious) newControlPoints[index - 1] = newControlPoints[index]; - slider.Path = new SliderPath(slider.Path.Type, newControlPoints); + ControlPointsChanged?.Invoke(newControlPoints); return true; } diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs index 24fcc460d1..0385824b27 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs @@ -1,14 +1,18 @@ // 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 osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Rulesets.Osu.Objects; +using osuTK; namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components { public class PathControlPointVisualiser : CompositeDrawable { + public Action ControlPointsChanged; + private readonly Slider slider; private readonly Container pieces; @@ -25,7 +29,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components base.Update(); while (slider.Path.ControlPoints.Length > pieces.Count) - pieces.Add(new PathControlPointPiece(slider, pieces.Count)); + pieces.Add(new PathControlPointPiece(slider, pieces.Count) { ControlPointsChanged = c => ControlPointsChanged?.Invoke(c) }); while (slider.Path.ControlPoints.Length < pieces.Count) pieces.Remove(pieces[pieces.Count - 1]); } diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs index e1478a062c..761c2961ea 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs @@ -51,7 +51,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders bodyPiece = new SliderBodyPiece(), headCirclePiece = new HitCirclePiece(), tailCirclePiece = new HitCirclePiece(), - new PathControlPointVisualiser(HitObject), + new PathControlPointVisualiser(HitObject) { ControlPointsChanged = _ => updateSlider() }, }; setState(PlacementState.Initial); diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs index fdeffc6f8a..a62c3cbb9f 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs @@ -1,7 +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 osu.Framework.Allocation; using osu.Framework.Graphics; +using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects.Drawables; @@ -15,6 +19,9 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders protected readonly SliderCircleSelectionBlueprint HeadBlueprint; protected readonly SliderCircleSelectionBlueprint TailBlueprint; + [Resolved] + private HitObjectComposer composer { get; set; } + public SliderSelectionBlueprint(DrawableSlider slider) : base(slider) { @@ -25,7 +32,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders BodyPiece = new SliderBodyPiece(), HeadBlueprint = CreateCircleSelectionBlueprint(slider, SliderPosition.Start), TailBlueprint = CreateCircleSelectionBlueprint(slider, SliderPosition.End), - new PathControlPointVisualiser(sliderObject), + new PathControlPointVisualiser(sliderObject) { ControlPointsChanged = onNewControlPoints }, }; } @@ -36,6 +43,14 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders BodyPiece.UpdateFrom(HitObject); } + private void onNewControlPoints(Vector2[] controlPoints) + { + var unsnappedPath = new SliderPath(controlPoints.Length > 2 ? PathType.Bezier : PathType.Linear, controlPoints); + var snappedDistance = composer.GetSnappedDistance((float)unsnappedPath.Distance); + + HitObject.Path = new SliderPath(unsnappedPath.Type, controlPoints, snappedDistance); + } + public override Vector2 SelectionPoint => HeadBlueprint.SelectionPoint; protected virtual SliderCircleSelectionBlueprint CreateCircleSelectionBlueprint(DrawableSlider slider, SliderPosition position) => new SliderCircleSelectionBlueprint(slider, position); From b7af4acdbf21bd3c3e521b1b12abb63d53341ea6 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 24 Oct 2019 19:04:00 +0900 Subject: [PATCH 16/28] Allow not having a composer --- .../Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs | 4 ++-- .../Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs index 761c2961ea..400a1fea14 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs @@ -33,7 +33,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders private PlacementState state; - [Resolved] + [Resolved(CanBeNull = true)] private HitObjectComposer composer { get; set; } public SliderPlacementBlueprint() @@ -137,7 +137,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders Vector2[] newControlPoints = segments.SelectMany(s => s.ControlPoints).Concat(cursor.Yield()).ToArray(); var unsnappedPath = new SliderPath(newControlPoints.Length > 2 ? PathType.Bezier : PathType.Linear, newControlPoints); - var snappedDistance = composer.GetSnappedDistance((float)unsnappedPath.Distance); + var snappedDistance = composer?.GetSnappedDistance((float)unsnappedPath.Distance) ?? (float)unsnappedPath.Distance; HitObject.Path = new SliderPath(newControlPoints.Length > 2 ? PathType.Bezier : PathType.Linear, newControlPoints, snappedDistance); diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs index a62c3cbb9f..a90ed677ff 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs @@ -19,7 +19,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders protected readonly SliderCircleSelectionBlueprint HeadBlueprint; protected readonly SliderCircleSelectionBlueprint TailBlueprint; - [Resolved] + [Resolved(CanBeNull = true)] private HitObjectComposer composer { get; set; } public SliderSelectionBlueprint(DrawableSlider slider) @@ -46,7 +46,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders private void onNewControlPoints(Vector2[] controlPoints) { var unsnappedPath = new SliderPath(controlPoints.Length > 2 ? PathType.Bezier : PathType.Linear, controlPoints); - var snappedDistance = composer.GetSnappedDistance((float)unsnappedPath.Distance); + var snappedDistance = composer?.GetSnappedDistance((float)unsnappedPath.Distance) ?? (float)unsnappedPath.Distance; HitObject.Path = new SliderPath(unsnappedPath.Type, controlPoints, snappedDistance); } From a6458fdeab45510f6cf92956cfbd038237009bb6 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 24 Oct 2019 19:04:24 +0900 Subject: [PATCH 17/28] Re-use slider type --- .../Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs index 400a1fea14..02923203b1 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs @@ -139,7 +139,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders var unsnappedPath = new SliderPath(newControlPoints.Length > 2 ? PathType.Bezier : PathType.Linear, newControlPoints); var snappedDistance = composer?.GetSnappedDistance((float)unsnappedPath.Distance) ?? (float)unsnappedPath.Distance; - HitObject.Path = new SliderPath(newControlPoints.Length > 2 ? PathType.Bezier : PathType.Linear, newControlPoints, snappedDistance); + HitObject.Path = new SliderPath(unsnappedPath.Type, newControlPoints, snappedDistance); bodyPiece.UpdateFrom(HitObject); headCirclePiece.UpdateFrom(HitObject.HeadCircle); From 85769982a01fb95da961bb5d2c23abfaa8669380 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Thu, 24 Oct 2019 17:49:34 +0300 Subject: [PATCH 18/28] Refactor LoadingButton --- .../Graphics/UserInterface/LoadingButton.cs | 26 +++------- .../Graphics/UserInterface/ShowMoreButton.cs | 49 +++++++++++-------- osu.Game/Overlays/Comments/VotePill.cs | 29 ++++++----- 3 files changed, 54 insertions(+), 50 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/LoadingButton.cs b/osu.Game/Graphics/UserInterface/LoadingButton.cs index c177431c78..49ec18ce8e 100644 --- a/osu.Game/Graphics/UserInterface/LoadingButton.cs +++ b/osu.Game/Graphics/UserInterface/LoadingButton.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; using osu.Framework.Input.Events; using osu.Game.Graphics.Containers; using osuTK; @@ -11,8 +10,6 @@ namespace osu.Game.Graphics.UserInterface { public abstract class LoadingButton : OsuHoverContainer { - private const float fade_duration = 200; - private bool isLoading; public bool IsLoading @@ -27,14 +24,12 @@ namespace osu.Game.Graphics.UserInterface if (value) { loading.Show(); - text.FadeOut(fade_duration, Easing.OutQuint); - OnLoadingStart(); + OnLoadStarted(); } else { loading.Hide(); - text.FadeIn(fade_duration, Easing.OutQuint); - OnLoadingFinished(); + OnLoadFinished(); } } } @@ -46,17 +41,12 @@ namespace osu.Game.Graphics.UserInterface } private readonly LoadingAnimation loading; - private readonly Drawable text; protected LoadingButton() { - Container content; - - Child = content = CreateContent(); - - content.AddRange(new[] + AddRange(new[] { - text = CreateText(), + CreateContent(), loading = new LoadingAnimation { Anchor = Anchor.Centre, @@ -82,16 +72,14 @@ namespace osu.Game.Graphics.UserInterface } } - protected virtual void OnLoadingStart() + protected virtual void OnLoadStarted() { } - protected virtual void OnLoadingFinished() + protected virtual void OnLoadFinished() { } - protected abstract Container CreateContent(); - - protected abstract Drawable CreateText(); + protected abstract Drawable CreateContent(); } } diff --git a/osu.Game/Graphics/UserInterface/ShowMoreButton.cs b/osu.Game/Graphics/UserInterface/ShowMoreButton.cs index 407e102ca5..4931a6aed6 100644 --- a/osu.Game/Graphics/UserInterface/ShowMoreButton.cs +++ b/osu.Game/Graphics/UserInterface/ShowMoreButton.cs @@ -14,6 +14,8 @@ namespace osu.Game.Graphics.UserInterface { public class ShowMoreButton : LoadingButton { + private const int duration = 200; + private Color4 chevronIconColour; protected Color4 ChevronIconColour @@ -34,43 +36,50 @@ namespace osu.Game.Graphics.UserInterface private ChevronIcon rightChevron; private SpriteText text; private Box background; + private FillFlowContainer textContainer; public ShowMoreButton() { AutoSizeAxes = Axes.Both; } - protected override Container CreateContent() => new CircularContainer + protected override Drawable CreateContent() => new CircularContainer { Masking = true, Size = new Vector2(140, 30), - Child = background = new Box - { - RelativeSizeAxes = Axes.Both, - } - }; - - protected override Drawable CreateText() => new FillFlowContainer - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Horizontal, - Spacing = new Vector2(7), Children = new Drawable[] { - leftChevron = new ChevronIcon(), - text = new OsuSpriteText + background = new Box + { + RelativeSizeAxes = Axes.Both, + }, + textContainer = new FillFlowContainer { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Font = OsuFont.GetFont(size: 12, weight: FontWeight.Bold), - Text = "show more".ToUpper(), - }, - rightChevron = new ChevronIcon(), + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(7), + Children = new Drawable[] + { + leftChevron = new ChevronIcon(), + text = new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Font = OsuFont.GetFont(size: 12, weight: FontWeight.Bold), + Text = "show more".ToUpper(), + }, + rightChevron = new ChevronIcon(), + } + } } }; + protected override void OnLoadStarted() => textContainer.FadeOut(duration, Easing.OutQuint); + + protected override void OnLoadFinished() => textContainer.FadeIn(duration, Easing.OutQuint); + private class ChevronIcon : SpriteIcon { private const int icon_size = 8; diff --git a/osu.Game/Overlays/Comments/VotePill.cs b/osu.Game/Overlays/Comments/VotePill.cs index 532fd8d905..e8d9013fd9 100644 --- a/osu.Game/Overlays/Comments/VotePill.cs +++ b/osu.Game/Overlays/Comments/VotePill.cs @@ -24,6 +24,8 @@ namespace osu.Game.Overlays.Comments { public class VotePill : LoadingButton, IHasAccentColour { + private const int duration = 200; + public Color4 AccentColour { get; set; } protected override IEnumerable EffectTargets => null; @@ -84,7 +86,7 @@ namespace osu.Game.Overlays.Comments IsLoading = false; } - protected override Container CreateContent() => new Container + protected override Drawable CreateContent() => new Container { RelativeSizeAxes = Axes.Y, AutoSizeAxes = Axes.X, @@ -116,22 +118,27 @@ namespace osu.Game.Overlays.Comments Margin = new MarginPadding { Right = 3 }, Alpha = 0, }, + votesCounter = new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Margin = new MarginPadding { Horizontal = 10 }, + Font = OsuFont.GetFont(size: 14), + AlwaysPresent = true, + } }, }; - protected override Drawable CreateText() => votesCounter = new OsuSpriteText + protected override void OnLoadStarted() { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Margin = new MarginPadding { Horizontal = 10 }, - Font = OsuFont.GetFont(size: 14), - AlwaysPresent = true, - }; + votesCounter.FadeOut(duration, Easing.OutQuint); + updateDisplay(); + } - protected override void OnLoadingStart() => updateDisplay(); - - protected override void OnLoadingFinished() + protected override void OnLoadFinished() { + votesCounter.FadeIn(duration, Easing.OutQuint); + if (IsHovered) onHoverAction(); } From 607b4d874a262185d105b6fcb0588498e121de46 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 25 Oct 2019 12:34:49 +0900 Subject: [PATCH 19/28] Refactor flow of snapping through HitObjectComposer --- .../TestSceneOsuDistanceSnapGrid.cs | 7 +- .../Sliders/SliderPlacementBlueprint.cs | 2 +- .../Sliders/SliderSelectionBlueprint.cs | 2 +- .../Edit/OsuDistanceSnapGrid.cs | 12 --- .../Editor/TestSceneDistanceSnapGrid.cs | 34 ++++---- osu.Game/Rulesets/Edit/HitObjectComposer.cs | 87 +++++++++++++++++-- .../Compose/Components/BlueprintContainer.cs | 8 +- .../Components/CircularDistanceSnapGrid.cs | 11 ++- .../Compose/Components/DistanceSnapGrid.cs | 54 +++--------- 9 files changed, 128 insertions(+), 89 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneOsuDistanceSnapGrid.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuDistanceSnapGrid.cs index fddbcea374..7b28cc9c50 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneOsuDistanceSnapGrid.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuDistanceSnapGrid.cs @@ -142,10 +142,9 @@ namespace osu.Game.Rulesets.Osu.Tests private void assertSnappedDistance(float expectedDistance) => AddAssert($"snap distance = {expectedDistance}", () => { - Vector2 snappedPosition = grid.GetSnappedPosition(grid.ToLocalSpace(InputManager.CurrentState.Mouse.Position)); - float distance = Vector2.Distance(snappedPosition, grid_position); + Vector2 snappedPosition = grid.GetSnappedPosition(grid.ToLocalSpace(InputManager.CurrentState.Mouse.Position)).position; - return Precision.AlmostEquals(expectedDistance, distance); + return Precision.AlmostEquals(expectedDistance, Vector2.Distance(snappedPosition, grid_position)); }); private void createGrid() @@ -160,7 +159,7 @@ namespace osu.Game.Rulesets.Osu.Tests Colour = Color4.SlateGray }, grid = new TestOsuDistanceSnapGrid(new HitCircle { Position = grid_position }), - new SnappingCursorContainer { GetSnapPosition = v => grid.GetSnappedPosition(grid.ToLocalSpace(v)) } + new SnappingCursorContainer { GetSnapPosition = v => grid.GetSnappedPosition(grid.ToLocalSpace(v)).position } }; }); } diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs index 02923203b1..6f5309c2c2 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs @@ -137,7 +137,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders Vector2[] newControlPoints = segments.SelectMany(s => s.ControlPoints).Concat(cursor.Yield()).ToArray(); var unsnappedPath = new SliderPath(newControlPoints.Length > 2 ? PathType.Bezier : PathType.Linear, newControlPoints); - var snappedDistance = composer?.GetSnappedDistance((float)unsnappedPath.Distance) ?? (float)unsnappedPath.Distance; + var snappedDistance = composer?.GetSnappedDistanceFromDistance(HitObject.StartTime, (float)unsnappedPath.Distance) ?? (float)unsnappedPath.Distance; HitObject.Path = new SliderPath(unsnappedPath.Type, newControlPoints, snappedDistance); diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs index a90ed677ff..9ee456e791 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs @@ -46,7 +46,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders private void onNewControlPoints(Vector2[] controlPoints) { var unsnappedPath = new SliderPath(controlPoints.Length > 2 ? PathType.Bezier : PathType.Linear, controlPoints); - var snappedDistance = composer?.GetSnappedDistance((float)unsnappedPath.Distance) ?? (float)unsnappedPath.Distance; + var snappedDistance = composer?.GetSnappedDistanceFromDistance(HitObject.StartTime, (float)unsnappedPath.Distance) ?? (float)unsnappedPath.Distance; HitObject.Path = new SliderPath(unsnappedPath.Type, controlPoints, snappedDistance); } diff --git a/osu.Game.Rulesets.Osu/Edit/OsuDistanceSnapGrid.cs b/osu.Game.Rulesets.Osu/Edit/OsuDistanceSnapGrid.cs index bc0f76f000..79cd51a7f4 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuDistanceSnapGrid.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuDistanceSnapGrid.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using osu.Game.Beatmaps; -using osu.Game.Beatmaps.ControlPoints; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Screens.Edit.Compose.Components; @@ -15,15 +13,5 @@ namespace osu.Game.Rulesets.Osu.Edit { Masking = true; } - - protected override float GetVelocity(double time, ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty) - { - TimingControlPoint timingPoint = controlPointInfo.TimingPointAt(time); - DifficultyControlPoint difficultyPoint = controlPointInfo.DifficultyPointAt(time); - - double scoringDistance = OsuHitObject.BASE_SCORING_DISTANCE * difficulty.SliderMultiplier * difficultyPoint.SpeedMultiplier; - - return (float)(scoringDistance / timingPoint.BeatLength); - } } } diff --git a/osu.Game.Tests/Visual/Editor/TestSceneDistanceSnapGrid.cs b/osu.Game.Tests/Visual/Editor/TestSceneDistanceSnapGrid.cs index 54d910fdcf..a67ba4d9e4 100644 --- a/osu.Game.Tests/Visual/Editor/TestSceneDistanceSnapGrid.cs +++ b/osu.Game.Tests/Visual/Editor/TestSceneDistanceSnapGrid.cs @@ -7,7 +7,6 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Shapes; using osu.Framework.MathUtils; -using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Osu.Beatmaps; @@ -93,23 +92,27 @@ namespace osu.Game.Tests.Visual.Editor [TestCase(2)] public void TestGridVelocity(float velocity) { - createGrid(g => g.Velocity = velocity); + // Todo: - float expectedDistance = (float)beat_length * velocity; - AddAssert($"spacing is {expectedDistance}", () => Precision.AlmostEquals(grid.DistanceSpacing, expectedDistance)); + // createGrid(g => g.Velocity = velocity); + // + // float expectedDistance = (float)beat_length * velocity; + // AddAssert($"spacing is {expectedDistance}", () => Precision.AlmostEquals(grid.DistanceSpacing, expectedDistance)); } [Test] public void TestGetSnappedTime() { - createGrid(); + //Todo: - Vector2 snapPosition = Vector2.Zero; - AddStep("get first tick position", () => snapPosition = grid_position + new Vector2((float)beat_length, 0)); - AddAssert("snap time is 1 beat away", () => Precision.AlmostEquals(beat_length, grid.GetSnappedTime(snapPosition), 0.01)); - - createGrid(g => g.Velocity = 2, "with velocity = 2"); - AddAssert("snap time is now 0.5 beats away", () => Precision.AlmostEquals(beat_length / 2, grid.GetSnappedTime(snapPosition), 0.01)); + // createGrid(); + // + // Vector2 snapPosition = Vector2.Zero; + // AddStep("get first tick position", () => snapPosition = grid_position + new Vector2((float)beat_length, 0)); + // AddAssert("snap time is 1 beat away", () => Precision.AlmostEquals(beat_length, grid.GetSnappedPosition(snapPosition).time, 0.01)); + // + // createGrid(g => g.Velocity = 2, "with velocity = 2"); + // AddAssert("snap time is now 0.5 beats away", () => Precision.AlmostEquals(beat_length / 2, grid.GetSnappedPosition(snapPosition).time, 0.01)); } private void createGrid(Action func = null, string description = null) @@ -132,8 +135,6 @@ namespace osu.Game.Tests.Visual.Editor private class TestDistanceSnapGrid : DistanceSnapGrid { - public new float Velocity = 1; - public new float DistanceSpacing => base.DistanceSpacing; public TestDistanceSnapGrid(HitObject hitObject, Vector2 centrePosition) @@ -203,11 +204,8 @@ namespace osu.Game.Tests.Visual.Editor } } - protected override float GetVelocity(double time, ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty) - => Velocity; - - public override Vector2 GetSnappedPosition(Vector2 screenSpacePosition) - => Vector2.Zero; + public override (Vector2 position, double time) GetSnappedPosition(Vector2 screenSpacePosition) + => (Vector2.Zero, 0); } } } diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index 3c24c3dd1f..beb0d38216 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -14,6 +14,7 @@ using osu.Framework.Logging; using osu.Framework.Threading; using osu.Framework.Timing; using osu.Game.Beatmaps; +using osu.Game.Beatmaps.ControlPoints; using osu.Game.Rulesets.Configuration; using osu.Game.Rulesets.Edit.Tools; using osu.Game.Rulesets.Mods; @@ -39,6 +40,9 @@ namespace osu.Game.Rulesets.Edit [Resolved] protected IFrameBasedClock EditorClock { get; private set; } + [Resolved] + private BindableBeatDivisor beatDivisor { get; set; } + private IWorkingBeatmap workingBeatmap; private Beatmap playableBeatmap; private IBeatmapProcessor beatmapProcessor; @@ -246,7 +250,7 @@ namespace osu.Game.Rulesets.Edit public void BeginPlacement(HitObject hitObject) { if (distanceSnapGrid != null) - hitObject.StartTime = GetSnappedTime(hitObject.StartTime, distanceSnapGrid.ToLocalSpace(inputManager.CurrentState.Mouse.Position)); + hitObject.StartTime = GetSnappedPosition(distanceSnapGrid.ToLocalSpace(inputManager.CurrentState.Mouse.Position), hitObject.StartTime).time; } public void EndPlacement(HitObject hitObject) @@ -257,11 +261,45 @@ namespace osu.Game.Rulesets.Edit public void Delete(HitObject hitObject) => EditorBeatmap.Remove(hitObject); - public override Vector2 GetSnappedPosition(Vector2 position) => distanceSnapGrid?.GetSnappedPosition(position) ?? position; + public override (Vector2 position, double time) GetSnappedPosition(Vector2 position, double time) => distanceSnapGrid?.GetSnappedPosition(position) ?? (position, time); - public override double GetSnappedTime(double startTime, Vector2 position) => distanceSnapGrid?.GetSnappedTime(position) ?? startTime; + public override float GetBeatSnapDistanceAt(double referenceTime) + { + DifficultyControlPoint difficultyPoint = EditorBeatmap.ControlPointInfo.DifficultyPointAt(referenceTime); + return (float)(100 * EditorBeatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier * difficultyPoint.SpeedMultiplier / beatDivisor.Value); + } - public override float GetSnappedDistance(float distance) => distanceSnapGrid?.GetSnappedDistance(distance) ?? distance; + public override float DurationToDistance(double referenceTime, double duration) + { + double beatLength = EditorBeatmap.ControlPointInfo.TimingPointAt(referenceTime).BeatLength / beatDivisor.Value; + return (float)(duration / beatLength * GetBeatSnapDistanceAt(referenceTime)); + } + + public override double DistanceToDuration(double referenceTime, float distance) + { + double beatLength = EditorBeatmap.ControlPointInfo.TimingPointAt(referenceTime).BeatLength / beatDivisor.Value; + return distance / GetBeatSnapDistanceAt(referenceTime) * beatLength; + } + + public override double GetSnappedDurationFromDistance(double referenceTime, float distance) + => beatSnap(referenceTime, DistanceToDuration(referenceTime, distance)); + + public override float GetSnappedDistanceFromDistance(double referenceTime, float distance) + => DurationToDistance(referenceTime, beatSnap(referenceTime, DistanceToDuration(referenceTime, distance))); + + /// + /// Snaps a duration to the closest beat of a timing point applicable at the reference time. + /// + /// The time of the timing point which resides in. + /// The duration to snap. + /// A value that represents snapped to the closest beat of the timing point. + private double beatSnap(double referenceTime, double duration) + { + double beatLength = EditorBeatmap.ControlPointInfo.TimingPointAt(referenceTime).BeatLength / beatDivisor.Value; + + // A 1ms offset prevents rounding errors due to minute variations in duration + return (int)((duration + 1) / beatLength) * beatLength; + } protected override void Dispose(bool isDisposing) { @@ -312,10 +350,45 @@ namespace osu.Game.Rulesets.Edit [CanBeNull] protected virtual DistanceSnapGrid CreateDistanceSnapGrid([NotNull] IEnumerable selectedHitObjects) => null; - public abstract Vector2 GetSnappedPosition(Vector2 position); + public abstract (Vector2 position, double time) GetSnappedPosition(Vector2 position, double time); - public abstract double GetSnappedTime(double startTime, Vector2 screenSpacePosition); + /// + /// Retrieves the distance between two points within a timing point that are one beat length apart. + /// + /// The time of the timing point. + /// The distance between two points residing in the timing point that are one beat length apart. + public abstract float GetBeatSnapDistanceAt(double referenceTime); - public abstract float GetSnappedDistance(float distance); + /// + /// Converts a duration to a distance. + /// + /// The time of the timing point which resides in. + /// The duration to convert. + /// A value that represents as a distance in the timing point. + public abstract float DurationToDistance(double referenceTime, double duration); + + /// + /// Converts a distance to a duration. + /// + /// The time of the timing point which resides in. + /// The distance to convert. + /// A value that represents as a duration in the timing point. + public abstract double DistanceToDuration(double referenceTime, float distance); + + /// + /// Converts a distance to a snapped duration. + /// + /// The time of the timing point which resides in. + /// The distance to convert. + /// A value that represents as a duration snapped to the closest beat of the timing point. + public abstract double GetSnappedDurationFromDistance(double referenceTime, float distance); + + /// + /// Converts an unsnapped distance to a snapped distance. + /// + /// The time of the timing point which resides in. + /// The distance to convert. + /// A value that represents snapped to the closest beat of the timing point. + public abstract float GetSnappedDistanceFromDistance(double referenceTime, float distance); } } diff --git a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs index 4001a0f33a..1bfd4c454b 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs @@ -185,7 +185,7 @@ namespace osu.Game.Screens.Edit.Compose.Components private void updatePlacementPosition(Vector2 screenSpacePosition) { - Vector2 snappedGridPosition = composer.GetSnappedPosition(ToLocalSpace(screenSpacePosition)); + Vector2 snappedGridPosition = composer.GetSnappedPosition(ToLocalSpace(screenSpacePosition), 0).position; Vector2 snappedScreenSpacePosition = ToScreenSpace(snappedGridPosition); currentPlacement.UpdatePosition(snappedScreenSpacePosition); @@ -232,15 +232,15 @@ namespace osu.Game.Screens.Edit.Compose.Components private void onDragRequested(SelectionBlueprint blueprint, DragEvent dragEvent) { HitObject draggedObject = blueprint.DrawableObject.HitObject; - Vector2 movePosition = blueprint.ScreenSpaceMovementStartPosition + dragEvent.ScreenSpaceMousePosition - dragEvent.ScreenSpaceMouseDownPosition; - Vector2 snappedPosition = composer.GetSnappedPosition(ToLocalSpace(movePosition)); + + (Vector2 snappedPosition, double snappedTime) = composer.GetSnappedPosition(ToLocalSpace(movePosition), draggedObject.StartTime); // Move the hitobjects selectionHandler.HandleMovement(new MoveSelectionEvent(blueprint, blueprint.ScreenSpaceMovementStartPosition, ToScreenSpace(snappedPosition))); // Apply the start time at the newly snapped-to position - double offset = composer.GetSnappedTime(draggedObject.StartTime, snappedPosition) - draggedObject.StartTime; + double offset = snappedTime - draggedObject.StartTime; foreach (HitObject obj in selectionHandler.SelectedHitObjects) obj.StartTime += offset; } diff --git a/osu.Game/Screens/Edit/Compose/Components/CircularDistanceSnapGrid.cs b/osu.Game/Screens/Edit/Compose/Components/CircularDistanceSnapGrid.cs index 381ae9f927..7e9e6e2290 100644 --- a/osu.Game/Screens/Edit/Compose/Components/CircularDistanceSnapGrid.cs +++ b/osu.Game/Screens/Edit/Compose/Components/CircularDistanceSnapGrid.cs @@ -2,9 +2,11 @@ // See the LICENCE file in the repository root for full licence text. using System; +using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.UserInterface; +using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Objects; using osuTK; @@ -12,6 +14,9 @@ namespace osu.Game.Screens.Edit.Compose.Components { public abstract class CircularDistanceSnapGrid : DistanceSnapGrid { + [Resolved] + private HitObjectComposer composer { get; set; } + protected CircularDistanceSnapGrid(HitObject hitObject, Vector2 centrePosition) : base(hitObject, centrePosition) { @@ -63,7 +68,7 @@ namespace osu.Game.Screens.Edit.Compose.Components } } - public override Vector2 GetSnappedPosition(Vector2 position) + public override (Vector2 position, double time) GetSnappedPosition(Vector2 position) { Vector2 direction = position - CentrePosition; @@ -76,7 +81,9 @@ namespace osu.Game.Screens.Edit.Compose.Components int radialCount = Math.Max(1, (int)Math.Round(distance / radius)); Vector2 normalisedDirection = direction * new Vector2(1f / distance); - return CentrePosition + normalisedDirection * radialCount * radius; + Vector2 snappedPosition = CentrePosition + normalisedDirection * radialCount * radius; + + return (snappedPosition, StartTime + composer.GetSnappedDurationFromDistance(StartTime, (snappedPosition - CentrePosition).Length)); } } } diff --git a/osu.Game/Screens/Edit/Compose/Components/DistanceSnapGrid.cs b/osu.Game/Screens/Edit/Compose/Components/DistanceSnapGrid.cs index 250a0abef5..9eaccc5ac3 100644 --- a/osu.Game/Screens/Edit/Compose/Components/DistanceSnapGrid.cs +++ b/osu.Game/Screens/Edit/Compose/Components/DistanceSnapGrid.cs @@ -6,9 +6,8 @@ using osu.Framework.Caching; using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; -using osu.Game.Beatmaps; -using osu.Game.Beatmaps.ControlPoints; using osu.Game.Graphics; +using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Types; using osuTK; @@ -20,16 +19,16 @@ namespace osu.Game.Screens.Edit.Compose.Components /// public abstract class DistanceSnapGrid : CompositeDrawable { - /// - /// The velocity of the beatmap at the point of placement in pixels per millisecond. - /// - protected double Velocity { get; private set; } - /// /// The spacing between each tick of the beat snapping grid. /// protected float DistanceSpacing { get; private set; } + /// + /// The snapping time at . + /// + protected double StartTime { get; private set; } + /// /// The position which the grid is centred on. /// The first beat snapping tick is located at + in the desired direction. @@ -45,25 +44,24 @@ namespace osu.Game.Screens.Edit.Compose.Components [Resolved] private BindableBeatDivisor beatDivisor { get; set; } + [Resolved] + private HitObjectComposer composer { get; set; } + private readonly Cached gridCache = new Cached(); private readonly HitObject hitObject; - private double startTime; - private double beatLength; - protected DistanceSnapGrid(HitObject hitObject, Vector2 centrePosition) { this.hitObject = hitObject; - this.CentrePosition = centrePosition; + CentrePosition = centrePosition; RelativeSizeAxes = Axes.Both; } [BackgroundDependencyLoader] private void load() { - startTime = (hitObject as IHasEndTime)?.EndTime ?? hitObject.StartTime; - beatLength = beatmap.ControlPointInfo.TimingPointAt(startTime).BeatLength; + StartTime = (hitObject as IHasEndTime)?.EndTime ?? hitObject.StartTime; } protected override void LoadComplete() @@ -75,8 +73,7 @@ namespace osu.Game.Screens.Edit.Compose.Components private void updateSpacing() { - Velocity = GetVelocity(startTime, beatmap.ControlPointInfo, beatmap.BeatmapInfo.BaseDifficulty); - DistanceSpacing = (float)(beatLength / beatDivisor.Value * Velocity); + DistanceSpacing = composer.GetBeatSnapDistanceAt(StartTime); gridCache.Invalidate(); } @@ -105,35 +102,12 @@ namespace osu.Game.Screens.Edit.Compose.Components /// protected abstract void CreateContent(Vector2 centrePosition); - /// - /// Retrieves the velocity of gameplay at a point in time in pixels per millisecond. - /// - /// The time to retrieve the velocity at. - /// The beatmap's at the point in time. - /// The beatmap's at the point in time. - /// The velocity. - protected abstract float GetVelocity(double time, ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty); - /// /// Snaps a position to this grid. /// /// The original position in coordinate space local to this . - /// The snapped position in coordinate space local to this . - public abstract Vector2 GetSnappedPosition(Vector2 position); - - /// - /// Retrieves the time at a snapped position. - /// - /// The snapped position in coordinate space local to this . - /// The time at the snapped position. - public double GetSnappedTime(Vector2 position) => startTime + (position - CentrePosition).Length / Velocity; - - /// - /// Snaps a distance by the snap distance of this grid. - /// - /// The distance to snap in coordinate space local to this . - /// The snapped distance. - public float GetSnappedDistance(float distance) => (int)(distance / DistanceSpacing) * DistanceSpacing; + /// A tuple containing the snapped position in coordinate space local to this and the respective time value. + public abstract (Vector2 position, double time) GetSnappedPosition(Vector2 position); /// /// Retrieves the applicable colour for a beat index. From 4ca6a5a0cc4fb841c35171dde5affcef9c0786fa Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 25 Oct 2019 16:50:21 +0900 Subject: [PATCH 20/28] Interface the distance snap provider --- osu.Game/Rulesets/Edit/HitObjectComposer.cs | 37 +------------- .../Rulesets/Edit/IDistanceSnapProvider.cs | 51 +++++++++++++++++++ .../Compose/Components/DistanceSnapGrid.cs | 4 +- 3 files changed, 55 insertions(+), 37 deletions(-) create mode 100644 osu.Game/Rulesets/Edit/IDistanceSnapProvider.cs diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index beb0d38216..5922bfba78 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -314,7 +314,8 @@ namespace osu.Game.Rulesets.Edit } [Cached(typeof(HitObjectComposer))] - public abstract class HitObjectComposer : CompositeDrawable + [Cached(typeof(IDistanceSnapProvider))] + public abstract class HitObjectComposer : CompositeDrawable, IDistanceSnapProvider { internal HitObjectComposer() { @@ -351,44 +352,10 @@ namespace osu.Game.Rulesets.Edit protected virtual DistanceSnapGrid CreateDistanceSnapGrid([NotNull] IEnumerable selectedHitObjects) => null; public abstract (Vector2 position, double time) GetSnappedPosition(Vector2 position, double time); - - /// - /// Retrieves the distance between two points within a timing point that are one beat length apart. - /// - /// The time of the timing point. - /// The distance between two points residing in the timing point that are one beat length apart. public abstract float GetBeatSnapDistanceAt(double referenceTime); - - /// - /// Converts a duration to a distance. - /// - /// The time of the timing point which resides in. - /// The duration to convert. - /// A value that represents as a distance in the timing point. public abstract float DurationToDistance(double referenceTime, double duration); - - /// - /// Converts a distance to a duration. - /// - /// The time of the timing point which resides in. - /// The distance to convert. - /// A value that represents as a duration in the timing point. public abstract double DistanceToDuration(double referenceTime, float distance); - - /// - /// Converts a distance to a snapped duration. - /// - /// The time of the timing point which resides in. - /// The distance to convert. - /// A value that represents as a duration snapped to the closest beat of the timing point. public abstract double GetSnappedDurationFromDistance(double referenceTime, float distance); - - /// - /// Converts an unsnapped distance to a snapped distance. - /// - /// The time of the timing point which resides in. - /// The distance to convert. - /// A value that represents snapped to the closest beat of the timing point. public abstract float GetSnappedDistanceFromDistance(double referenceTime, float distance); } } diff --git a/osu.Game/Rulesets/Edit/IDistanceSnapProvider.cs b/osu.Game/Rulesets/Edit/IDistanceSnapProvider.cs new file mode 100644 index 0000000000..c6e61f68da --- /dev/null +++ b/osu.Game/Rulesets/Edit/IDistanceSnapProvider.cs @@ -0,0 +1,51 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osuTK; + +namespace osu.Game.Rulesets.Edit +{ + public interface IDistanceSnapProvider + { + (Vector2 position, double time) GetSnappedPosition(Vector2 position, double time); + + /// + /// Retrieves the distance between two points within a timing point that are one beat length apart. + /// + /// The time of the timing point. + /// The distance between two points residing in the timing point that are one beat length apart. + float GetBeatSnapDistanceAt(double referenceTime); + + /// + /// Converts a duration to a distance. + /// + /// The time of the timing point which resides in. + /// The duration to convert. + /// A value that represents as a distance in the timing point. + float DurationToDistance(double referenceTime, double duration); + + /// + /// Converts a distance to a duration. + /// + /// The time of the timing point which resides in. + /// The distance to convert. + /// A value that represents as a duration in the timing point. + double DistanceToDuration(double referenceTime, float distance); + + /// + /// Converts a distance to a snapped duration. + /// + /// The time of the timing point which resides in. + /// The distance to convert. + /// A value that represents as a duration snapped to the closest beat of the timing point. + double GetSnappedDurationFromDistance(double referenceTime, float distance); + + /// + /// Converts an unsnapped distance to a snapped distance. + /// + /// The time of the timing point which resides in. + /// The distance to convert. + /// A value that represents snapped to the closest beat of the timing point. + float GetSnappedDistanceFromDistance(double referenceTime, float distance); + } +} diff --git a/osu.Game/Screens/Edit/Compose/Components/DistanceSnapGrid.cs b/osu.Game/Screens/Edit/Compose/Components/DistanceSnapGrid.cs index 9eaccc5ac3..d6ee6063ef 100644 --- a/osu.Game/Screens/Edit/Compose/Components/DistanceSnapGrid.cs +++ b/osu.Game/Screens/Edit/Compose/Components/DistanceSnapGrid.cs @@ -45,7 +45,7 @@ namespace osu.Game.Screens.Edit.Compose.Components private BindableBeatDivisor beatDivisor { get; set; } [Resolved] - private HitObjectComposer composer { get; set; } + private IDistanceSnapProvider snapProvider { get; set; } private readonly Cached gridCache = new Cached(); private readonly HitObject hitObject; @@ -73,7 +73,7 @@ namespace osu.Game.Screens.Edit.Compose.Components private void updateSpacing() { - DistanceSpacing = composer.GetBeatSnapDistanceAt(StartTime); + DistanceSpacing = snapProvider.GetBeatSnapDistanceAt(StartTime); gridCache.Invalidate(); } From ae011e8ee82db586ae808a0b8c10d6d44242f77e Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 25 Oct 2019 17:25:46 +0900 Subject: [PATCH 21/28] Fix distance snap grid test scenes --- .../TestSceneOsuDistanceSnapGrid.cs | 99 +++++---------- .../Editor/TestSceneDistanceSnapGrid.cs | 117 +++++------------- .../Components/CircularDistanceSnapGrid.cs | 7 +- .../Compose/Components/DistanceSnapGrid.cs | 8 +- 4 files changed, 63 insertions(+), 168 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneOsuDistanceSnapGrid.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuDistanceSnapGrid.cs index 7b28cc9c50..b6907cf7c0 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneOsuDistanceSnapGrid.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuDistanceSnapGrid.cs @@ -11,6 +11,7 @@ using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Events; using osu.Framework.MathUtils; using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Osu.Beatmaps; using osu.Game.Rulesets.Osu.Edit; using osu.Game.Rulesets.Osu.Objects; @@ -38,26 +39,34 @@ namespace osu.Game.Rulesets.Osu.Tests [Cached] private readonly BindableBeatDivisor beatDivisor = new BindableBeatDivisor(); - private TestOsuDistanceSnapGrid grid; + [Cached(typeof(IDistanceSnapProvider))] + private readonly SnapProvider snapProvider = new SnapProvider(); + + private readonly TestOsuDistanceSnapGrid grid; public TestSceneOsuDistanceSnapGrid() { editorBeatmap = new EditorBeatmap(new OsuBeatmap()); - createGrid(); + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.SlateGray + }, + grid = new TestOsuDistanceSnapGrid(new HitCircle { Position = grid_position }), + new SnappingCursorContainer { GetSnapPosition = v => grid.GetSnappedPosition(grid.ToLocalSpace(v)).position } + }; } [SetUp] public void Setup() => Schedule(() => { - Clear(); - editorBeatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier = 1; editorBeatmap.ControlPointInfo.DifficultyPoints.Clear(); editorBeatmap.ControlPointInfo.TimingPoints.Clear(); editorBeatmap.ControlPointInfo.TimingPoints.Add(new TimingControlPoint { BeatLength = beat_length }); - - beatDivisor.Value = 1; }); [TestCase(1)] @@ -71,53 +80,11 @@ namespace osu.Game.Rulesets.Osu.Tests public void TestBeatDivisor(int divisor) { AddStep($"set beat divisor = {divisor}", () => beatDivisor.Value = divisor); - createGrid(); - } - - [TestCase(100, 100)] - [TestCase(200, 100)] - public void TestBeatLength(float beatLength, float expectedSpacing) - { - AddStep($"set beat length = {beatLength}", () => - { - editorBeatmap.ControlPointInfo.TimingPoints.Clear(); - editorBeatmap.ControlPointInfo.TimingPoints.Add(new TimingControlPoint { BeatLength = beatLength }); - }); - - createGrid(); - AddAssert($"spacing = {expectedSpacing}", () => Precision.AlmostEquals(expectedSpacing, grid.DistanceSpacing)); - } - - [TestCase(0.5f, 50)] - [TestCase(1, 100)] - [TestCase(1.5f, 150)] - public void TestSpeedMultiplier(float multiplier, float expectedSpacing) - { - AddStep($"set speed multiplier = {multiplier}", () => - { - editorBeatmap.ControlPointInfo.DifficultyPoints.Clear(); - editorBeatmap.ControlPointInfo.DifficultyPoints.Add(new DifficultyControlPoint { SpeedMultiplier = multiplier }); - }); - - createGrid(); - AddAssert($"spacing = {expectedSpacing}", () => Precision.AlmostEquals(expectedSpacing, grid.DistanceSpacing)); - } - - [TestCase(0.5f, 50)] - [TestCase(1, 100)] - [TestCase(1.5f, 150)] - public void TestSliderMultiplier(float multiplier, float expectedSpacing) - { - AddStep($"set speed multiplier = {multiplier}", () => editorBeatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier = multiplier); - createGrid(); - AddAssert($"spacing = {expectedSpacing}", () => Precision.AlmostEquals(expectedSpacing, grid.DistanceSpacing)); } [Test] public void TestCursorInCentre() { - createGrid(); - AddStep("move mouse to centre", () => InputManager.MoveMouseTo(grid.ToScreenSpace(grid_position))); assertSnappedDistance((float)beat_length); } @@ -125,8 +92,6 @@ namespace osu.Game.Rulesets.Osu.Tests [Test] public void TestCursorBeforeMovementPoint() { - createGrid(); - AddStep("move mouse to just before movement point", () => InputManager.MoveMouseTo(grid.ToScreenSpace(grid_position + new Vector2((float)beat_length, 0) * 1.49f))); assertSnappedDistance((float)beat_length); } @@ -134,8 +99,6 @@ namespace osu.Game.Rulesets.Osu.Tests [Test] public void TestCursorAfterMovementPoint() { - createGrid(); - AddStep("move mouse to just after movement point", () => InputManager.MoveMouseTo(grid.ToScreenSpace(grid_position + new Vector2((float)beat_length, 0) * 1.51f))); assertSnappedDistance((float)beat_length * 2); } @@ -147,23 +110,6 @@ namespace osu.Game.Rulesets.Osu.Tests return Precision.AlmostEquals(expectedDistance, Vector2.Distance(snappedPosition, grid_position)); }); - private void createGrid() - { - AddStep("create grid", () => - { - Children = new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = Color4.SlateGray - }, - grid = new TestOsuDistanceSnapGrid(new HitCircle { Position = grid_position }), - new SnappingCursorContainer { GetSnapPosition = v => grid.GetSnappedPosition(grid.ToLocalSpace(v)).position } - }; - }); - } - private class SnappingCursorContainer : CompositeDrawable { public Func GetSnapPosition; @@ -212,5 +158,20 @@ namespace osu.Game.Rulesets.Osu.Tests { } } + + private class SnapProvider : IDistanceSnapProvider + { + public (Vector2 position, double time) GetSnappedPosition(Vector2 position, double time) => (position, time); + + public float GetBeatSnapDistanceAt(double referenceTime) => (float)beat_length; + + public float DurationToDistance(double referenceTime, double duration) => 0; + + public double DistanceToDuration(double referenceTime, float distance) => 0; + + public double GetSnappedDurationFromDistance(double referenceTime, float distance) => 0; + + public float GetSnappedDistanceFromDistance(double referenceTime, float distance) => 0; + } } } diff --git a/osu.Game.Tests/Visual/Editor/TestSceneDistanceSnapGrid.cs b/osu.Game.Tests/Visual/Editor/TestSceneDistanceSnapGrid.cs index a67ba4d9e4..558e1106a7 100644 --- a/osu.Game.Tests/Visual/Editor/TestSceneDistanceSnapGrid.cs +++ b/osu.Game.Tests/Visual/Editor/TestSceneDistanceSnapGrid.cs @@ -1,13 +1,12 @@ // 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 NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Shapes; -using osu.Framework.MathUtils; using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Osu.Beatmaps; using osu.Game.Rulesets.Osu.Objects; @@ -26,27 +25,25 @@ namespace osu.Game.Tests.Visual.Editor [Cached(typeof(IEditorBeatmap))] private readonly EditorBeatmap editorBeatmap; - private TestDistanceSnapGrid grid; + [Cached(typeof(IDistanceSnapProvider))] + private readonly SnapProvider snapProvider = new SnapProvider(); public TestSceneDistanceSnapGrid() { editorBeatmap = new EditorBeatmap(new OsuBeatmap()); editorBeatmap.ControlPointInfo.TimingPoints.Add(new TimingControlPoint { BeatLength = beat_length }); - createGrid(); + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.SlateGray + }, + new TestDistanceSnapGrid(new HitObject(), grid_position) + }; } - [SetUp] - public void Setup() => Schedule(() => - { - Clear(); - - editorBeatmap.ControlPointInfo.TimingPoints.Clear(); - editorBeatmap.ControlPointInfo.TimingPoints.Add(new TimingControlPoint { BeatLength = beat_length }); - - BeatDivisor.Value = 1; - }); - [TestCase(1)] [TestCase(2)] [TestCase(3)] @@ -55,82 +52,9 @@ namespace osu.Game.Tests.Visual.Editor [TestCase(8)] [TestCase(12)] [TestCase(16)] - public void TestInitialBeatDivisor(int divisor) + public void TestBeatDivisor(int divisor) { AddStep($"set beat divisor = {divisor}", () => BeatDivisor.Value = divisor); - createGrid(); - - float expectedDistance = (float)beat_length / divisor; - AddAssert($"spacing is {expectedDistance}", () => Precision.AlmostEquals(grid.DistanceSpacing, expectedDistance)); - } - - [Test] - public void TestChangeBeatDivisor() - { - createGrid(); - AddStep("set beat divisor = 2", () => BeatDivisor.Value = 2); - - const float expected_distance = (float)beat_length / 2; - AddAssert($"spacing is {expected_distance}", () => Precision.AlmostEquals(grid.DistanceSpacing, expected_distance)); - } - - [TestCase(100)] - [TestCase(200)] - public void TestBeatLength(double beatLength) - { - AddStep($"set beat length = {beatLength}", () => - { - editorBeatmap.ControlPointInfo.TimingPoints.Clear(); - editorBeatmap.ControlPointInfo.TimingPoints.Add(new TimingControlPoint { BeatLength = beatLength }); - }); - - createGrid(); - AddAssert($"spacing is {beatLength}", () => Precision.AlmostEquals(grid.DistanceSpacing, beatLength)); - } - - [TestCase(1)] - [TestCase(2)] - public void TestGridVelocity(float velocity) - { - // Todo: - - // createGrid(g => g.Velocity = velocity); - // - // float expectedDistance = (float)beat_length * velocity; - // AddAssert($"spacing is {expectedDistance}", () => Precision.AlmostEquals(grid.DistanceSpacing, expectedDistance)); - } - - [Test] - public void TestGetSnappedTime() - { - //Todo: - - // createGrid(); - // - // Vector2 snapPosition = Vector2.Zero; - // AddStep("get first tick position", () => snapPosition = grid_position + new Vector2((float)beat_length, 0)); - // AddAssert("snap time is 1 beat away", () => Precision.AlmostEquals(beat_length, grid.GetSnappedPosition(snapPosition).time, 0.01)); - // - // createGrid(g => g.Velocity = 2, "with velocity = 2"); - // AddAssert("snap time is now 0.5 beats away", () => Precision.AlmostEquals(beat_length / 2, grid.GetSnappedPosition(snapPosition).time, 0.01)); - } - - private void createGrid(Action func = null, string description = null) - { - AddStep($"create grid {description ?? string.Empty}", () => - { - Children = new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = Color4.SlateGray - }, - grid = new TestDistanceSnapGrid(new HitObject(), grid_position) - }; - - func?.Invoke(grid); - }); } private class TestDistanceSnapGrid : DistanceSnapGrid @@ -207,5 +131,20 @@ namespace osu.Game.Tests.Visual.Editor public override (Vector2 position, double time) GetSnappedPosition(Vector2 screenSpacePosition) => (Vector2.Zero, 0); } + + private class SnapProvider : IDistanceSnapProvider + { + public (Vector2 position, double time) GetSnappedPosition(Vector2 position, double time) => (position, time); + + public float GetBeatSnapDistanceAt(double referenceTime) => 10; + + public float DurationToDistance(double referenceTime, double duration) => 0; + + public double DistanceToDuration(double referenceTime, float distance) => 0; + + public double GetSnappedDurationFromDistance(double referenceTime, float distance) => 0; + + public float GetSnappedDistanceFromDistance(double referenceTime, float distance) => 0; + } } } diff --git a/osu.Game/Screens/Edit/Compose/Components/CircularDistanceSnapGrid.cs b/osu.Game/Screens/Edit/Compose/Components/CircularDistanceSnapGrid.cs index 7e9e6e2290..f45115e1e4 100644 --- a/osu.Game/Screens/Edit/Compose/Components/CircularDistanceSnapGrid.cs +++ b/osu.Game/Screens/Edit/Compose/Components/CircularDistanceSnapGrid.cs @@ -2,11 +2,9 @@ // See the LICENCE file in the repository root for full licence text. using System; -using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.UserInterface; -using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Objects; using osuTK; @@ -14,9 +12,6 @@ namespace osu.Game.Screens.Edit.Compose.Components { public abstract class CircularDistanceSnapGrid : DistanceSnapGrid { - [Resolved] - private HitObjectComposer composer { get; set; } - protected CircularDistanceSnapGrid(HitObject hitObject, Vector2 centrePosition) : base(hitObject, centrePosition) { @@ -83,7 +78,7 @@ namespace osu.Game.Screens.Edit.Compose.Components Vector2 normalisedDirection = direction * new Vector2(1f / distance); Vector2 snappedPosition = CentrePosition + normalisedDirection * radialCount * radius; - return (snappedPosition, StartTime + composer.GetSnappedDurationFromDistance(StartTime, (snappedPosition - CentrePosition).Length)); + return (snappedPosition, StartTime + SnapProvider.GetSnappedDurationFromDistance(StartTime, (snappedPosition - CentrePosition).Length)); } } } diff --git a/osu.Game/Screens/Edit/Compose/Components/DistanceSnapGrid.cs b/osu.Game/Screens/Edit/Compose/Components/DistanceSnapGrid.cs index d6ee6063ef..193474093f 100644 --- a/osu.Game/Screens/Edit/Compose/Components/DistanceSnapGrid.cs +++ b/osu.Game/Screens/Edit/Compose/Components/DistanceSnapGrid.cs @@ -38,15 +38,15 @@ namespace osu.Game.Screens.Edit.Compose.Components [Resolved] protected OsuColour Colours { get; private set; } + [Resolved] + protected IDistanceSnapProvider SnapProvider { get; private set; } + [Resolved] private IEditorBeatmap beatmap { get; set; } [Resolved] private BindableBeatDivisor beatDivisor { get; set; } - [Resolved] - private IDistanceSnapProvider snapProvider { get; set; } - private readonly Cached gridCache = new Cached(); private readonly HitObject hitObject; @@ -73,7 +73,7 @@ namespace osu.Game.Screens.Edit.Compose.Components private void updateSpacing() { - DistanceSpacing = snapProvider.GetBeatSnapDistanceAt(StartTime); + DistanceSpacing = SnapProvider.GetBeatSnapDistanceAt(StartTime); gridCache.Invalidate(); } From ccc45dea206a183a127f9ad41116c27774183ff1 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 25 Oct 2019 18:19:26 +0900 Subject: [PATCH 22/28] Add hitobject composer snapping test --- ...tSceneHitObjectComposerDistanceSnapping.cs | 195 ++++++++++++++++++ 1 file changed, 195 insertions(+) create mode 100644 osu.Game.Tests/Editor/TestSceneHitObjectComposerDistanceSnapping.cs diff --git a/osu.Game.Tests/Editor/TestSceneHitObjectComposerDistanceSnapping.cs b/osu.Game.Tests/Editor/TestSceneHitObjectComposerDistanceSnapping.cs new file mode 100644 index 0000000000..e6b0cf992a --- /dev/null +++ b/osu.Game.Tests/Editor/TestSceneHitObjectComposerDistanceSnapping.cs @@ -0,0 +1,195 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using NUnit.Framework; +using osu.Framework.Testing; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Rulesets.Osu; +using osu.Game.Rulesets.Osu.Edit; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Screens.Edit; +using osu.Game.Tests.Visual; + +namespace osu.Game.Tests.Editor +{ + [HeadlessTest] + public class TestSceneHitObjectComposerDistanceSnapping : EditorClockTestScene + { + private TestHitObjectComposer composer; + + [SetUp] + public void Setup() => Schedule(() => + { + Child = composer = new TestHitObjectComposer(); + + BeatDivisor.Value = 1; + + composer.EditorBeatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier = 1; + composer.EditorBeatmap.ControlPointInfo.DifficultyPoints.Clear(); + composer.EditorBeatmap.ControlPointInfo.TimingPoints.Clear(); + + composer.EditorBeatmap.ControlPointInfo.DifficultyPoints.Add(new DifficultyControlPoint { SpeedMultiplier = 1 }); + composer.EditorBeatmap.ControlPointInfo.TimingPoints.Add(new TimingControlPoint { BeatLength = 1000 }); + }); + + [TestCase(1)] + [TestCase(2)] + public void TestSliderMultiplier(float multiplier) + { + AddStep($"set multiplier = {multiplier}", () => composer.EditorBeatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier = multiplier); + + assertSnapDistance(100 * multiplier); + } + + [TestCase(1)] + [TestCase(2)] + public void TestSpeedMultiplier(float multiplier) + { + AddStep($"set multiplier = {multiplier}", () => + { + composer.EditorBeatmap.ControlPointInfo.DifficultyPoints.Clear(); + composer.EditorBeatmap.ControlPointInfo.DifficultyPoints.Add(new DifficultyControlPoint { SpeedMultiplier = multiplier }); + }); + + assertSnapDistance(100 * multiplier); + } + + [TestCase(1)] + [TestCase(2)] + public void TestBeatDivisor(int divisor) + { + AddStep($"set divisor = {divisor}", () => BeatDivisor.Value = divisor); + + assertSnapDistance(100f / divisor); + } + + [Test] + public void TestConvertDurationToDistance() + { + assertDurationToDistance(500, 50); + assertDurationToDistance(1000, 100); + + AddStep("set slider multiplier = 2", () => composer.EditorBeatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier = 2); + + assertDurationToDistance(500, 100); + assertDurationToDistance(1000, 200); + + AddStep("set beat length = 500", () => + { + composer.EditorBeatmap.ControlPointInfo.TimingPoints.Clear(); + composer.EditorBeatmap.ControlPointInfo.TimingPoints.Add(new TimingControlPoint { BeatLength = 500 }); + }); + + assertDurationToDistance(500, 200); + assertDurationToDistance(1000, 400); + } + + [Test] + public void TestConvertDistanceToDuration() + { + assertDistanceToDuration(50, 500); + assertDistanceToDuration(100, 1000); + + AddStep("set slider multiplier = 2", () => composer.EditorBeatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier = 2); + + assertDistanceToDuration(100, 500); + assertDistanceToDuration(200, 1000); + + AddStep("set beat length = 500", () => + { + composer.EditorBeatmap.ControlPointInfo.TimingPoints.Clear(); + composer.EditorBeatmap.ControlPointInfo.TimingPoints.Add(new TimingControlPoint { BeatLength = 500 }); + }); + + assertDistanceToDuration(200, 500); + assertDistanceToDuration(400, 1000); + } + + [Test] + public void TestGetSnappedDurationFromDistance() + { + assertSnappedDuration(50, 0); + assertSnappedDuration(100, 1000); + assertSnappedDuration(150, 1000); + assertSnappedDuration(200, 2000); + assertSnappedDuration(250, 2000); + + AddStep("set slider multiplier = 2", () => composer.EditorBeatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier = 2); + + assertSnappedDuration(50, 0); + assertSnappedDuration(100, 0); + assertSnappedDuration(150, 0); + assertSnappedDuration(200, 1000); + assertSnappedDuration(250, 1000); + + AddStep("set beat length = 500", () => + { + composer.EditorBeatmap.ControlPointInfo.TimingPoints.Clear(); + composer.EditorBeatmap.ControlPointInfo.TimingPoints.Add(new TimingControlPoint { BeatLength = 500 }); + }); + + assertSnappedDuration(50, 0); + assertSnappedDuration(100, 0); + assertSnappedDuration(150, 0); + assertSnappedDuration(200, 500); + assertSnappedDuration(250, 500); + assertSnappedDuration(400, 1000); + } + + [Test] + public void GetSnappedDistanceFromDistance() + { + assertSnappedDistance(50, 0); + assertSnappedDistance(100, 100); + assertSnappedDistance(150, 100); + assertSnappedDistance(200, 200); + assertSnappedDistance(250, 200); + + AddStep("set slider multiplier = 2", () => composer.EditorBeatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier = 2); + + assertSnappedDistance(50, 0); + assertSnappedDistance(100, 0); + assertSnappedDistance(150, 0); + assertSnappedDistance(200, 200); + assertSnappedDistance(250, 200); + + AddStep("set beat length = 500", () => + { + composer.EditorBeatmap.ControlPointInfo.TimingPoints.Clear(); + composer.EditorBeatmap.ControlPointInfo.TimingPoints.Add(new TimingControlPoint { BeatLength = 500 }); + }); + + assertSnappedDistance(50, 0); + assertSnappedDistance(100, 0); + assertSnappedDistance(150, 0); + assertSnappedDistance(200, 200); + assertSnappedDistance(250, 200); + assertSnappedDistance(400, 400); + } + + private void assertSnapDistance(float expectedDistance) + => AddAssert($"distance is {expectedDistance}", () => composer.GetBeatSnapDistanceAt(0) == expectedDistance); + + private void assertDurationToDistance(double duration, float expectedDistance) + => AddAssert($"duration = {duration} -> distance = {expectedDistance}", () => composer.DurationToDistance(0, duration) == expectedDistance); + + private void assertDistanceToDuration(float distance, double expectedDuration) + => AddAssert($"distance = {distance} -> duration = {expectedDuration}", () => composer.DistanceToDuration(0, distance) == expectedDuration); + + private void assertSnappedDuration(float distance, double expectedDuration) + => AddAssert($"distance = {distance} -> duration = {expectedDuration} (snapped)", () => composer.GetSnappedDurationFromDistance(0, distance) == expectedDuration); + + private void assertSnappedDistance(float distance, float expectedDistance) + => AddAssert($"distance = {distance} -> distance = {expectedDistance} (snapped)", () => composer.GetSnappedDistanceFromDistance(0, distance) == expectedDistance); + + private class TestHitObjectComposer : OsuHitObjectComposer + { + public new EditorBeatmap EditorBeatmap => base.EditorBeatmap; + + public TestHitObjectComposer() + : base(new OsuRuleset()) + { + } + } + } +} From da6ee05dd689646d5c4831d9c1d7cc149754d184 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 25 Oct 2019 18:37:44 +0900 Subject: [PATCH 23/28] Fix not being able to drag non-snaked sliders --- .../Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs index fdeffc6f8a..ae0492ac3e 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs @@ -38,6 +38,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders public override Vector2 SelectionPoint => HeadBlueprint.SelectionPoint; + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => BodyPiece.ReceivePositionalInputAt(screenSpacePos); + protected virtual SliderCircleSelectionBlueprint CreateCircleSelectionBlueprint(DrawableSlider slider, SliderPosition position) => new SliderCircleSelectionBlueprint(slider, position); } } From a9ec6b256266f6c2c1c579ac61bac1d72ada66ca Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 25 Oct 2019 19:00:10 +0900 Subject: [PATCH 24/28] Fix testcase failure --- osu.Game.Tests/Visual/Editor/TestSceneHitObjectComposer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Editor/TestSceneHitObjectComposer.cs b/osu.Game.Tests/Visual/Editor/TestSceneHitObjectComposer.cs index 0ea73fb3de..b7c7028b52 100644 --- a/osu.Game.Tests/Visual/Editor/TestSceneHitObjectComposer.cs +++ b/osu.Game.Tests/Visual/Editor/TestSceneHitObjectComposer.cs @@ -22,7 +22,7 @@ using osuTK; namespace osu.Game.Tests.Visual.Editor { [TestFixture] - public class TestSceneHitObjectComposer : OsuTestScene + public class TestSceneHitObjectComposer : EditorClockTestScene { public override IReadOnlyList RequiredTypes => new[] { From a724909c251b3175dd33db24904dca582043a00e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 26 Oct 2019 01:43:33 +0200 Subject: [PATCH 25/28] Add temporary mobile report issue template Due to an overwhelming amount of mobile reports that are not actively being worked on (neither by the core team, due to more pressing priorities, nor by external contributors) and take up considerable time to manage, add an issue template that aims to enforce a temporary moratorium on accepting mobile issues. --- .github/ISSUE_TEMPLATE/mobile-issues.md | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/mobile-issues.md diff --git a/.github/ISSUE_TEMPLATE/mobile-issues.md b/.github/ISSUE_TEMPLATE/mobile-issues.md new file mode 100644 index 0000000000..f41562f532 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/mobile-issues.md @@ -0,0 +1,7 @@ +--- +name: Mobile Report +about: ⚠ Due to current development priorities we are currently not accepting mobile reports. +--- + +⚠ **PLEASE READ** ⚠: Due to prioritising finishing the client for desktop first we are not accepting reports related to mobile platforms for the time being. +Please check back in the future when the focus of development shifts towards mobile! From dca8de5e6be35d875fc7f33780a68560a768c807 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 26 Oct 2019 02:06:39 +0200 Subject: [PATCH 26/28] Rephrase template description --- .github/ISSUE_TEMPLATE/mobile-issues.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/mobile-issues.md b/.github/ISSUE_TEMPLATE/mobile-issues.md index f41562f532..6938f18097 100644 --- a/.github/ISSUE_TEMPLATE/mobile-issues.md +++ b/.github/ISSUE_TEMPLATE/mobile-issues.md @@ -1,6 +1,6 @@ --- name: Mobile Report -about: ⚠ Due to current development priorities we are currently not accepting mobile reports. +about: ⚠ Due to current development priorities we are not accepting mobile reports at this time. --- ⚠ **PLEASE READ** ⚠: Due to prioritising finishing the client for desktop first we are not accepting reports related to mobile platforms for the time being. From 654890776d31d75a5bcab4761341752890b39908 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 26 Oct 2019 14:56:29 +0200 Subject: [PATCH 27/28] Add exemption for potential code contributors Add an exemption clause allowing potential code contributors to submit issues if they state they would like to work on them, and note that mobile-related pull requests are still accepted. Suggested-by: Dean Herbert --- .github/ISSUE_TEMPLATE/mobile-issues.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/mobile-issues.md b/.github/ISSUE_TEMPLATE/mobile-issues.md index 6938f18097..f171e80b8b 100644 --- a/.github/ISSUE_TEMPLATE/mobile-issues.md +++ b/.github/ISSUE_TEMPLATE/mobile-issues.md @@ -1,7 +1,8 @@ --- name: Mobile Report -about: ⚠ Due to current development priorities we are not accepting mobile reports at this time. +about: ⚠ Due to current development priorities we are not accepting mobile reports at this time (unless you're willing to fix them yourself!) --- -⚠ **PLEASE READ** ⚠: Due to prioritising finishing the client for desktop first we are not accepting reports related to mobile platforms for the time being. -Please check back in the future when the focus of development shifts towards mobile! +⚠ **PLEASE READ** ⚠: Due to prioritising finishing the client for desktop first we are not accepting reports related to mobile platforms for the time being, unless you're willing to fix them. +If you'd like to report a problem or suggest a feature and then work on it, feel free to open an issue and highlight that you'd like to address it yourself in the issue body; mobile pull requests are also welcome. +Otherwise, please check back in the future when the focus of development shifts towards mobile! From 814b520e5e7bc41a99adbc927b7021f4c6741a6d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 27 Oct 2019 11:35:45 +0900 Subject: [PATCH 28/28] Avoid potential mis-cast in comparison --- osu.Game/Beatmaps/Formats/LegacyDecoder.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Beatmaps/Formats/LegacyDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyDecoder.cs index 26f7209be6..a5a4380d4a 100644 --- a/osu.Game/Beatmaps/Formats/LegacyDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyDecoder.cs @@ -204,8 +204,8 @@ namespace osu.Game.Beatmaps.Formats } public override bool EquivalentTo(ControlPoint other) => - base.EquivalentTo(other) - && CustomSampleBank == ((LegacySampleControlPoint)other).CustomSampleBank; + base.EquivalentTo(other) && other is LegacySampleControlPoint otherTyped && + CustomSampleBank == otherTyped.CustomSampleBank; } } }