From 00250972c34caba4c5f1ab8dbdd29fa0ba80c697 Mon Sep 17 00:00:00 2001 From: Liam DeVoe Date: Thu, 6 Jul 2023 02:31:12 -0400 Subject: [PATCH 001/139] skip frames after a negative frame until the negative time is "paid back" --- osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs b/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs index c6461840aa..f91c96efb6 100644 --- a/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs +++ b/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs @@ -269,6 +269,13 @@ namespace osu.Game.Scoring.Legacy float lastTime = beatmapOffset; ReplayFrame currentFrame = null; + // the negative time amount that must be "paid back" by positive frames before we start including frames again. + // When a negative frame occurs in a replay, all future frames are skipped until the sum total of their times + // is equal to or greater than the time of that negative frame. + // This value will be negative if we are in a time deficit, ie we have a negative frame that must be paid back. + // Otherwise it will be 0. + float timeDeficit = 0; + string[] frames = reader.ReadToEnd().Split(','); for (int i = 0; i < frames.Length; i++) @@ -296,9 +303,13 @@ namespace osu.Game.Scoring.Legacy // ignore these frames as they serve no real purpose (and can even mislead ruleset-specific handlers - see mania) continue; + timeDeficit += diff; + timeDeficit = Math.Min(0, timeDeficit); + + // still paying back the deficit from a negative frame. Skip this frame. // Todo: At some point we probably want to rewind and play back the negative-time frames // but for now we'll achieve equal playback to stable by skipping negative frames - if (diff < 0) + if (timeDeficit < 0) continue; currentFrame = convertFrame(new LegacyReplayFrame(lastTime, From cc6646c82b13e021514a0465b118383d8e96ba7f Mon Sep 17 00:00:00 2001 From: Liam DeVoe Date: Thu, 6 Jul 2023 17:13:33 -0400 Subject: [PATCH 002/139] properly handle negative frame before a break this was causing replay data before the skip to be...skipped. --- osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs | 20 ++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs b/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs index f91c96efb6..63465652e8 100644 --- a/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs +++ b/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs @@ -267,6 +267,7 @@ namespace osu.Game.Scoring.Legacy private void readLegacyReplay(Replay replay, StreamReader reader) { float lastTime = beatmapOffset; + bool skipFramesPresent = false; ReplayFrame currentFrame = null; // the negative time amount that must be "paid back" by positive frames before we start including frames again. @@ -298,18 +299,31 @@ namespace osu.Game.Scoring.Legacy lastTime += diff; if (i < 2 && mouseX == 256 && mouseY == -500) + { // at the start of the replay, stable places two replay frames, at time 0 and SkipBoundary - 1, respectively. // both frames use a position of (256, -500). // ignore these frames as they serve no real purpose (and can even mislead ruleset-specific handlers - see mania) + skipFramesPresent = true; continue; + } - timeDeficit += diff; - timeDeficit = Math.Min(0, timeDeficit); + // if the skip frames inserted by stable are present, the third frame will have a large negative time + // roughly equal to SkipBoundary. We don't want this to count towards the deficit: doing so would cause + // the replay data before the skip to be, well, skipped. + // In other words, this frame, if present, is a different kind of negative frame. It sets the "offset" + // for the beginning of the replay. This is the only negative frame to be handled in such a way. + bool isNegativeBreakFrame = i == 2 && skipFramesPresent && diff < 0; + + if (!isNegativeBreakFrame) + { + timeDeficit += diff; + timeDeficit = Math.Min(0, timeDeficit); + } // still paying back the deficit from a negative frame. Skip this frame. // Todo: At some point we probably want to rewind and play back the negative-time frames // but for now we'll achieve equal playback to stable by skipping negative frames - if (timeDeficit < 0) + if (timeDeficit < 0 || isNegativeBreakFrame) continue; currentFrame = convertFrame(new LegacyReplayFrame(lastTime, From 217b07810fb497f571fadfeb4d015394a43075d0 Mon Sep 17 00:00:00 2001 From: Liam DeVoe Date: Thu, 27 Jul 2023 02:12:21 -0400 Subject: [PATCH 003/139] don't skip the negative break frame investigation reveals this frame is played back by stable --- osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs b/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs index 63465652e8..8b1b24ce95 100644 --- a/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs +++ b/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs @@ -1,4 +1,4 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. #nullable disable @@ -323,7 +323,7 @@ namespace osu.Game.Scoring.Legacy // still paying back the deficit from a negative frame. Skip this frame. // Todo: At some point we probably want to rewind and play back the negative-time frames // but for now we'll achieve equal playback to stable by skipping negative frames - if (timeDeficit < 0 || isNegativeBreakFrame) + if (timeDeficit < 0) continue; currentFrame = convertFrame(new LegacyReplayFrame(lastTime, From a93561cab05807be032e963b37092ff349f12fc1 Mon Sep 17 00:00:00 2001 From: Liam DeVoe Date: Thu, 27 Jul 2023 02:12:43 -0400 Subject: [PATCH 004/139] remove resolved comment --- osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs b/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs index 8b1b24ce95..79224b7d4f 100644 --- a/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs +++ b/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs @@ -321,8 +321,6 @@ namespace osu.Game.Scoring.Legacy } // still paying back the deficit from a negative frame. Skip this frame. - // Todo: At some point we probably want to rewind and play back the negative-time frames - // but for now we'll achieve equal playback to stable by skipping negative frames if (timeDeficit < 0) continue; From 61760f614a9900e5cd71a298a8979ca69d9b8eb0 Mon Sep 17 00:00:00 2001 From: Liam DeVoe Date: Thu, 27 Jul 2023 16:34:18 -0400 Subject: [PATCH 005/139] fix legacy score decode tests for negative frame --- .../Beatmaps/Formats/LegacyScoreDecoderTest.cs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyScoreDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyScoreDecoderTest.cs index 93cda34ef7..89b6d76e54 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyScoreDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyScoreDecoderTest.cs @@ -92,17 +92,20 @@ namespace osu.Game.Tests.Beatmaps.Formats [TestCase(LegacyBeatmapDecoder.LATEST_VERSION, false)] public void TestLegacyBeatmapReplayOffsetsDecode(int beatmapVersion, bool offsetApplied) { - const double first_frame_time = 48; - const double second_frame_time = 65; + const double first_frame_time = 31; + const double second_frame_time = 48; + const double third_frame_time = 65; var decoder = new TestLegacyScoreDecoder(beatmapVersion); using (var resourceStream = TestResources.OpenResource("Replays/mania-replay.osr")) { var score = decoder.Parse(resourceStream); + int offset = offsetApplied ? LegacyBeatmapDecoder.EARLY_VERSION_TIMING_OFFSET : 0; - Assert.That(score.Replay.Frames[0].Time, Is.EqualTo(first_frame_time + (offsetApplied ? LegacyBeatmapDecoder.EARLY_VERSION_TIMING_OFFSET : 0))); - Assert.That(score.Replay.Frames[1].Time, Is.EqualTo(second_frame_time + (offsetApplied ? LegacyBeatmapDecoder.EARLY_VERSION_TIMING_OFFSET : 0))); + Assert.That(score.Replay.Frames[0].Time, Is.EqualTo(first_frame_time + offset)); + Assert.That(score.Replay.Frames[1].Time, Is.EqualTo(second_frame_time + offset)); + Assert.That(score.Replay.Frames[2].Time, Is.EqualTo(third_frame_time + offset)); } } From 7d174dd8bb4998ac264463067b798fae14541f0c Mon Sep 17 00:00:00 2001 From: Liam DeVoe Date: Thu, 27 Jul 2023 17:20:54 -0400 Subject: [PATCH 006/139] dont count any of first three frames towards time deficit --- osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs | 21 ++++++++----------- 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs b/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs index 79224b7d4f..eceaada399 100644 --- a/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs +++ b/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs @@ -267,7 +267,6 @@ namespace osu.Game.Scoring.Legacy private void readLegacyReplay(Replay replay, StreamReader reader) { float lastTime = beatmapOffset; - bool skipFramesPresent = false; ReplayFrame currentFrame = null; // the negative time amount that must be "paid back" by positive frames before we start including frames again. @@ -299,22 +298,20 @@ namespace osu.Game.Scoring.Legacy lastTime += diff; if (i < 2 && mouseX == 256 && mouseY == -500) - { // at the start of the replay, stable places two replay frames, at time 0 and SkipBoundary - 1, respectively. // both frames use a position of (256, -500). // ignore these frames as they serve no real purpose (and can even mislead ruleset-specific handlers - see mania) - skipFramesPresent = true; continue; - } - // if the skip frames inserted by stable are present, the third frame will have a large negative time - // roughly equal to SkipBoundary. We don't want this to count towards the deficit: doing so would cause - // the replay data before the skip to be, well, skipped. - // In other words, this frame, if present, is a different kind of negative frame. It sets the "offset" - // for the beginning of the replay. This is the only negative frame to be handled in such a way. - bool isNegativeBreakFrame = i == 2 && skipFramesPresent && diff < 0; - - if (!isNegativeBreakFrame) + // negative frames are only counted towards the deficit after the very beginning of the replay. + // When the two skip frames are present (see directly above), the third frame will have a large + // negative time roughly equal to SkipBoundary. This shouldn't be counted towards the deficit, otherwise + // any replay data before the skip would be, well, skipped. + // + // On testing against stable it appears that stable ignores the negative time of *any* of the first + // three frames, regardless of if the skip frames are present. Hence the condition here. + // But this may be incorrect and need to be revisited later. + if (i > 2) { timeDeficit += diff; timeDeficit = Math.Min(0, timeDeficit); From 04ef04b9026dfe84c323e3d08204f5a722fb5d74 Mon Sep 17 00:00:00 2001 From: Liam DeVoe Date: Thu, 27 Jul 2023 21:12:08 -0400 Subject: [PATCH 007/139] only ignore the first negative frame among the first 3 replay frames --- osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs b/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs index eceaada399..fdeda24c75 100644 --- a/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs +++ b/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs @@ -267,6 +267,7 @@ namespace osu.Game.Scoring.Legacy private void readLegacyReplay(Replay replay, StreamReader reader) { float lastTime = beatmapOffset; + bool negativeFrameEncounted = false; ReplayFrame currentFrame = null; // the negative time amount that must be "paid back" by positive frames before we start including frames again. @@ -308,15 +309,19 @@ namespace osu.Game.Scoring.Legacy // negative time roughly equal to SkipBoundary. This shouldn't be counted towards the deficit, otherwise // any replay data before the skip would be, well, skipped. // - // On testing against stable it appears that stable ignores the negative time of *any* of the first - // three frames, regardless of if the skip frames are present. Hence the condition here. - // But this may be incorrect and need to be revisited later. - if (i > 2) + // On testing against stable, it appears that stable ignores the negative time of only the first + // negative frame of the first three replay frames, regardless of if the skip frames are present. + // Hence the condition here. + // But there is a possibility this is incorrect and may need to be revisited later. + if (i > 2 || negativeFrameEncounted) { timeDeficit += diff; timeDeficit = Math.Min(0, timeDeficit); } + if (diff < 0) + negativeFrameEncounted = true; + // still paying back the deficit from a negative frame. Skip this frame. if (timeDeficit < 0) continue; From 4060373e0399d3d0cd80639050fcbf06e03dc0ae Mon Sep 17 00:00:00 2001 From: nanashi-1 Date: Sat, 5 Aug 2023 21:15:31 +0800 Subject: [PATCH 008/139] add rank display element --- osu.Game/Screens/Play/HUD/RankDisplay.cs | 46 ++++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 osu.Game/Screens/Play/HUD/RankDisplay.cs diff --git a/osu.Game/Screens/Play/HUD/RankDisplay.cs b/osu.Game/Screens/Play/HUD/RankDisplay.cs new file mode 100644 index 0000000000..be8841b647 --- /dev/null +++ b/osu.Game/Screens/Play/HUD/RankDisplay.cs @@ -0,0 +1,46 @@ +// 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.Game.Rulesets.Scoring; +using osu.Game.Skinning; +using osu.Framework.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Extensions; + +namespace osu.Game.Screens.Play.HUD +{ + public partial class RankDisplay : FontAdjustableSkinComponent + { + [Resolved] + private ScoreProcessor scoreProcessor { get; set; } = null!; + + private readonly OsuSpriteText text; + + public RankDisplay() + { + AutoSizeAxes = Axes.Both; + + InternalChildren = new Drawable[] + { + text = new OsuSpriteText + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + } + }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + text.Text = scoreProcessor.Rank.Value.GetDescription(); + + scoreProcessor.Rank.BindValueChanged(v => text.Text = v.NewValue.GetDescription()); + } + + protected override void SetFont(FontUsage font) => text.Font = font.With(size: 40); + } +} From c67f6d949bca80561bf82456c1132f6a5dea8403 Mon Sep 17 00:00:00 2001 From: nanashi-1 Date: Sun, 6 Aug 2023 18:15:14 +0800 Subject: [PATCH 009/139] tests --- .../Archives/modified-argon-20230806.osk | Bin 0 -> 1354 bytes osu.Game.Tests/Skins/SkinDeserialisationTest.cs | 4 +++- 2 files changed, 3 insertions(+), 1 deletion(-) create mode 100644 osu.Game.Tests/Resources/Archives/modified-argon-20230806.osk diff --git a/osu.Game.Tests/Resources/Archives/modified-argon-20230806.osk b/osu.Game.Tests/Resources/Archives/modified-argon-20230806.osk new file mode 100644 index 0000000000000000000000000000000000000000..e7c035811d62df429b930fd41548457307cbf90a GIT binary patch literal 1354 zcmWIWW@Zs#U|`^2_)x_b{xqpXate^Q9xTGZP@J8ar42}OmnuF-Rk~{%W2L3)BpF`hq7LuxG?;EX_=A3@+2KLW50%z zCJT1*?Tu&XoUtI*VfhwjufoTAdrWT0Ma{Xkf@$w(9SMn5xn44g8x$8lRNOs3(qd74 z%Jfi^|ABpl0Vfx(=r4_R5}XqAe#WUcQ*y4{IN&>P(dHEU0JzIP0$qM&RRUiT(A6D4 z%nQUImuKdsz#gq{ny90<(fJCWZu^Z35nV4SU;s zezd&&{rb(l6CZWTX0CB?f8})STDef8DX&0-clIHVq~7&z*EpW3q-AL+#q+qH5nYgJ zvuyS;c?VC{_qW5o8TKyp>l6GXlhoQW^O(vvN!~iABF$+Be%qw2$$KZ^)3U@X{KluV zAFX*U)-Jnh)Sa;?@cZP{{EfZ~ue1iJ=uZv0$-0a&G2qsM8Pleh$E~n?aL_4X)&OEvgYy;j{w*|8v}ZGzaIeJgDh3p?^IJ1ESF zGT)sN8N0h{)!T)llX3-E3@0gAZY}Elo??03He!zw^Q84#%@-FfE%o^CtvPq`Y8$pg zSCYP5__pSq&1TE$)K6i#i}(ygYP2dW90ip;D#MOSo%?9AdF{Q|rrEbr7rkBBULB-& zWUaVTq;82zB2RwC37_z_-gepkxyRe&*4fsU&hx&LH~;#yyxk>lwrQuGF$|3tWc|ma zJAcbQfjiE2pAt&Fd|$~NUsC@#l0&fIso|-E!Q4_hJLh#pHne=1BslE-~E|J=o2m$7s89QqI{qdu7~e5_aE^w!dV5Of5&o z%suLV`NPbzhb#qp21+&!oeN@4YRUY)bbQ9XTUV_eL*D%;cl`0`ioSyG>dU2b`hNdm zU6LCeyZy1r?)PtUHsoIsS3e$8@YTvc?Y=pGep(76YbrMkWyk z+=U3xSO{nYQLrKfSr@hp4Ale7G`pa>;Mo~nD|+@qXboq?mD$kEKo1Cn86TO^gC)S5 Rl?^1%0)!udv@;8c2LR#77DE64 literal 0 HcmV?d00001 diff --git a/osu.Game.Tests/Skins/SkinDeserialisationTest.cs b/osu.Game.Tests/Skins/SkinDeserialisationTest.cs index 82d204f134..72581f5513 100644 --- a/osu.Game.Tests/Skins/SkinDeserialisationTest.cs +++ b/osu.Game.Tests/Skins/SkinDeserialisationTest.cs @@ -52,7 +52,9 @@ namespace osu.Game.Tests.Skins // Covers player avatar and flag. "Archives/modified-argon-20230305.osk", // Covers key counters - "Archives/modified-argon-pro-20230618.osk" + "Archives/modified-argon-pro-20230618.osk", + // Covers rank display + "Archives/modified-argon-20230806.osk" }; /// From 92bf363ecf499a8a4b6ca9b56b63dd6024c40877 Mon Sep 17 00:00:00 2001 From: nanashi-1 Date: Sun, 6 Aug 2023 20:43:09 +0800 Subject: [PATCH 010/139] use Drawable Rank --- .../Screens/Play/HUD/DefaultRankDisplay.cs | 40 ++++++++++++++++ .../Screens/Play/HUD/GameplayRankDisplay.cs | 14 ++++++ osu.Game/Screens/Play/HUD/RankDisplay.cs | 46 ------------------- 3 files changed, 54 insertions(+), 46 deletions(-) create mode 100644 osu.Game/Screens/Play/HUD/DefaultRankDisplay.cs create mode 100644 osu.Game/Screens/Play/HUD/GameplayRankDisplay.cs delete mode 100644 osu.Game/Screens/Play/HUD/RankDisplay.cs diff --git a/osu.Game/Screens/Play/HUD/DefaultRankDisplay.cs b/osu.Game/Screens/Play/HUD/DefaultRankDisplay.cs new file mode 100644 index 0000000000..a686b6d9fb --- /dev/null +++ b/osu.Game/Screens/Play/HUD/DefaultRankDisplay.cs @@ -0,0 +1,40 @@ +// 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.Online.Leaderboards; +using osu.Game.Rulesets.Scoring; +using osuTK; + + +namespace osu.Game.Screens.Play.HUD +{ + public partial class DefaultRankDisplay : GameplayRankDisplay + { + [Resolved] + private ScoreProcessor scoreProcessor { get; set; } = null!; + + private UpdateableRank rank; + + public DefaultRankDisplay() + { + Size = new Vector2(70, 35); + + InternalChildren = new Drawable[] { + rank = new UpdateableRank(Scoring.ScoreRank.X) { + RelativeSizeAxes = Axes.Both + }, + }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + rank.Rank = scoreProcessor.Rank.Value; + + scoreProcessor.Rank.BindValueChanged(v => rank.Rank = v.NewValue); + } + } +} \ No newline at end of file diff --git a/osu.Game/Screens/Play/HUD/GameplayRankDisplay.cs b/osu.Game/Screens/Play/HUD/GameplayRankDisplay.cs new file mode 100644 index 0000000000..402a733abd --- /dev/null +++ b/osu.Game/Screens/Play/HUD/GameplayRankDisplay.cs @@ -0,0 +1,14 @@ +// 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.Skinning; +using osu.Framework.Graphics.Containers; + + +namespace osu.Game.Screens.Play.HUD +{ + public abstract partial class GameplayRankDisplay : Container, ISerialisableDrawable + { + public bool UsesFixedAnchor { get; set; } + } +} diff --git a/osu.Game/Screens/Play/HUD/RankDisplay.cs b/osu.Game/Screens/Play/HUD/RankDisplay.cs deleted file mode 100644 index be8841b647..0000000000 --- a/osu.Game/Screens/Play/HUD/RankDisplay.cs +++ /dev/null @@ -1,46 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using osu.Framework.Allocation; -using osu.Game.Rulesets.Scoring; -using osu.Game.Skinning; -using osu.Framework.Graphics; -using osu.Game.Graphics.Sprites; -using osu.Framework.Graphics.Sprites; -using osu.Framework.Extensions; - -namespace osu.Game.Screens.Play.HUD -{ - public partial class RankDisplay : FontAdjustableSkinComponent - { - [Resolved] - private ScoreProcessor scoreProcessor { get; set; } = null!; - - private readonly OsuSpriteText text; - - public RankDisplay() - { - AutoSizeAxes = Axes.Both; - - InternalChildren = new Drawable[] - { - text = new OsuSpriteText - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - } - }; - } - - protected override void LoadComplete() - { - base.LoadComplete(); - - text.Text = scoreProcessor.Rank.Value.GetDescription(); - - scoreProcessor.Rank.BindValueChanged(v => text.Text = v.NewValue.GetDescription()); - } - - protected override void SetFont(FontUsage font) => text.Font = font.With(size: 40); - } -} From f3f7d1ba7c97ae6ca35092df6987fbed0b17b70a Mon Sep 17 00:00:00 2001 From: nanashi-1 Date: Mon, 7 Aug 2023 09:50:24 +0800 Subject: [PATCH 011/139] remove measly abstract --- osu.Game/Screens/Play/HUD/DefaultRankDisplay.cs | 7 +++++-- osu.Game/Screens/Play/HUD/GameplayRankDisplay.cs | 14 -------------- 2 files changed, 5 insertions(+), 16 deletions(-) delete mode 100644 osu.Game/Screens/Play/HUD/GameplayRankDisplay.cs diff --git a/osu.Game/Screens/Play/HUD/DefaultRankDisplay.cs b/osu.Game/Screens/Play/HUD/DefaultRankDisplay.cs index a686b6d9fb..433acf678a 100644 --- a/osu.Game/Screens/Play/HUD/DefaultRankDisplay.cs +++ b/osu.Game/Screens/Play/HUD/DefaultRankDisplay.cs @@ -3,19 +3,22 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Game.Online.Leaderboards; using osu.Game.Rulesets.Scoring; +using osu.Game.Skinning; using osuTK; namespace osu.Game.Screens.Play.HUD { - public partial class DefaultRankDisplay : GameplayRankDisplay + public partial class DefaultRankDisplay : Container, ISerialisableDrawable { [Resolved] private ScoreProcessor scoreProcessor { get; set; } = null!; + public bool UsesFixedAnchor { get; set; } - private UpdateableRank rank; + private readonly UpdateableRank rank; public DefaultRankDisplay() { diff --git a/osu.Game/Screens/Play/HUD/GameplayRankDisplay.cs b/osu.Game/Screens/Play/HUD/GameplayRankDisplay.cs deleted file mode 100644 index 402a733abd..0000000000 --- a/osu.Game/Screens/Play/HUD/GameplayRankDisplay.cs +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using osu.Game.Skinning; -using osu.Framework.Graphics.Containers; - - -namespace osu.Game.Screens.Play.HUD -{ - public abstract partial class GameplayRankDisplay : Container, ISerialisableDrawable - { - public bool UsesFixedAnchor { get; set; } - } -} From eb3d3b51e204fef93fdd80e59218e8fb261ae275 Mon Sep 17 00:00:00 2001 From: nanashi-1 Date: Mon, 7 Aug 2023 09:50:44 +0800 Subject: [PATCH 012/139] add legacy rank display --- osu.Game/Skinning/LegacyRankDisplay.cs | 47 ++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 osu.Game/Skinning/LegacyRankDisplay.cs diff --git a/osu.Game/Skinning/LegacyRankDisplay.cs b/osu.Game/Skinning/LegacyRankDisplay.cs new file mode 100644 index 0000000000..0ae3b45107 --- /dev/null +++ b/osu.Game/Skinning/LegacyRankDisplay.cs @@ -0,0 +1,47 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +#nullable disable + +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Graphics.Textures; +using osu.Game.Graphics.Sprites; +using osu.Game.Rulesets.Scoring; + +namespace osu.Game.Skinning +{ + public partial class LegacyRankDisplay : CompositeDrawable, ISerialisableDrawable + { + public bool UsesFixedAnchor { get; set; } + + [Resolved] + private ScoreProcessor scoreProcessor { get; set; } = null!; + + [Resolved] + private ISkinSource source { get; set; } = null!; + + private readonly Sprite rank; + + public LegacyRankDisplay() + { + AutoSizeAxes = Axes.Both; + + AddInternal(rank = new Sprite()); + } + + protected override void LoadComplete() + { + + var skin = source.FindProvider(s => getTexture(s, "A") != null); + + rank.Texture = getTexture(skin, scoreProcessor.Rank.Value.ToString()); + + scoreProcessor.Rank.BindValueChanged(v => rank.Texture = getTexture(skin, v.NewValue.ToString())); + } + + private static Texture getTexture(ISkin skin, string name) => skin?.GetTexture($"ranking-{name}"); + } +} \ No newline at end of file From 9bdff29dd72c24b7b48292d7f7312363a16a1e2e Mon Sep 17 00:00:00 2001 From: nanashi-1 Date: Mon, 7 Aug 2023 09:50:54 +0800 Subject: [PATCH 013/139] add visual test --- .../Gameplay/TestSceneSkinnableRankDisplay.cs | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableRankDisplay.cs diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableRankDisplay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableRankDisplay.cs new file mode 100644 index 0000000000..dc8b3d994b --- /dev/null +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableRankDisplay.cs @@ -0,0 +1,37 @@ +// 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.Allocation; +using osu.Framework.Graphics; +using osu.Game.Rulesets.Osu; +using osu.Game.Rulesets.Scoring; +using osu.Game.Screens.Play.HUD; +using osu.Game.Skinning; + +namespace osu.Game.Tests.Visual.Gameplay +{ + public partial class TestSceneSkinnableRankDisplay : SkinnableHUDComponentTestScene + { + [Cached] + private ScoreProcessor scoreProcessor = new ScoreProcessor(new OsuRuleset()); + + protected override Drawable CreateDefaultImplementation() => new DefaultRankDisplay(); + + protected override Drawable CreateLegacyImplementation() => new LegacyRankDisplay(); + + [Test] + public void TestChangingRank() + { + AddStep("Set rank to SS Hidden", () => scoreProcessor.Rank.Value = Scoring.ScoreRank.XH); + AddStep("Set rank to SS", () => scoreProcessor.Rank.Value = Scoring.ScoreRank.X); + AddStep("Set rank to S Hidden", () => scoreProcessor.Rank.Value = Scoring.ScoreRank.SH); + AddStep("Set rank to S", () => scoreProcessor.Rank.Value = Scoring.ScoreRank.S); + AddStep("Set rank to A", () => scoreProcessor.Rank.Value = Scoring.ScoreRank.A); + AddStep("Set rank to B", () => scoreProcessor.Rank.Value = Scoring.ScoreRank.B); + AddStep("Set rank to C", () => scoreProcessor.Rank.Value = Scoring.ScoreRank.C); + AddStep("Set rank to D", () => scoreProcessor.Rank.Value = Scoring.ScoreRank.D); + AddStep("Set rank to F", () => scoreProcessor.Rank.Value = Scoring.ScoreRank.F); + } + } +} \ No newline at end of file From bd67e933105435ee161d8b639cd7221fec88e003 Mon Sep 17 00:00:00 2001 From: nanashi-1 Date: Mon, 7 Aug 2023 11:16:51 +0800 Subject: [PATCH 014/139] fix code format --- osu.Game/Screens/Play/HUD/DefaultRankDisplay.cs | 10 ++++++---- osu.Game/Skinning/LegacyRankDisplay.cs | 2 -- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/DefaultRankDisplay.cs b/osu.Game/Screens/Play/HUD/DefaultRankDisplay.cs index 433acf678a..09ab7d156c 100644 --- a/osu.Game/Screens/Play/HUD/DefaultRankDisplay.cs +++ b/osu.Game/Screens/Play/HUD/DefaultRankDisplay.cs @@ -9,13 +9,13 @@ using osu.Game.Rulesets.Scoring; using osu.Game.Skinning; using osuTK; - namespace osu.Game.Screens.Play.HUD { public partial class DefaultRankDisplay : Container, ISerialisableDrawable { [Resolved] private ScoreProcessor scoreProcessor { get; set; } = null!; + public bool UsesFixedAnchor { get; set; } private readonly UpdateableRank rank; @@ -24,11 +24,13 @@ namespace osu.Game.Screens.Play.HUD { Size = new Vector2(70, 35); - InternalChildren = new Drawable[] { - rank = new UpdateableRank(Scoring.ScoreRank.X) { + InternalChildren = new Drawable[] + { + rank = new UpdateableRank(Scoring.ScoreRank.X) + { RelativeSizeAxes = Axes.Both }, - }; + }; } protected override void LoadComplete() diff --git a/osu.Game/Skinning/LegacyRankDisplay.cs b/osu.Game/Skinning/LegacyRankDisplay.cs index 0ae3b45107..83d4299360 100644 --- a/osu.Game/Skinning/LegacyRankDisplay.cs +++ b/osu.Game/Skinning/LegacyRankDisplay.cs @@ -8,7 +8,6 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; -using osu.Game.Graphics.Sprites; using osu.Game.Rulesets.Scoring; namespace osu.Game.Skinning @@ -34,7 +33,6 @@ namespace osu.Game.Skinning protected override void LoadComplete() { - var skin = source.FindProvider(s => getTexture(s, "A") != null); rank.Texture = getTexture(skin, scoreProcessor.Rank.Value.ToString()); From 07b4f6115b8d35fbb9a03a6399846e5bbeef9db9 Mon Sep 17 00:00:00 2001 From: nanashi-1 Date: Mon, 7 Aug 2023 20:26:32 +0800 Subject: [PATCH 015/139] use small --- osu.Game/Skinning/LegacyRankDisplay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Skinning/LegacyRankDisplay.cs b/osu.Game/Skinning/LegacyRankDisplay.cs index 83d4299360..cd121deb8c 100644 --- a/osu.Game/Skinning/LegacyRankDisplay.cs +++ b/osu.Game/Skinning/LegacyRankDisplay.cs @@ -40,6 +40,6 @@ namespace osu.Game.Skinning scoreProcessor.Rank.BindValueChanged(v => rank.Texture = getTexture(skin, v.NewValue.ToString())); } - private static Texture getTexture(ISkin skin, string name) => skin?.GetTexture($"ranking-{name}"); + private static Texture getTexture(ISkin skin, string name) => skin?.GetTexture($"ranking-{name}-small"); } } \ No newline at end of file From fed338a42b12a248b902f2ea1389f06fcec0ec70 Mon Sep 17 00:00:00 2001 From: nanashi-1 Date: Wed, 9 Aug 2023 07:34:30 +0800 Subject: [PATCH 016/139] improve code --- osu.Game/Skinning/LegacyRankDisplay.cs | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/osu.Game/Skinning/LegacyRankDisplay.cs b/osu.Game/Skinning/LegacyRankDisplay.cs index cd121deb8c..b663f52097 100644 --- a/osu.Game/Skinning/LegacyRankDisplay.cs +++ b/osu.Game/Skinning/LegacyRankDisplay.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. -#nullable disable - using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -33,13 +31,7 @@ namespace osu.Game.Skinning protected override void LoadComplete() { - var skin = source.FindProvider(s => getTexture(s, "A") != null); - - rank.Texture = getTexture(skin, scoreProcessor.Rank.Value.ToString()); - - scoreProcessor.Rank.BindValueChanged(v => rank.Texture = getTexture(skin, v.NewValue.ToString())); + scoreProcessor.Rank.BindValueChanged(v => rank.Texture = source.GetTexture($"ranking-{v.NewValue}-small"), true); } - - private static Texture getTexture(ISkin skin, string name) => skin?.GetTexture($"ranking-{name}-small"); } } \ No newline at end of file From 94613b42e4622a80d724f0e2d700f68f2986c7d9 Mon Sep 17 00:00:00 2001 From: nanashi-1 Date: Wed, 9 Aug 2023 08:33:50 +0800 Subject: [PATCH 017/139] update tests --- .../Archives/modified-classic-20230809.osk | Bin 0 -> 1603 bytes .../Archives/modified-default-20230809.osk | Bin 0 -> 2141 bytes osu.Game.Tests/Skins/SkinDeserialisationTest.cs | 6 ++++-- 3 files changed, 4 insertions(+), 2 deletions(-) create mode 100644 osu.Game.Tests/Resources/Archives/modified-classic-20230809.osk create mode 100644 osu.Game.Tests/Resources/Archives/modified-default-20230809.osk diff --git a/osu.Game.Tests/Resources/Archives/modified-classic-20230809.osk b/osu.Game.Tests/Resources/Archives/modified-classic-20230809.osk new file mode 100644 index 0000000000000000000000000000000000000000..b200ab126188d5d056f8d0b11ba37ef9fbc715cf GIT binary patch literal 1603 zcmWIWW@Zs#U|`^2xZ}VX-n-?LnWvYTmwC3|n(vT-fJ^oKq!&#r zDodP7LP|mc6uVUKHMH*S&T$t^U;Mx$rm=s)Bc>^fE30PhjL%Q){5tXCs_(a~Ef)ly z)RDAR4VZ2kP{vzX&lq`H>^HYbDf11zeFfs@q&{<0dz}wrUHe%_MB=G;mZegGn)gh# zwB0EWJ?hfK{FDBPna&F;;_X^+ZEN3KfukiEVzG8vbCQ+SFUIx!yu9#6-Qyq3J+D`- zTODb7_wSqvh5!^FElG=;+y?Z?G9c!K`Y1CmEnhFII6u#{cO%y!0}yzAT>)Sz5C*M&Em(6e<%On^HgA>a-zz4UF-dFCojJbiDOSv zdzYFltCwtZTkfp(MIkfKl==Fo4qn7Dvsn}9P#qxV2jbxTy!7DIoYdqJu#;Cm3knD* z()7~t_c`VH^^EuF@J&H0*t!^9FBgBBEL7a^=S9Q<2igBCI$uzK?Vjf zpsBuznRy}_J{A+TOZ!HTI`PLNh zWQqH$t6L|&J)m{uzn%J&N!t>IS8NeI7=KlPsPWM!U+}m@oe5)HgK+ z?{v~GHl6M%`dt%fZ(Y1++uXD34n|o8O@F~Xmr2TRhJcUA3u_UR`Cp%!Oshrnpf*&+qITO+5 zI;;3kKdaUuHM8miVi#(gpNB|UJ&&wfYkWqt-K?xxoZX z(K^rN|FqNJZ$F80D%ZarIB)u@b(`*Li~Z8z5@8)i2 z7pF-r2lYy;m&r8CYERc@Sa|V3n5dPdy_@=(njo)L@(I-|+~ikAIepvvsp^TY>6bfZ zyc>UK@y?d~qvy`*<2YlAG2=?f%{59ntdaW@RBy%!%|`z5bNl*8=kyv14`%R_t}mfkw^%Ji_e-JG-o^K+#0zOBCfAtoc3_x|!L zIkhZ@^BM|-e%z~4?=;~0YtVI=w{()g(YiS*_o5c;RIEF?pW*oLyFtIcKD+B1e$dcM zNk-*$`m}l7HTK_P=5LXYja!%d)qIoGv4c@-cDDX`-m_(H+T`%7TQA+7CiBJgtgh?R zrg>i_FIS20dCODbbu{I+aKicGS<#V@p76axIMkWyk+yx-e zSO{nYQLy3=T^D*G1J%R8(0B=|3tr@)YemoQ2(8V)G8irMqiaUb)da3BBx literal 0 HcmV?d00001 diff --git a/osu.Game.Tests/Resources/Archives/modified-default-20230809.osk b/osu.Game.Tests/Resources/Archives/modified-default-20230809.osk new file mode 100644 index 0000000000000000000000000000000000000000..a46c20d2b83309aeadcc6a563d504a7db203258b GIT binary patch literal 2141 zcmZ`)2{ha37XMpft$mrG)U+6}L^8EaJ6ijiR>V3zc#XBOB-TMtl13E?+Nz?KVv3ep zYfx&NDiV8JS`td_<)LberIYscy&m3s-*@i$&UerG-Shpvdw=&LtvEQv0RZ3u)bx2B zX*@;jL#$f?=!*vcf_*VqCq)X7Xj4jkBIfpkn`LHK&5qG64&<6xQ57e;|UG1b_;1UR{^{9)8^NC!S zPJUFf(IFY63y+zjuXx$lp^8?iXVY^jJCF0)r`C^ye5#aWBR)LKsqg-XE30qM4n5$K zbafv;H@?k`Iei|!t`}L^rx#tjDTqA8+b<>FGM&X&j12$;zVXIjy>L*UU>r6lrHZc| zCT_BIwd1rczrXoOx4_U;D0ERlmi+>)aG75LA~@CJJ3w}!L$~OYx!zo@eR%C9poi(n z5fM;=$slXU=lRF$WtGQ9Cfa-BpCR&5h7cRtQx`PKyq22wX20!ELuxdQ8qS<23l4Wo z=ty^;n;+AezE%`_=Kh20_}$a*i&GR^$mS%?Ro`mz1zt>R^@z-20yfie+)Q==AV>j# zB#XcWGzM#KZ)}9~55QqPu_3`@6+!lCIeqqJ^^$Nny}t6!?T1j_Ha zQ1Q@l#Pm-NyBnXQFIr$Mi33ley_}0iGZQW=eFuz&u;z)Hvi)3zI*+b*{3nw3g@&KC z9m|5j6?)1ve(+W8{Gob%M+@BDjD#=|Ua?$NobUKHq%sR{C&?|gW&D8%>+n;4dU8hE zgLdgrU0I&Igv6e3VMnoNC?!Z`GS%pjB3ivPPf&`|(zfS}Ae{J^R<%E@LTpCb$?#jR z&2Hzg`Hh(RldHy5EN4xp!gbRlrOBK5VvJV-`L2BPhm1=4ljg)+@L>F-TF(3QS zfR3s&^$BH<^92f)m6L5PrXm?K@fvxCctFjp?Qd3L|2t27iu=aZdfoE+!4t zWed|OH|#Vn{}GqzE#tV>bRFBbUJ;VkdaOPfJR`ioODU#{rw<0_fn@lVP*MU3WNb-+ z5w#?D?(Lw;Odo;{juB~Kvk)yoB~cuC2gzNKF7t(*sh%Y*S<~yS4%J%ddm|)mY~p-YaGuIvQ-E8WS^r3_wM2P} zbAMn?B8QNkOeCRE5hXkAJ`$=|@oTf1;ypUt_lg+L0ahA`Jd4ldh4Y7rjb}CGjfiD`^ zi<=~`Vi=jz8BrMSi&3=cs?^`FQTB#|cFW zwXhvl%NdvU*&3GAQ@8*?h@}==9M;>`)6dgAxFS-@y38-Ohkwo!reSa`>3zayfT!p+JVZ@F0?qW;^)sKl7jwckvTRQHoe8^lU~8YGmLbvA9RV?7f?_VU`W8EBa2s3#!GwB{-5RuD z@wID%49OEN#34kYMeJC=nJtZC4->geDWIF3z8CBeb*hwy(HTEYhS(fv24)9oT|k?j6kWDo%m}X> zxT)-bYOs&jTP=388h#~MBZtp7h(Y_-k1zj*EYH^vBnL7^QZ@z~$IqU;dcR-)x4==+ z@y)Zngc0nD;*6xvN*gs!yvRl~JaHj$%@;(OE}bYe+xskC1n+{x4}oyAVnmB z^LoF36Mivp-gSG`=qmNO`7KTAkggn9s?Kh6E&j#Yk{RXWqo{KG(HODUg-ngZ`8|He z4X?z?D}-f&EJOOCBfK!4ejZ=bTRMwEB7-27YSsv<#YT@6&9NF)!j;HovYuo`$ewqS zuShSI1dvv2?Bc-xAC0WA|5}7E$G->VQPk1F@(sgk)Z5=s{~0q!p+{T#cW48vbpLmQ tKN{m`p?{At$N67ne-wQ57r%o$S-Ls=r${Rh%Lf1;)`qg4C&m5c_BT%1iFW`1 literal 0 HcmV?d00001 diff --git a/osu.Game.Tests/Skins/SkinDeserialisationTest.cs b/osu.Game.Tests/Skins/SkinDeserialisationTest.cs index 72581f5513..7fa10559dc 100644 --- a/osu.Game.Tests/Skins/SkinDeserialisationTest.cs +++ b/osu.Game.Tests/Skins/SkinDeserialisationTest.cs @@ -53,8 +53,10 @@ namespace osu.Game.Tests.Skins "Archives/modified-argon-20230305.osk", // Covers key counters "Archives/modified-argon-pro-20230618.osk", - // Covers rank display - "Archives/modified-argon-20230806.osk" + // Covers default rank display + "Archives/modified-default-20230809.osk", + // Covers legacy rank display + "Archives/modified-classic-20230809.osk" }; /// From 2c3d5dc21a31b4d65d618fbe8dedf714135f32a2 Mon Sep 17 00:00:00 2001 From: nanashi-1 Date: Wed, 9 Aug 2023 08:44:01 +0800 Subject: [PATCH 018/139] remove unnecesary directive --- osu.Game/Skinning/LegacyRankDisplay.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Skinning/LegacyRankDisplay.cs b/osu.Game/Skinning/LegacyRankDisplay.cs index b663f52097..38ece4e5e4 100644 --- a/osu.Game/Skinning/LegacyRankDisplay.cs +++ b/osu.Game/Skinning/LegacyRankDisplay.cs @@ -5,7 +5,6 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; -using osu.Framework.Graphics.Textures; using osu.Game.Rulesets.Scoring; namespace osu.Game.Skinning From 56eaf48892e23dbb5245cbe78e742be3e0faedd1 Mon Sep 17 00:00:00 2001 From: nanashi-1 Date: Wed, 9 Aug 2023 11:29:52 +0800 Subject: [PATCH 019/139] remove unnecessary archive --- .../Archives/modified-argon-20230806.osk | Bin 1354 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 osu.Game.Tests/Resources/Archives/modified-argon-20230806.osk diff --git a/osu.Game.Tests/Resources/Archives/modified-argon-20230806.osk b/osu.Game.Tests/Resources/Archives/modified-argon-20230806.osk deleted file mode 100644 index e7c035811d62df429b930fd41548457307cbf90a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1354 zcmWIWW@Zs#U|`^2_)x_b{xqpXate^Q9xTGZP@J8ar42}OmnuF-Rk~{%W2L3)BpF`hq7LuxG?;EX_=A3@+2KLW50%z zCJT1*?Tu&XoUtI*VfhwjufoTAdrWT0Ma{Xkf@$w(9SMn5xn44g8x$8lRNOs3(qd74 z%Jfi^|ABpl0Vfx(=r4_R5}XqAe#WUcQ*y4{IN&>P(dHEU0JzIP0$qM&RRUiT(A6D4 z%nQUImuKdsz#gq{ny90<(fJCWZu^Z35nV4SU;s zezd&&{rb(l6CZWTX0CB?f8})STDef8DX&0-clIHVq~7&z*EpW3q-AL+#q+qH5nYgJ zvuyS;c?VC{_qW5o8TKyp>l6GXlhoQW^O(vvN!~iABF$+Be%qw2$$KZ^)3U@X{KluV zAFX*U)-Jnh)Sa;?@cZP{{EfZ~ue1iJ=uZv0$-0a&G2qsM8Pleh$E~n?aL_4X)&OEvgYy;j{w*|8v}ZGzaIeJgDh3p?^IJ1ESF zGT)sN8N0h{)!T)llX3-E3@0gAZY}Elo??03He!zw^Q84#%@-FfE%o^CtvPq`Y8$pg zSCYP5__pSq&1TE$)K6i#i}(ygYP2dW90ip;D#MOSo%?9AdF{Q|rrEbr7rkBBULB-& zWUaVTq;82zB2RwC37_z_-gepkxyRe&*4fsU&hx&LH~;#yyxk>lwrQuGF$|3tWc|ma zJAcbQfjiE2pAt&Fd|$~NUsC@#l0&fIso|-E!Q4_hJLh#pHne=1BslE-~E|J=o2m$7s89QqI{qdu7~e5_aE^w!dV5Of5&o z%suLV`NPbzhb#qp21+&!oeN@4YRUY)bbQ9XTUV_eL*D%;cl`0`ioSyG>dU2b`hNdm zU6LCeyZy1r?)PtUHsoIsS3e$8@YTvc?Y=pGep(76YbrMkWyk z+=U3xSO{nYQLrKfSr@hp4Ale7G`pa>;Mo~nD|+@qXboq?mD$kEKo1Cn86TO^gC)S5 Rl?^1%0)!udv@;8c2LR#77DE64 From f3b88c318b57ee222973779359611fdc18fe6eb6 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Thu, 28 Dec 2023 17:49:14 +0100 Subject: [PATCH 020/139] Add rotation to snap grid visual --- .../TestSceneRectangularPositionSnapGrid.cs | 16 +- .../Components/RectangularPositionSnapGrid.cs | 140 +++++++++++++++--- 2 files changed, 125 insertions(+), 31 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneRectangularPositionSnapGrid.cs b/osu.Game.Tests/Visual/Editing/TestSceneRectangularPositionSnapGrid.cs index e73a45e154..210af09055 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneRectangularPositionSnapGrid.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneRectangularPositionSnapGrid.cs @@ -33,28 +33,30 @@ namespace osu.Game.Tests.Visual.Editing }, content = new Container { - RelativeSizeAxes = Axes.Both + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding(10), } }); } private static readonly object[][] test_cases = { - new object[] { new Vector2(0, 0), new Vector2(10, 10) }, - new object[] { new Vector2(240, 180), new Vector2(10, 15) }, - new object[] { new Vector2(160, 120), new Vector2(30, 20) }, - new object[] { new Vector2(480, 360), new Vector2(100, 100) }, + new object[] { new Vector2(0, 0), new Vector2(10, 10), 0f }, + new object[] { new Vector2(240, 180), new Vector2(10, 15), 30f }, + new object[] { new Vector2(160, 120), new Vector2(30, 20), -30f }, + new object[] { new Vector2(480, 360), new Vector2(100, 100), 0f }, }; [TestCaseSource(nameof(test_cases))] - public void TestRectangularGrid(Vector2 position, Vector2 spacing) + public void TestRectangularGrid(Vector2 position, Vector2 spacing, float rotation) { RectangularPositionSnapGrid grid = null; AddStep("create grid", () => Child = grid = new RectangularPositionSnapGrid(position) { RelativeSizeAxes = Axes.Both, - Spacing = spacing + Spacing = spacing, + GridLineRotation = rotation }); AddStep("add snapping cursor", () => Add(new SnappingCursorContainer diff --git a/osu.Game/Screens/Edit/Compose/Components/RectangularPositionSnapGrid.cs b/osu.Game/Screens/Edit/Compose/Components/RectangularPositionSnapGrid.cs index cfc01fe17b..160e7e026b 100644 --- a/osu.Game/Screens/Edit/Compose/Components/RectangularPositionSnapGrid.cs +++ b/osu.Game/Screens/Edit/Compose/Components/RectangularPositionSnapGrid.cs @@ -15,10 +15,20 @@ namespace osu.Game.Screens.Edit.Compose.Components { public partial class RectangularPositionSnapGrid : CompositeDrawable { + private Vector2 startPosition; + /// /// The position of the origin of this in local coordinates. /// - public Vector2 StartPosition { get; } + public Vector2 StartPosition + { + get => startPosition; + set + { + startPosition = value; + gridCache.Invalidate(); + } + } private Vector2 spacing = Vector2.One; @@ -38,11 +48,27 @@ namespace osu.Game.Screens.Edit.Compose.Components } } + private float gridLineRotation; + + /// + /// The rotation in degrees of the grid lines of this . + /// + public float GridLineRotation + { + get => gridLineRotation; + set + { + gridLineRotation = value; + gridCache.Invalidate(); + } + } + private readonly LayoutValue gridCache = new LayoutValue(Invalidation.RequiredParentSizeToFit); public RectangularPositionSnapGrid(Vector2 startPosition) { StartPosition = startPosition; + Masking = true; AddLayout(gridCache); } @@ -65,47 +91,43 @@ namespace osu.Game.Screens.Edit.Compose.Components private void createContent() { var drawSize = DrawSize; + var rot = Quaternion.FromAxisAngle(Vector3.UnitZ, MathHelper.DegreesToRadians(GridLineRotation)); - generateGridLines(Direction.Horizontal, StartPosition.Y, 0, -Spacing.Y); - generateGridLines(Direction.Horizontal, StartPosition.Y, drawSize.Y, Spacing.Y); + generateGridLines(Vector2.Transform(new Vector2(0, -Spacing.Y), rot), GridLineRotation + 90, drawSize); + generateGridLines(Vector2.Transform(new Vector2(0, Spacing.Y), rot), GridLineRotation + 90, drawSize); - generateGridLines(Direction.Vertical, StartPosition.X, 0, -Spacing.X); - generateGridLines(Direction.Vertical, StartPosition.X, drawSize.X, Spacing.X); + generateGridLines(Vector2.Transform(new Vector2(-Spacing.X, 0), rot), GridLineRotation, drawSize); + generateGridLines(Vector2.Transform(new Vector2(Spacing.X, 0), rot), GridLineRotation, drawSize); + + generateOutline(drawSize); } - private void generateGridLines(Direction direction, float startPosition, float endPosition, float step) + private void generateGridLines(Vector2 step, float rotation, Vector2 drawSize) { int index = 0; - float currentPosition = startPosition; + var currentPosition = startPosition; // Make lines the same width independent of display resolution. float lineWidth = DrawWidth / ScreenSpaceDrawQuad.Width; + float lineLength = drawSize.Length * 2; List generatedLines = new List(); - while (Precision.AlmostBigger((endPosition - currentPosition) * Math.Sign(step), 0)) + while (lineDefinitelyIntersectsBox(currentPosition, step.PerpendicularLeft, drawSize) || + isMovingTowardsBox(currentPosition, step, drawSize)) { var gridLine = new Box { Colour = Colour4.White, Alpha = 0.1f, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.None, + Width = lineWidth, + Height = lineLength, + Position = currentPosition, + Rotation = rotation, }; - if (direction == Direction.Horizontal) - { - gridLine.Origin = Anchor.CentreLeft; - gridLine.RelativeSizeAxes = Axes.X; - gridLine.Height = lineWidth; - gridLine.Y = currentPosition; - } - else - { - gridLine.Origin = Anchor.TopCentre; - gridLine.RelativeSizeAxes = Axes.Y; - gridLine.Width = lineWidth; - gridLine.X = currentPosition; - } - generatedLines.Add(gridLine); index += 1; @@ -116,11 +138,81 @@ namespace osu.Game.Screens.Edit.Compose.Components return; generatedLines.First().Alpha = 0.3f; - generatedLines.Last().Alpha = 0.3f; AddRangeInternal(generatedLines); } + private bool isMovingTowardsBox(Vector2 currentPosition, Vector2 step, Vector2 box) + { + return (currentPosition + step).LengthSquared < currentPosition.LengthSquared || + (currentPosition + step - box).LengthSquared < (currentPosition - box).LengthSquared; + } + + private bool lineDefinitelyIntersectsBox(Vector2 lineStart, Vector2 lineDir, Vector2 box) + { + var p2 = lineStart + lineDir; + + double d1 = det(Vector2.Zero); + double d2 = det(new Vector2(box.X, 0)); + double d3 = det(new Vector2(0, box.Y)); + double d4 = det(box); + + return definitelyDifferentSign(d1, d2) || definitelyDifferentSign(d3, d4) || + definitelyDifferentSign(d1, d3) || definitelyDifferentSign(d2, d4); + + double det(Vector2 p) => (p.X - lineStart.X) * (p2.Y - lineStart.Y) - (p.Y - lineStart.Y) * (p2.X - lineStart.X); + + bool definitelyDifferentSign(double a, double b) => !Precision.AlmostEquals(a, 0) && + !Precision.AlmostEquals(b, 0) && + Math.Sign(a) != Math.Sign(b); + } + + private void generateOutline(Vector2 drawSize) + { + // Make lines the same width independent of display resolution. + float lineWidth = DrawWidth / ScreenSpaceDrawQuad.Width; + + AddRangeInternal(new[] + { + new Box + { + Colour = Colour4.White, + Alpha = 0.3f, + Origin = Anchor.CentreLeft, + RelativeSizeAxes = Axes.X, + Height = lineWidth, + Y = 0, + }, + new Box + { + Colour = Colour4.White, + Alpha = 0.3f, + Origin = Anchor.CentreLeft, + RelativeSizeAxes = Axes.X, + Height = lineWidth, + Y = drawSize.Y, + }, + new Box + { + Colour = Colour4.White, + Alpha = 0.3f, + Origin = Anchor.TopCentre, + RelativeSizeAxes = Axes.Y, + Width = lineWidth, + X = 0, + }, + new Box + { + Colour = Colour4.White, + Alpha = 0.3f, + Origin = Anchor.TopCentre, + RelativeSizeAxes = Axes.Y, + Width = lineWidth, + X = drawSize.X, + }, + }); + } + public Vector2 GetSnappedPosition(Vector2 original) { Vector2 relativeToStart = original - StartPosition; From f2edd705ea537774a662ca6121092b137bb2bb8e Mon Sep 17 00:00:00 2001 From: OliBomby Date: Thu, 28 Dec 2023 18:56:49 +0100 Subject: [PATCH 021/139] add rotation to snapped position --- .../Components/RectangularPositionSnapGrid.cs | 5 +++-- osu.Game/Utils/GeometryUtils.cs | 18 +++++++++++++++--- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/RectangularPositionSnapGrid.cs b/osu.Game/Screens/Edit/Compose/Components/RectangularPositionSnapGrid.cs index 160e7e026b..ea9eaf41bb 100644 --- a/osu.Game/Screens/Edit/Compose/Components/RectangularPositionSnapGrid.cs +++ b/osu.Game/Screens/Edit/Compose/Components/RectangularPositionSnapGrid.cs @@ -9,6 +9,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Layout; using osu.Framework.Utils; +using osu.Game.Utils; using osuTK; namespace osu.Game.Screens.Edit.Compose.Components @@ -215,11 +216,11 @@ namespace osu.Game.Screens.Edit.Compose.Components public Vector2 GetSnappedPosition(Vector2 original) { - Vector2 relativeToStart = original - StartPosition; + Vector2 relativeToStart = GeometryUtils.RotateVector(original - StartPosition, GridLineRotation); Vector2 offset = Vector2.Divide(relativeToStart, Spacing); Vector2 roundedOffset = new Vector2(MathF.Round(offset.X), MathF.Round(offset.Y)); - return StartPosition + Vector2.Multiply(roundedOffset, Spacing); + return StartPosition + GeometryUtils.RotateVector(Vector2.Multiply(roundedOffset, Spacing), -GridLineRotation); } } } diff --git a/osu.Game/Utils/GeometryUtils.cs b/osu.Game/Utils/GeometryUtils.cs index 725e93d098..fcc6b8ae2a 100644 --- a/osu.Game/Utils/GeometryUtils.cs +++ b/osu.Game/Utils/GeometryUtils.cs @@ -27,9 +27,8 @@ namespace osu.Game.Utils point.X -= origin.X; point.Y -= origin.Y; - Vector2 ret; - ret.X = point.X * MathF.Cos(MathUtils.DegreesToRadians(angle)) + point.Y * MathF.Sin(MathUtils.DegreesToRadians(angle)); - ret.Y = point.X * -MathF.Sin(MathUtils.DegreesToRadians(angle)) + point.Y * MathF.Cos(MathUtils.DegreesToRadians(angle)); + Vector2 ret = RotateVector(point, angle); + Matrix2 ret.X += origin.X; ret.Y += origin.Y; @@ -37,6 +36,19 @@ namespace osu.Game.Utils return ret; } + /// + /// Rotate a vector around the origin. + /// + /// The vector. + /// The angle to rotate (in degrees). + public static Vector2 RotateVector(Vector2 vector, float angle) + { + return new Vector2( + vector.X * MathF.Cos(MathUtils.DegreesToRadians(angle)) + vector.Y * MathF.Sin(MathUtils.DegreesToRadians(angle)), + vector.X * -MathF.Sin(MathUtils.DegreesToRadians(angle)) + vector.Y * MathF.Cos(MathUtils.DegreesToRadians(angle)) + ); + } + /// /// Given a flip direction, a surrounding quad for all selected objects, and a position, /// will return the flipped position in screen space coordinates. From 2193601f3a70150d2e777939b3e75c9a6dd248db Mon Sep 17 00:00:00 2001 From: OliBomby Date: Thu, 28 Dec 2023 20:00:24 +0100 Subject: [PATCH 022/139] fix typo --- osu.Game/Utils/GeometryUtils.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Utils/GeometryUtils.cs b/osu.Game/Utils/GeometryUtils.cs index fcc6b8ae2a..e0d217dd48 100644 --- a/osu.Game/Utils/GeometryUtils.cs +++ b/osu.Game/Utils/GeometryUtils.cs @@ -28,7 +28,6 @@ namespace osu.Game.Utils point.Y -= origin.Y; Vector2 ret = RotateVector(point, angle); - Matrix2 ret.X += origin.X; ret.Y += origin.Y; From 92c3b142a4941b14c83c479079adec3d6d6a9be5 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Thu, 28 Dec 2023 20:52:11 +0100 Subject: [PATCH 023/139] Added Triangular snap grid --- .../TestSceneTriangularPositionSnapGrid.cs | 108 ++++++++ .../Components/TriangularPositionSnapGrid.cs | 258 ++++++++++++++++++ 2 files changed, 366 insertions(+) create mode 100644 osu.Game.Tests/Visual/Editing/TestSceneTriangularPositionSnapGrid.cs create mode 100644 osu.Game/Screens/Edit/Compose/Components/TriangularPositionSnapGrid.cs diff --git a/osu.Game.Tests/Visual/Editing/TestSceneTriangularPositionSnapGrid.cs b/osu.Game.Tests/Visual/Editing/TestSceneTriangularPositionSnapGrid.cs new file mode 100644 index 0000000000..2f5ffd8423 --- /dev/null +++ b/osu.Game.Tests/Visual/Editing/TestSceneTriangularPositionSnapGrid.cs @@ -0,0 +1,108 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +#nullable disable + +using System; +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Input.Events; +using osu.Game.Screens.Edit.Compose.Components; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Tests.Visual.Editing +{ + public partial class TestSceneTriangularPositionSnapGrid : OsuManualInputManagerTestScene + { + private Container content; + protected override Container Content => content; + + [BackgroundDependencyLoader] + private void load() + { + base.Content.AddRange(new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Colour4.Gray + }, + content = new Container + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding(10), + } + }); + } + + private static readonly object[][] test_cases = + { + new object[] { new Vector2(0, 0), 10, 0f }, + new object[] { new Vector2(240, 180), 10, 10f }, + new object[] { new Vector2(160, 120), 30, -10f }, + new object[] { new Vector2(480, 360), 100, 0f }, + }; + + [TestCaseSource(nameof(test_cases))] + public void TestTriangularGrid(Vector2 position, float spacing, float rotation) + { + TriangularPositionSnapGrid grid = null; + + AddStep("create grid", () => Child = grid = new TriangularPositionSnapGrid(position) + { + RelativeSizeAxes = Axes.Both, + Spacing = spacing, + GridLineRotation = rotation + }); + + AddStep("add snapping cursor", () => Add(new SnappingCursorContainer + { + RelativeSizeAxes = Axes.Both, + GetSnapPosition = pos => grid.GetSnappedPosition(grid.ToLocalSpace(pos)) + })); + } + + private partial class SnappingCursorContainer : CompositeDrawable + { + public Func GetSnapPosition; + + private readonly Drawable cursor; + + public SnappingCursorContainer() + { + RelativeSizeAxes = Axes.Both; + + InternalChild = cursor = new Circle + { + Origin = Anchor.Centre, + Size = new Vector2(50), + Colour = Color4.Red + }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + updatePosition(GetContainingInputManager().CurrentState.Mouse.Position); + } + + protected override bool OnMouseMove(MouseMoveEvent e) + { + base.OnMouseMove(e); + + updatePosition(e.ScreenSpaceMousePosition); + return true; + } + + private void updatePosition(Vector2 screenSpacePosition) + { + cursor.Position = GetSnapPosition.Invoke(screenSpacePosition); + } + } + } +} diff --git a/osu.Game/Screens/Edit/Compose/Components/TriangularPositionSnapGrid.cs b/osu.Game/Screens/Edit/Compose/Components/TriangularPositionSnapGrid.cs new file mode 100644 index 0000000000..58889bd085 --- /dev/null +++ b/osu.Game/Screens/Edit/Compose/Components/TriangularPositionSnapGrid.cs @@ -0,0 +1,258 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using System.Linq; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Layout; +using osu.Framework.Utils; +using osu.Game.Utils; +using osuTK; + +namespace osu.Game.Screens.Edit.Compose.Components +{ + public partial class TriangularPositionSnapGrid : CompositeDrawable + { + private Vector2 startPosition; + + /// + /// The position of the origin of this in local coordinates. + /// + public Vector2 StartPosition + { + get => startPosition; + set + { + startPosition = value; + gridCache.Invalidate(); + } + } + + private float spacing = 1; + + /// + /// The spacing between grid lines of this . + /// + public float Spacing + { + get => spacing; + set + { + if (spacing <= 0) + throw new ArgumentException("Grid spacing must be positive."); + + spacing = value; + gridCache.Invalidate(); + } + } + + private float gridLineRotation; + + /// + /// The rotation in degrees of the grid lines of this . + /// + public float GridLineRotation + { + get => gridLineRotation; + set + { + gridLineRotation = value; + gridCache.Invalidate(); + } + } + + private readonly LayoutValue gridCache = new LayoutValue(Invalidation.RequiredParentSizeToFit); + + public TriangularPositionSnapGrid(Vector2 startPosition) + { + StartPosition = startPosition; + Masking = true; + + AddLayout(gridCache); + } + + protected override void Update() + { + base.Update(); + + if (!gridCache.IsValid) + { + ClearInternal(); + + if (DrawWidth > 0 && DrawHeight > 0) + createContent(); + + gridCache.Validate(); + } + } + + private const float sqrt3 = 1.73205080757f; + private const float sqrt3_over2 = 0.86602540378f; + private const float one_over_sqrt3 = 0.57735026919f; + + private void createContent() + { + var drawSize = DrawSize; + float stepSpacing = Spacing * sqrt3_over2; + var step1 = GeometryUtils.RotateVector(new Vector2(stepSpacing, 0), -GridLineRotation - 30); + var step2 = GeometryUtils.RotateVector(new Vector2(stepSpacing, 0), -GridLineRotation - 90); + var step3 = GeometryUtils.RotateVector(new Vector2(stepSpacing, 0), -GridLineRotation - 150); + + generateGridLines(step1, drawSize); + generateGridLines(-step1, drawSize); + + generateGridLines(step2, drawSize); + generateGridLines(-step2, drawSize); + + generateGridLines(step3, drawSize); + generateGridLines(-step3, drawSize); + + generateOutline(drawSize); + } + + private void generateGridLines(Vector2 step, Vector2 drawSize) + { + int index = 0; + var currentPosition = startPosition; + + // Make lines the same width independent of display resolution. + float lineWidth = DrawWidth / ScreenSpaceDrawQuad.Width; + float lineLength = drawSize.Length * 2; + + List generatedLines = new List(); + + while (lineDefinitelyIntersectsBox(currentPosition, step.PerpendicularLeft, drawSize) || + isMovingTowardsBox(currentPosition, step, drawSize)) + { + var gridLine = new Box + { + Colour = Colour4.White, + Alpha = 0.1f, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.None, + Width = lineWidth, + Height = lineLength, + Position = currentPosition, + Rotation = MathHelper.RadiansToDegrees(MathF.Atan2(step.Y, step.X)), + }; + + generatedLines.Add(gridLine); + + index += 1; + currentPosition = startPosition + index * step; + } + + if (generatedLines.Count == 0) + return; + + generatedLines.First().Alpha = 0.3f; + + AddRangeInternal(generatedLines); + } + + private bool isMovingTowardsBox(Vector2 currentPosition, Vector2 step, Vector2 box) + { + return (currentPosition + step).LengthSquared < currentPosition.LengthSquared || + (currentPosition + step - box).LengthSquared < (currentPosition - box).LengthSquared; + } + + private bool lineDefinitelyIntersectsBox(Vector2 lineStart, Vector2 lineDir, Vector2 box) + { + var p2 = lineStart + lineDir; + + double d1 = det(Vector2.Zero); + double d2 = det(new Vector2(box.X, 0)); + double d3 = det(new Vector2(0, box.Y)); + double d4 = det(box); + + return definitelyDifferentSign(d1, d2) || definitelyDifferentSign(d3, d4) || + definitelyDifferentSign(d1, d3) || definitelyDifferentSign(d2, d4); + + double det(Vector2 p) => (p.X - lineStart.X) * (p2.Y - lineStart.Y) - (p.Y - lineStart.Y) * (p2.X - lineStart.X); + + bool definitelyDifferentSign(double a, double b) => !Precision.AlmostEquals(a, 0) && + !Precision.AlmostEquals(b, 0) && + Math.Sign(a) != Math.Sign(b); + } + + private void generateOutline(Vector2 drawSize) + { + // Make lines the same width independent of display resolution. + float lineWidth = DrawWidth / ScreenSpaceDrawQuad.Width; + + AddRangeInternal(new[] + { + new Box + { + Colour = Colour4.White, + Alpha = 0.3f, + Origin = Anchor.CentreLeft, + RelativeSizeAxes = Axes.X, + Height = lineWidth, + Y = 0, + }, + new Box + { + Colour = Colour4.White, + Alpha = 0.3f, + Origin = Anchor.CentreLeft, + RelativeSizeAxes = Axes.X, + Height = lineWidth, + Y = drawSize.Y, + }, + new Box + { + Colour = Colour4.White, + Alpha = 0.3f, + Origin = Anchor.TopCentre, + RelativeSizeAxes = Axes.Y, + Width = lineWidth, + X = 0, + }, + new Box + { + Colour = Colour4.White, + Alpha = 0.3f, + Origin = Anchor.TopCentre, + RelativeSizeAxes = Axes.Y, + Width = lineWidth, + X = drawSize.X, + }, + }); + } + + public Vector2 GetSnappedPosition(Vector2 original) + { + Vector2 relativeToStart = GeometryUtils.RotateVector(original - StartPosition, GridLineRotation); + Vector2 hex = pixelToHex(relativeToStart); + + return StartPosition + GeometryUtils.RotateVector(hexToPixel(hex), -GridLineRotation); + } + + private Vector2 pixelToHex(Vector2 pixel) + { + float x = pixel.X / Spacing; + float y = pixel.Y / Spacing; + // Algorithm from Charles Chambers + // with modifications and comments by Chris Cox 2023 + // + float t = sqrt3 * y + 1; // scaled y, plus phase + float temp1 = MathF.Floor(t + x); // (y+x) diagonal, this calc needs floor + float temp2 = t - x; // (y-x) diagonal, no floor needed + float temp3 = 2 * x + 1; // scaled horizontal, no floor needed, needs +1 to get correct phase + float qf = (temp1 + temp3) / 3.0f; // pseudo x with fraction + float rf = (temp1 + temp2) / 3.0f; // pseudo y with fraction + float q = MathF.Floor(qf); // pseudo x, quantized and thus requires floor + float r = MathF.Floor(rf); // pseudo y, quantized and thus requires floor + return new Vector2(q, r); + } + + private Vector2 hexToPixel(Vector2 hex) + { + return new Vector2(Spacing * (hex.X - hex.Y / 2), Spacing * one_over_sqrt3 * 1.5f * hex.Y); + } + } +} From d0c8b285cefc29025407597467357cd2940cb862 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Thu, 28 Dec 2023 21:00:47 +0100 Subject: [PATCH 024/139] clean up code duplication --- .../Components/LinedPositionSnapGrid.cs | 173 +++++++++++++++++ .../Components/RectangularPositionSnapGrid.cs | 167 +--------------- .../Components/TriangularPositionSnapGrid.cs | 179 ++---------------- 3 files changed, 195 insertions(+), 324 deletions(-) create mode 100644 osu.Game/Screens/Edit/Compose/Components/LinedPositionSnapGrid.cs diff --git a/osu.Game/Screens/Edit/Compose/Components/LinedPositionSnapGrid.cs b/osu.Game/Screens/Edit/Compose/Components/LinedPositionSnapGrid.cs new file mode 100644 index 0000000000..642a125265 --- /dev/null +++ b/osu.Game/Screens/Edit/Compose/Components/LinedPositionSnapGrid.cs @@ -0,0 +1,173 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using System.Linq; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Layout; +using osu.Framework.Utils; +using osuTK; + +namespace osu.Game.Screens.Edit.Compose.Components +{ + public abstract partial class LinedPositionSnapGrid : CompositeDrawable + { + private Vector2 startPosition; + + /// + /// The position of the origin of this in local coordinates. + /// + public Vector2 StartPosition + { + get => startPosition; + set + { + startPosition = value; + GridCache.Invalidate(); + } + } + + protected readonly LayoutValue GridCache = new LayoutValue(Invalidation.RequiredParentSizeToFit); + + protected LinedPositionSnapGrid(Vector2 startPosition) + { + StartPosition = startPosition; + Masking = true; + + AddLayout(GridCache); + } + + protected override void Update() + { + base.Update(); + + if (!GridCache.IsValid) + { + ClearInternal(); + + if (DrawWidth > 0 && DrawHeight > 0) + CreateContent(); + + GridCache.Validate(); + } + } + + protected abstract void CreateContent(); + + protected void GenerateGridLines(Vector2 step, Vector2 drawSize) + { + int index = 0; + var currentPosition = startPosition; + + // Make lines the same width independent of display resolution. + float lineWidth = DrawWidth / ScreenSpaceDrawQuad.Width; + float lineLength = drawSize.Length * 2; + + List generatedLines = new List(); + + while (lineDefinitelyIntersectsBox(currentPosition, step.PerpendicularLeft, drawSize) || + isMovingTowardsBox(currentPosition, step, drawSize)) + { + var gridLine = new Box + { + Colour = Colour4.White, + Alpha = 0.1f, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.None, + Width = lineWidth, + Height = lineLength, + Position = currentPosition, + Rotation = MathHelper.RadiansToDegrees(MathF.Atan2(step.Y, step.X)), + }; + + generatedLines.Add(gridLine); + + index += 1; + currentPosition = startPosition + index * step; + } + + if (generatedLines.Count == 0) + return; + + generatedLines.First().Alpha = 0.3f; + + AddRangeInternal(generatedLines); + } + + private bool isMovingTowardsBox(Vector2 currentPosition, Vector2 step, Vector2 box) + { + return (currentPosition + step).LengthSquared < currentPosition.LengthSquared || + (currentPosition + step - box).LengthSquared < (currentPosition - box).LengthSquared; + } + + private bool lineDefinitelyIntersectsBox(Vector2 lineStart, Vector2 lineDir, Vector2 box) + { + var p2 = lineStart + lineDir; + + double d1 = det(Vector2.Zero); + double d2 = det(new Vector2(box.X, 0)); + double d3 = det(new Vector2(0, box.Y)); + double d4 = det(box); + + return definitelyDifferentSign(d1, d2) || definitelyDifferentSign(d3, d4) || + definitelyDifferentSign(d1, d3) || definitelyDifferentSign(d2, d4); + + double det(Vector2 p) => (p.X - lineStart.X) * (p2.Y - lineStart.Y) - (p.Y - lineStart.Y) * (p2.X - lineStart.X); + + bool definitelyDifferentSign(double a, double b) => !Precision.AlmostEquals(a, 0) && + !Precision.AlmostEquals(b, 0) && + Math.Sign(a) != Math.Sign(b); + } + + protected void GenerateOutline(Vector2 drawSize) + { + // Make lines the same width independent of display resolution. + float lineWidth = DrawWidth / ScreenSpaceDrawQuad.Width; + + AddRangeInternal(new[] + { + new Box + { + Colour = Colour4.White, + Alpha = 0.3f, + Origin = Anchor.CentreLeft, + RelativeSizeAxes = Axes.X, + Height = lineWidth, + Y = 0, + }, + new Box + { + Colour = Colour4.White, + Alpha = 0.3f, + Origin = Anchor.CentreLeft, + RelativeSizeAxes = Axes.X, + Height = lineWidth, + Y = drawSize.Y, + }, + new Box + { + Colour = Colour4.White, + Alpha = 0.3f, + Origin = Anchor.TopCentre, + RelativeSizeAxes = Axes.Y, + Width = lineWidth, + X = 0, + }, + new Box + { + Colour = Colour4.White, + Alpha = 0.3f, + Origin = Anchor.TopCentre, + RelativeSizeAxes = Axes.Y, + Width = lineWidth, + X = drawSize.X, + }, + }); + } + + public abstract Vector2 GetSnappedPosition(Vector2 original); + } +} diff --git a/osu.Game/Screens/Edit/Compose/Components/RectangularPositionSnapGrid.cs b/osu.Game/Screens/Edit/Compose/Components/RectangularPositionSnapGrid.cs index ea9eaf41bb..14a0e3625a 100644 --- a/osu.Game/Screens/Edit/Compose/Components/RectangularPositionSnapGrid.cs +++ b/osu.Game/Screens/Edit/Compose/Components/RectangularPositionSnapGrid.cs @@ -2,35 +2,15 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Collections.Generic; -using System.Linq; using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; using osu.Framework.Layout; -using osu.Framework.Utils; using osu.Game.Utils; using osuTK; namespace osu.Game.Screens.Edit.Compose.Components { - public partial class RectangularPositionSnapGrid : CompositeDrawable + public partial class RectangularPositionSnapGrid : LinedPositionSnapGrid { - private Vector2 startPosition; - - /// - /// The position of the origin of this in local coordinates. - /// - public Vector2 StartPosition - { - get => startPosition; - set - { - startPosition = value; - gridCache.Invalidate(); - } - } - private Vector2 spacing = Vector2.One; /// @@ -67,154 +47,25 @@ namespace osu.Game.Screens.Edit.Compose.Components private readonly LayoutValue gridCache = new LayoutValue(Invalidation.RequiredParentSizeToFit); public RectangularPositionSnapGrid(Vector2 startPosition) + : base(startPosition) { - StartPosition = startPosition; - Masking = true; - - AddLayout(gridCache); } - protected override void Update() - { - base.Update(); - - if (!gridCache.IsValid) - { - ClearInternal(); - - if (DrawWidth > 0 && DrawHeight > 0) - createContent(); - - gridCache.Validate(); - } - } - - private void createContent() + protected override void CreateContent() { var drawSize = DrawSize; var rot = Quaternion.FromAxisAngle(Vector3.UnitZ, MathHelper.DegreesToRadians(GridLineRotation)); - generateGridLines(Vector2.Transform(new Vector2(0, -Spacing.Y), rot), GridLineRotation + 90, drawSize); - generateGridLines(Vector2.Transform(new Vector2(0, Spacing.Y), rot), GridLineRotation + 90, drawSize); + GenerateGridLines(Vector2.Transform(new Vector2(0, -Spacing.Y), rot), drawSize); + GenerateGridLines(Vector2.Transform(new Vector2(0, Spacing.Y), rot), drawSize); - generateGridLines(Vector2.Transform(new Vector2(-Spacing.X, 0), rot), GridLineRotation, drawSize); - generateGridLines(Vector2.Transform(new Vector2(Spacing.X, 0), rot), GridLineRotation, drawSize); + GenerateGridLines(Vector2.Transform(new Vector2(-Spacing.X, 0), rot), drawSize); + GenerateGridLines(Vector2.Transform(new Vector2(Spacing.X, 0), rot), drawSize); - generateOutline(drawSize); + GenerateOutline(drawSize); } - private void generateGridLines(Vector2 step, float rotation, Vector2 drawSize) - { - int index = 0; - var currentPosition = startPosition; - - // Make lines the same width independent of display resolution. - float lineWidth = DrawWidth / ScreenSpaceDrawQuad.Width; - float lineLength = drawSize.Length * 2; - - List generatedLines = new List(); - - while (lineDefinitelyIntersectsBox(currentPosition, step.PerpendicularLeft, drawSize) || - isMovingTowardsBox(currentPosition, step, drawSize)) - { - var gridLine = new Box - { - Colour = Colour4.White, - Alpha = 0.1f, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.None, - Width = lineWidth, - Height = lineLength, - Position = currentPosition, - Rotation = rotation, - }; - - generatedLines.Add(gridLine); - - index += 1; - currentPosition = startPosition + index * step; - } - - if (generatedLines.Count == 0) - return; - - generatedLines.First().Alpha = 0.3f; - - AddRangeInternal(generatedLines); - } - - private bool isMovingTowardsBox(Vector2 currentPosition, Vector2 step, Vector2 box) - { - return (currentPosition + step).LengthSquared < currentPosition.LengthSquared || - (currentPosition + step - box).LengthSquared < (currentPosition - box).LengthSquared; - } - - private bool lineDefinitelyIntersectsBox(Vector2 lineStart, Vector2 lineDir, Vector2 box) - { - var p2 = lineStart + lineDir; - - double d1 = det(Vector2.Zero); - double d2 = det(new Vector2(box.X, 0)); - double d3 = det(new Vector2(0, box.Y)); - double d4 = det(box); - - return definitelyDifferentSign(d1, d2) || definitelyDifferentSign(d3, d4) || - definitelyDifferentSign(d1, d3) || definitelyDifferentSign(d2, d4); - - double det(Vector2 p) => (p.X - lineStart.X) * (p2.Y - lineStart.Y) - (p.Y - lineStart.Y) * (p2.X - lineStart.X); - - bool definitelyDifferentSign(double a, double b) => !Precision.AlmostEquals(a, 0) && - !Precision.AlmostEquals(b, 0) && - Math.Sign(a) != Math.Sign(b); - } - - private void generateOutline(Vector2 drawSize) - { - // Make lines the same width independent of display resolution. - float lineWidth = DrawWidth / ScreenSpaceDrawQuad.Width; - - AddRangeInternal(new[] - { - new Box - { - Colour = Colour4.White, - Alpha = 0.3f, - Origin = Anchor.CentreLeft, - RelativeSizeAxes = Axes.X, - Height = lineWidth, - Y = 0, - }, - new Box - { - Colour = Colour4.White, - Alpha = 0.3f, - Origin = Anchor.CentreLeft, - RelativeSizeAxes = Axes.X, - Height = lineWidth, - Y = drawSize.Y, - }, - new Box - { - Colour = Colour4.White, - Alpha = 0.3f, - Origin = Anchor.TopCentre, - RelativeSizeAxes = Axes.Y, - Width = lineWidth, - X = 0, - }, - new Box - { - Colour = Colour4.White, - Alpha = 0.3f, - Origin = Anchor.TopCentre, - RelativeSizeAxes = Axes.Y, - Width = lineWidth, - X = drawSize.X, - }, - }); - } - - public Vector2 GetSnappedPosition(Vector2 original) + public override Vector2 GetSnappedPosition(Vector2 original) { Vector2 relativeToStart = GeometryUtils.RotateVector(original - StartPosition, GridLineRotation); Vector2 offset = Vector2.Divide(relativeToStart, Spacing); diff --git a/osu.Game/Screens/Edit/Compose/Components/TriangularPositionSnapGrid.cs b/osu.Game/Screens/Edit/Compose/Components/TriangularPositionSnapGrid.cs index 58889bd085..4b6c5dcfe4 100644 --- a/osu.Game/Screens/Edit/Compose/Components/TriangularPositionSnapGrid.cs +++ b/osu.Game/Screens/Edit/Compose/Components/TriangularPositionSnapGrid.cs @@ -2,35 +2,13 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Collections.Generic; -using System.Linq; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; -using osu.Framework.Layout; -using osu.Framework.Utils; using osu.Game.Utils; using osuTK; namespace osu.Game.Screens.Edit.Compose.Components { - public partial class TriangularPositionSnapGrid : CompositeDrawable + public partial class TriangularPositionSnapGrid : LinedPositionSnapGrid { - private Vector2 startPosition; - - /// - /// The position of the origin of this in local coordinates. - /// - public Vector2 StartPosition - { - get => startPosition; - set - { - startPosition = value; - gridCache.Invalidate(); - } - } - private float spacing = 1; /// @@ -45,7 +23,7 @@ namespace osu.Game.Screens.Edit.Compose.Components throw new ArgumentException("Grid spacing must be positive."); spacing = value; - gridCache.Invalidate(); + GridCache.Invalidate(); } } @@ -60,40 +38,20 @@ namespace osu.Game.Screens.Edit.Compose.Components set { gridLineRotation = value; - gridCache.Invalidate(); + GridCache.Invalidate(); } } - private readonly LayoutValue gridCache = new LayoutValue(Invalidation.RequiredParentSizeToFit); - public TriangularPositionSnapGrid(Vector2 startPosition) + : base(startPosition) { - StartPosition = startPosition; - Masking = true; - - AddLayout(gridCache); - } - - protected override void Update() - { - base.Update(); - - if (!gridCache.IsValid) - { - ClearInternal(); - - if (DrawWidth > 0 && DrawHeight > 0) - createContent(); - - gridCache.Validate(); - } } private const float sqrt3 = 1.73205080757f; private const float sqrt3_over2 = 0.86602540378f; private const float one_over_sqrt3 = 0.57735026919f; - private void createContent() + protected override void CreateContent() { var drawSize = DrawSize; float stepSpacing = Spacing * sqrt3_over2; @@ -101,130 +59,19 @@ namespace osu.Game.Screens.Edit.Compose.Components var step2 = GeometryUtils.RotateVector(new Vector2(stepSpacing, 0), -GridLineRotation - 90); var step3 = GeometryUtils.RotateVector(new Vector2(stepSpacing, 0), -GridLineRotation - 150); - generateGridLines(step1, drawSize); - generateGridLines(-step1, drawSize); + GenerateGridLines(step1, drawSize); + GenerateGridLines(-step1, drawSize); - generateGridLines(step2, drawSize); - generateGridLines(-step2, drawSize); + GenerateGridLines(step2, drawSize); + GenerateGridLines(-step2, drawSize); - generateGridLines(step3, drawSize); - generateGridLines(-step3, drawSize); + GenerateGridLines(step3, drawSize); + GenerateGridLines(-step3, drawSize); - generateOutline(drawSize); + GenerateOutline(drawSize); } - private void generateGridLines(Vector2 step, Vector2 drawSize) - { - int index = 0; - var currentPosition = startPosition; - - // Make lines the same width independent of display resolution. - float lineWidth = DrawWidth / ScreenSpaceDrawQuad.Width; - float lineLength = drawSize.Length * 2; - - List generatedLines = new List(); - - while (lineDefinitelyIntersectsBox(currentPosition, step.PerpendicularLeft, drawSize) || - isMovingTowardsBox(currentPosition, step, drawSize)) - { - var gridLine = new Box - { - Colour = Colour4.White, - Alpha = 0.1f, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.None, - Width = lineWidth, - Height = lineLength, - Position = currentPosition, - Rotation = MathHelper.RadiansToDegrees(MathF.Atan2(step.Y, step.X)), - }; - - generatedLines.Add(gridLine); - - index += 1; - currentPosition = startPosition + index * step; - } - - if (generatedLines.Count == 0) - return; - - generatedLines.First().Alpha = 0.3f; - - AddRangeInternal(generatedLines); - } - - private bool isMovingTowardsBox(Vector2 currentPosition, Vector2 step, Vector2 box) - { - return (currentPosition + step).LengthSquared < currentPosition.LengthSquared || - (currentPosition + step - box).LengthSquared < (currentPosition - box).LengthSquared; - } - - private bool lineDefinitelyIntersectsBox(Vector2 lineStart, Vector2 lineDir, Vector2 box) - { - var p2 = lineStart + lineDir; - - double d1 = det(Vector2.Zero); - double d2 = det(new Vector2(box.X, 0)); - double d3 = det(new Vector2(0, box.Y)); - double d4 = det(box); - - return definitelyDifferentSign(d1, d2) || definitelyDifferentSign(d3, d4) || - definitelyDifferentSign(d1, d3) || definitelyDifferentSign(d2, d4); - - double det(Vector2 p) => (p.X - lineStart.X) * (p2.Y - lineStart.Y) - (p.Y - lineStart.Y) * (p2.X - lineStart.X); - - bool definitelyDifferentSign(double a, double b) => !Precision.AlmostEquals(a, 0) && - !Precision.AlmostEquals(b, 0) && - Math.Sign(a) != Math.Sign(b); - } - - private void generateOutline(Vector2 drawSize) - { - // Make lines the same width independent of display resolution. - float lineWidth = DrawWidth / ScreenSpaceDrawQuad.Width; - - AddRangeInternal(new[] - { - new Box - { - Colour = Colour4.White, - Alpha = 0.3f, - Origin = Anchor.CentreLeft, - RelativeSizeAxes = Axes.X, - Height = lineWidth, - Y = 0, - }, - new Box - { - Colour = Colour4.White, - Alpha = 0.3f, - Origin = Anchor.CentreLeft, - RelativeSizeAxes = Axes.X, - Height = lineWidth, - Y = drawSize.Y, - }, - new Box - { - Colour = Colour4.White, - Alpha = 0.3f, - Origin = Anchor.TopCentre, - RelativeSizeAxes = Axes.Y, - Width = lineWidth, - X = 0, - }, - new Box - { - Colour = Colour4.White, - Alpha = 0.3f, - Origin = Anchor.TopCentre, - RelativeSizeAxes = Axes.Y, - Width = lineWidth, - X = drawSize.X, - }, - }); - } - - public Vector2 GetSnappedPosition(Vector2 original) + public override Vector2 GetSnappedPosition(Vector2 original) { Vector2 relativeToStart = GeometryUtils.RotateVector(original - StartPosition, GridLineRotation); Vector2 hex = pixelToHex(relativeToStart); From a20c430d6f1c4e8deeb2ca974d719ae032bd26fd Mon Sep 17 00:00:00 2001 From: OliBomby Date: Thu, 28 Dec 2023 22:35:00 +0100 Subject: [PATCH 025/139] fix wrong grid cache being used --- .../Compose/Components/RectangularPositionSnapGrid.cs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/RectangularPositionSnapGrid.cs b/osu.Game/Screens/Edit/Compose/Components/RectangularPositionSnapGrid.cs index 14a0e3625a..930a592850 100644 --- a/osu.Game/Screens/Edit/Compose/Components/RectangularPositionSnapGrid.cs +++ b/osu.Game/Screens/Edit/Compose/Components/RectangularPositionSnapGrid.cs @@ -2,8 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System; -using osu.Framework.Graphics; -using osu.Framework.Layout; using osu.Game.Utils; using osuTK; @@ -25,7 +23,7 @@ namespace osu.Game.Screens.Edit.Compose.Components throw new ArgumentException("Grid spacing must be positive."); spacing = value; - gridCache.Invalidate(); + GridCache.Invalidate(); } } @@ -40,12 +38,10 @@ namespace osu.Game.Screens.Edit.Compose.Components set { gridLineRotation = value; - gridCache.Invalidate(); + GridCache.Invalidate(); } } - private readonly LayoutValue gridCache = new LayoutValue(Invalidation.RequiredParentSizeToFit); - public RectangularPositionSnapGrid(Vector2 startPosition) : base(startPosition) { From b16c232490a5353dbf3fd4e59390f8147b4e816b Mon Sep 17 00:00:00 2001 From: OliBomby Date: Thu, 28 Dec 2023 22:36:30 +0100 Subject: [PATCH 026/139] add basic control by grid tool box --- .../Edit/OsuHitObjectComposer.cs | 11 ++ osu.Game/Rulesets/Edit/GridToolboxGroup.cs | 108 ++++++++++++++++++ 2 files changed, 119 insertions(+) create mode 100644 osu.Game/Rulesets/Edit/GridToolboxGroup.cs diff --git a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs index 448cfaf84c..e487a5d490 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs @@ -65,6 +65,9 @@ namespace osu.Game.Rulesets.Osu.Edit [Cached(typeof(IDistanceSnapProvider))] protected readonly OsuDistanceSnapProvider DistanceSnapProvider = new OsuDistanceSnapProvider(); + [Cached] + protected readonly GridToolboxGroup GridToolboxGroup = new GridToolboxGroup(); + [Cached] protected readonly FreehandSliderToolboxGroup FreehandlSliderToolboxGroup = new FreehandSliderToolboxGroup(); @@ -99,8 +102,16 @@ namespace osu.Game.Rulesets.Osu.Edit // we may be entering the screen with a selection already active updateDistanceSnapGrid(); + GridToolboxGroup.StartPositionX.ValueChanged += x => + rectangularPositionSnapGrid.StartPosition = new Vector2(x.NewValue, rectangularPositionSnapGrid.StartPosition.Y); + GridToolboxGroup.StartPositionY.ValueChanged += y => + rectangularPositionSnapGrid.StartPosition = new Vector2(rectangularPositionSnapGrid.StartPosition.X, y.NewValue); + GridToolboxGroup.Spacing.ValueChanged += s => rectangularPositionSnapGrid.Spacing = new Vector2(s.NewValue); + GridToolboxGroup.GridLinesRotation.ValueChanged += r => rectangularPositionSnapGrid.GridLineRotation = r.NewValue; + RightToolbox.AddRange(new EditorToolboxGroup[] { + GridToolboxGroup, new TransformToolboxGroup { RotationHandler = BlueprintContainer.SelectionHandler.RotationHandler, }, FreehandlSliderToolboxGroup } diff --git a/osu.Game/Rulesets/Edit/GridToolboxGroup.cs b/osu.Game/Rulesets/Edit/GridToolboxGroup.cs new file mode 100644 index 0000000000..b6903c1369 --- /dev/null +++ b/osu.Game/Rulesets/Edit/GridToolboxGroup.cs @@ -0,0 +1,108 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Game.Graphics.UserInterface; +using osu.Game.Screens.Edit; + +namespace osu.Game.Rulesets.Edit +{ + public partial class GridToolboxGroup : EditorToolboxGroup + { + [Resolved] + private EditorBeatmap editorBeatmap { get; set; } = null!; + + public GridToolboxGroup() + : base("grid") + { + } + + public BindableFloat StartPositionX { get; } = new BindableFloat(256f) + { + MinValue = 0f, + MaxValue = 512f, + Precision = 1f + }; + + public BindableFloat StartPositionY { get; } = new BindableFloat(192) + { + MinValue = 0f, + MaxValue = 384f, + Precision = 1f + }; + + public BindableFloat Spacing { get; } = new BindableFloat(4f) + { + MinValue = 4f, + MaxValue = 128f, + Precision = 1f + }; + + public BindableFloat GridLinesRotation { get; } = new BindableFloat(0f) + { + MinValue = -180f, + MaxValue = 180f, + Precision = 1f + }; + + private ExpandableSlider startPositionXSlider = null!; + private ExpandableSlider startPositionYSlider = null!; + private ExpandableSlider spacingSlider = null!; + private ExpandableSlider gridLinesRotationSlider = null!; + + [BackgroundDependencyLoader] + private void load() + { + Children = new Drawable[] + { + startPositionXSlider = new ExpandableSlider + { + Current = StartPositionX + }, + startPositionYSlider = new ExpandableSlider + { + Current = StartPositionY + }, + spacingSlider = new ExpandableSlider + { + Current = Spacing + }, + gridLinesRotationSlider = new ExpandableSlider + { + Current = GridLinesRotation + } + }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + StartPositionX.BindValueChanged(x => + { + startPositionXSlider.ContractedLabelText = $"X: {x.NewValue:N0}"; + startPositionXSlider.ExpandedLabelText = $"X Offset: {x.NewValue:N0}"; + }, true); + + StartPositionY.BindValueChanged(y => + { + startPositionYSlider.ContractedLabelText = $"Y: {y.NewValue:N0}"; + startPositionYSlider.ExpandedLabelText = $"Y Offset: {y.NewValue:N0}"; + }, true); + + Spacing.BindValueChanged(spacing => + { + spacingSlider.ContractedLabelText = $"S: {spacing.NewValue:N0}"; + spacingSlider.ExpandedLabelText = $"Spacing: {spacing.NewValue:N0}"; + }, true); + + GridLinesRotation.BindValueChanged(rotation => + { + gridLinesRotationSlider.ContractedLabelText = $"R: {rotation.NewValue:N0}"; + gridLinesRotationSlider.ExpandedLabelText = $"Rotation: {rotation.NewValue:N0}"; + }, true); + } + } +} From 0ce1a48e68ea26d9295a611e6648ffafc8a8651f Mon Sep 17 00:00:00 2001 From: OliBomby Date: Thu, 28 Dec 2023 22:54:30 +0100 Subject: [PATCH 027/139] Add comment --- .../Edit/Compose/Components/TriangularPositionSnapGrid.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Screens/Edit/Compose/Components/TriangularPositionSnapGrid.cs b/osu.Game/Screens/Edit/Compose/Components/TriangularPositionSnapGrid.cs index 4b6c5dcfe4..af44641f5e 100644 --- a/osu.Game/Screens/Edit/Compose/Components/TriangularPositionSnapGrid.cs +++ b/osu.Game/Screens/Edit/Compose/Components/TriangularPositionSnapGrid.cs @@ -99,6 +99,8 @@ namespace osu.Game.Screens.Edit.Compose.Components private Vector2 hexToPixel(Vector2 hex) { + // Taken from + // with modifications for the different definition of size. return new Vector2(Spacing * (hex.X - hex.Y / 2), Spacing * one_over_sqrt3 * 1.5f * hex.Y); } } From f223487e1cd9cd2a391f79575a13222cfa19987e Mon Sep 17 00:00:00 2001 From: OliBomby Date: Thu, 28 Dec 2023 23:10:06 +0100 Subject: [PATCH 028/139] improve code --- .../Editor/TestSceneOsuEditorGrids.cs | 7 +- .../Edit/OsuGridToolboxGroup.cs | 71 +++++++++++++++---- .../Edit/OsuHitObjectComposer.cs | 18 ++--- .../Edit/OsuRectangularPositionSnapGrid.cs | 69 ------------------ .../TestSceneRectangularPositionSnapGrid.cs | 3 +- .../Screens/Editors/LadderEditorScreen.cs | 2 +- .../Components/LinedPositionSnapGrid.cs | 3 +- .../Components/RectangularPositionSnapGrid.cs | 5 -- .../Components/TriangularPositionSnapGrid.cs | 5 -- 9 files changed, 75 insertions(+), 108 deletions(-) rename osu.Game/Rulesets/Edit/GridToolboxGroup.cs => osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs (65%) delete mode 100644 osu.Game.Rulesets.Osu/Edit/OsuRectangularPositionSnapGrid.cs diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorGrids.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorGrids.cs index d14e593587..299db23ccc 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorGrids.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorGrids.cs @@ -7,6 +7,7 @@ using osu.Framework.Testing; using osu.Framework.Utils; using osu.Game.Rulesets.Osu.Edit; using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles; +using osu.Game.Screens.Edit.Compose.Components; using osu.Game.Tests.Visual; using osuTK; using osuTK.Input; @@ -69,7 +70,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor AddStep("choose placement tool", () => InputManager.Key(Key.Number2)); AddStep("move cursor to (1, 1)", () => { - var composer = Editor.ChildrenOfType().Single(); + var composer = Editor.ChildrenOfType().Single(); InputManager.MoveMouseTo(composer.ToScreenSpace(new Vector2(1, 1))); }); @@ -83,7 +84,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor public void TestGridSizeToggling() { AddStep("enable rectangular grid", () => InputManager.Key(Key.Y)); - AddUntilStep("rectangular grid visible", () => this.ChildrenOfType().Any()); + AddUntilStep("rectangular grid visible", () => this.ChildrenOfType().Any()); gridSizeIs(4); nextGridSizeIs(8); @@ -99,7 +100,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor } private void gridSizeIs(int size) - => AddAssert($"grid size is {size}", () => this.ChildrenOfType().Single().Spacing == new Vector2(size) + => AddAssert($"grid size is {size}", () => this.ChildrenOfType().Single().Spacing == new Vector2(size) && EditorBeatmap.BeatmapInfo.GridSize == size); } } diff --git a/osu.Game/Rulesets/Edit/GridToolboxGroup.cs b/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs similarity index 65% rename from osu.Game/Rulesets/Edit/GridToolboxGroup.cs rename to osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs index b6903c1369..d93b6c27c0 100644 --- a/osu.Game/Rulesets/Edit/GridToolboxGroup.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs @@ -1,35 +1,40 @@ // 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.Bindables; using osu.Framework.Graphics; +using osu.Framework.Input.Bindings; +using osu.Framework.Input.Events; using osu.Game.Graphics.UserInterface; +using osu.Game.Input.Bindings; +using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Osu.UI; using osu.Game.Screens.Edit; -namespace osu.Game.Rulesets.Edit +namespace osu.Game.Rulesets.Osu.Edit { - public partial class GridToolboxGroup : EditorToolboxGroup + public partial class OsuGridToolboxGroup : EditorToolboxGroup, IKeyBindingHandler { + private static readonly int[] grid_sizes = { 4, 8, 16, 32 }; + + private int currentGridSizeIndex = grid_sizes.Length - 1; + [Resolved] private EditorBeatmap editorBeatmap { get; set; } = null!; - public GridToolboxGroup() - : base("grid") - { - } - - public BindableFloat StartPositionX { get; } = new BindableFloat(256f) + public BindableFloat StartPositionX { get; } = new BindableFloat(OsuPlayfield.BASE_SIZE.X / 2) { MinValue = 0f, - MaxValue = 512f, + MaxValue = OsuPlayfield.BASE_SIZE.X, Precision = 1f }; - public BindableFloat StartPositionY { get; } = new BindableFloat(192) + public BindableFloat StartPositionY { get; } = new BindableFloat(OsuPlayfield.BASE_SIZE.Y / 2) { MinValue = 0f, - MaxValue = 384f, + MaxValue = OsuPlayfield.BASE_SIZE.Y, Precision = 1f }; @@ -42,8 +47,8 @@ namespace osu.Game.Rulesets.Edit public BindableFloat GridLinesRotation { get; } = new BindableFloat(0f) { - MinValue = -180f, - MaxValue = 180f, + MinValue = -45f, + MaxValue = 45f, Precision = 1f }; @@ -52,6 +57,11 @@ namespace osu.Game.Rulesets.Edit private ExpandableSlider spacingSlider = null!; private ExpandableSlider gridLinesRotationSlider = null!; + public OsuGridToolboxGroup() + : base("grid") + { + } + [BackgroundDependencyLoader] private void load() { @@ -74,6 +84,11 @@ namespace osu.Game.Rulesets.Edit Current = GridLinesRotation } }; + + int gridSizeIndex = Array.IndexOf(grid_sizes, editorBeatmap.BeatmapInfo.GridSize); + if (gridSizeIndex >= 0) + currentGridSizeIndex = gridSizeIndex; + updateSpacing(); } protected override void LoadComplete() @@ -104,5 +119,35 @@ namespace osu.Game.Rulesets.Edit gridLinesRotationSlider.ExpandedLabelText = $"Rotation: {rotation.NewValue:N0}"; }, true); } + + private void nextGridSize() + { + currentGridSizeIndex = (currentGridSizeIndex + 1) % grid_sizes.Length; + updateSpacing(); + } + + private void updateSpacing() + { + int gridSize = grid_sizes[currentGridSizeIndex]; + + editorBeatmap.BeatmapInfo.GridSize = gridSize; + Spacing.Value = gridSize; + } + + public bool OnPressed(KeyBindingPressEvent e) + { + switch (e.Action) + { + case GlobalAction.EditorCycleGridDisplayMode: + nextGridSize(); + return true; + } + + return false; + } + + public void OnReleased(KeyBindingReleaseEvent e) + { + } } } diff --git a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs index e487a5d490..12457fc88d 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs @@ -66,7 +66,7 @@ namespace osu.Game.Rulesets.Osu.Edit protected readonly OsuDistanceSnapProvider DistanceSnapProvider = new OsuDistanceSnapProvider(); [Cached] - protected readonly GridToolboxGroup GridToolboxGroup = new GridToolboxGroup(); + protected readonly OsuGridToolboxGroup OsuGridToolboxGroup = new OsuGridToolboxGroup(); [Cached] protected readonly FreehandSliderToolboxGroup FreehandlSliderToolboxGroup = new FreehandSliderToolboxGroup(); @@ -86,7 +86,7 @@ namespace osu.Game.Rulesets.Osu.Edit { RelativeSizeAxes = Axes.Both }, - rectangularPositionSnapGrid = new OsuRectangularPositionSnapGrid + rectangularPositionSnapGrid = new RectangularPositionSnapGrid { RelativeSizeAxes = Axes.Both } @@ -102,16 +102,16 @@ namespace osu.Game.Rulesets.Osu.Edit // we may be entering the screen with a selection already active updateDistanceSnapGrid(); - GridToolboxGroup.StartPositionX.ValueChanged += x => - rectangularPositionSnapGrid.StartPosition = new Vector2(x.NewValue, rectangularPositionSnapGrid.StartPosition.Y); - GridToolboxGroup.StartPositionY.ValueChanged += y => - rectangularPositionSnapGrid.StartPosition = new Vector2(rectangularPositionSnapGrid.StartPosition.X, y.NewValue); - GridToolboxGroup.Spacing.ValueChanged += s => rectangularPositionSnapGrid.Spacing = new Vector2(s.NewValue); - GridToolboxGroup.GridLinesRotation.ValueChanged += r => rectangularPositionSnapGrid.GridLineRotation = r.NewValue; + OsuGridToolboxGroup.StartPositionX.BindValueChanged(x => + rectangularPositionSnapGrid.StartPosition = new Vector2(x.NewValue, rectangularPositionSnapGrid.StartPosition.Y), true); + OsuGridToolboxGroup.StartPositionY.BindValueChanged(y => + rectangularPositionSnapGrid.StartPosition = new Vector2(rectangularPositionSnapGrid.StartPosition.X, y.NewValue), true); + OsuGridToolboxGroup.Spacing.BindValueChanged(s => rectangularPositionSnapGrid.Spacing = new Vector2(s.NewValue), true); + OsuGridToolboxGroup.GridLinesRotation.BindValueChanged(r => rectangularPositionSnapGrid.GridLineRotation = r.NewValue, true); RightToolbox.AddRange(new EditorToolboxGroup[] { - GridToolboxGroup, + OsuGridToolboxGroup, new TransformToolboxGroup { RotationHandler = BlueprintContainer.SelectionHandler.RotationHandler, }, FreehandlSliderToolboxGroup } diff --git a/osu.Game.Rulesets.Osu/Edit/OsuRectangularPositionSnapGrid.cs b/osu.Game.Rulesets.Osu/Edit/OsuRectangularPositionSnapGrid.cs deleted file mode 100644 index efc6668ebf..0000000000 --- a/osu.Game.Rulesets.Osu/Edit/OsuRectangularPositionSnapGrid.cs +++ /dev/null @@ -1,69 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using System; -using osu.Framework.Allocation; -using osu.Framework.Input.Bindings; -using osu.Framework.Input.Events; -using osu.Game.Input.Bindings; -using osu.Game.Rulesets.Osu.UI; -using osu.Game.Screens.Edit; -using osu.Game.Screens.Edit.Compose.Components; -using osuTK; - -namespace osu.Game.Rulesets.Osu.Edit -{ - public partial class OsuRectangularPositionSnapGrid : RectangularPositionSnapGrid, IKeyBindingHandler - { - private static readonly int[] grid_sizes = { 4, 8, 16, 32 }; - - private int currentGridSizeIndex = grid_sizes.Length - 1; - - [Resolved] - private EditorBeatmap editorBeatmap { get; set; } = null!; - - public OsuRectangularPositionSnapGrid() - : base(OsuPlayfield.BASE_SIZE / 2) - { - } - - [BackgroundDependencyLoader] - private void load() - { - int gridSizeIndex = Array.IndexOf(grid_sizes, editorBeatmap.BeatmapInfo.GridSize); - if (gridSizeIndex >= 0) - currentGridSizeIndex = gridSizeIndex; - updateSpacing(); - } - - private void nextGridSize() - { - currentGridSizeIndex = (currentGridSizeIndex + 1) % grid_sizes.Length; - updateSpacing(); - } - - private void updateSpacing() - { - int gridSize = grid_sizes[currentGridSizeIndex]; - - editorBeatmap.BeatmapInfo.GridSize = gridSize; - Spacing = new Vector2(gridSize); - } - - public bool OnPressed(KeyBindingPressEvent e) - { - switch (e.Action) - { - case GlobalAction.EditorCycleGridDisplayMode: - nextGridSize(); - return true; - } - - return false; - } - - public void OnReleased(KeyBindingReleaseEvent e) - { - } - } -} diff --git a/osu.Game.Tests/Visual/Editing/TestSceneRectangularPositionSnapGrid.cs b/osu.Game.Tests/Visual/Editing/TestSceneRectangularPositionSnapGrid.cs index 210af09055..a0042cf605 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneRectangularPositionSnapGrid.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneRectangularPositionSnapGrid.cs @@ -52,9 +52,10 @@ namespace osu.Game.Tests.Visual.Editing { RectangularPositionSnapGrid grid = null; - AddStep("create grid", () => Child = grid = new RectangularPositionSnapGrid(position) + AddStep("create grid", () => Child = grid = new RectangularPositionSnapGrid() { RelativeSizeAxes = Axes.Both, + StartPosition = position, Spacing = spacing, GridLineRotation = rotation }); diff --git a/osu.Game.Tournament/Screens/Editors/LadderEditorScreen.cs b/osu.Game.Tournament/Screens/Editors/LadderEditorScreen.cs index 4074e681f9..ad00e8d47d 100644 --- a/osu.Game.Tournament/Screens/Editors/LadderEditorScreen.cs +++ b/osu.Game.Tournament/Screens/Editors/LadderEditorScreen.cs @@ -51,7 +51,7 @@ namespace osu.Game.Tournament.Screens.Editors AddInternal(rightClickMessage = new WarningBox("Right click to place and link matches")); - ScrollContent.Add(grid = new RectangularPositionSnapGrid(Vector2.Zero) + ScrollContent.Add(grid = new RectangularPositionSnapGrid { Spacing = new Vector2(GRID_SPACING), Anchor = Anchor.Centre, diff --git a/osu.Game/Screens/Edit/Compose/Components/LinedPositionSnapGrid.cs b/osu.Game/Screens/Edit/Compose/Components/LinedPositionSnapGrid.cs index 642a125265..3616bc1ca1 100644 --- a/osu.Game/Screens/Edit/Compose/Components/LinedPositionSnapGrid.cs +++ b/osu.Game/Screens/Edit/Compose/Components/LinedPositionSnapGrid.cs @@ -32,9 +32,8 @@ namespace osu.Game.Screens.Edit.Compose.Components protected readonly LayoutValue GridCache = new LayoutValue(Invalidation.RequiredParentSizeToFit); - protected LinedPositionSnapGrid(Vector2 startPosition) + protected LinedPositionSnapGrid() { - StartPosition = startPosition; Masking = true; AddLayout(GridCache); diff --git a/osu.Game/Screens/Edit/Compose/Components/RectangularPositionSnapGrid.cs b/osu.Game/Screens/Edit/Compose/Components/RectangularPositionSnapGrid.cs index 930a592850..2392921203 100644 --- a/osu.Game/Screens/Edit/Compose/Components/RectangularPositionSnapGrid.cs +++ b/osu.Game/Screens/Edit/Compose/Components/RectangularPositionSnapGrid.cs @@ -42,11 +42,6 @@ namespace osu.Game.Screens.Edit.Compose.Components } } - public RectangularPositionSnapGrid(Vector2 startPosition) - : base(startPosition) - { - } - protected override void CreateContent() { var drawSize = DrawSize; diff --git a/osu.Game/Screens/Edit/Compose/Components/TriangularPositionSnapGrid.cs b/osu.Game/Screens/Edit/Compose/Components/TriangularPositionSnapGrid.cs index af44641f5e..c98890e294 100644 --- a/osu.Game/Screens/Edit/Compose/Components/TriangularPositionSnapGrid.cs +++ b/osu.Game/Screens/Edit/Compose/Components/TriangularPositionSnapGrid.cs @@ -42,11 +42,6 @@ namespace osu.Game.Screens.Edit.Compose.Components } } - public TriangularPositionSnapGrid(Vector2 startPosition) - : base(startPosition) - { - } - private const float sqrt3 = 1.73205080757f; private const float sqrt3_over2 = 0.86602540378f; private const float one_over_sqrt3 = 0.57735026919f; From 351cfbff3e02f96b79eff3a020f0a519e1348052 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Thu, 28 Dec 2023 23:38:10 +0100 Subject: [PATCH 029/139] Fix snapping going out of bounds --- osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs index 12457fc88d..3e5cede1d3 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs @@ -24,6 +24,7 @@ using osu.Game.Rulesets.Edit.Tools; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.UI; using osu.Game.Rulesets.UI; using osu.Game.Screens.Edit.Components.TernaryButtons; using osu.Game.Screens.Edit.Compose.Components; @@ -218,6 +219,10 @@ namespace osu.Game.Rulesets.Osu.Edit { Vector2 pos = rectangularPositionSnapGrid.GetSnappedPosition(rectangularPositionSnapGrid.ToLocalSpace(result.ScreenSpacePosition)); + // A rotated grid can produce a position that is outside of the playfield. + // We need to clamp the position to the playfield bounds to ensure that the snapped position is always in bounds. + pos = Vector2.Clamp(pos, Vector2.Zero, OsuPlayfield.BASE_SIZE); + result.ScreenSpacePosition = rectangularPositionSnapGrid.ToScreenSpace(pos); } } From 8ef9bdf861d40eafb12eedbfeb8c093e00c987a2 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Thu, 28 Dec 2023 23:39:24 +0100 Subject: [PATCH 030/139] clarify comment --- osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs index 3e5cede1d3..7b872eef38 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs @@ -219,7 +219,7 @@ namespace osu.Game.Rulesets.Osu.Edit { Vector2 pos = rectangularPositionSnapGrid.GetSnappedPosition(rectangularPositionSnapGrid.ToLocalSpace(result.ScreenSpacePosition)); - // A rotated grid can produce a position that is outside of the playfield. + // A grid which doesn't perfectly fit the playfield can produce a position that is outside of the playfield. // We need to clamp the position to the playfield bounds to ensure that the snapped position is always in bounds. pos = Vector2.Clamp(pos, Vector2.Zero, OsuPlayfield.BASE_SIZE); From 040fd5ef9c65ca22aeb5dd9d903d0ed1cf6f0951 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Fri, 29 Dec 2023 16:59:12 +0100 Subject: [PATCH 031/139] Add option to change grid type --- .../Edit/OsuGridToolboxGroup.cs | 30 ++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs b/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs index d93b6c27c0..892c89ebc2 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs @@ -2,9 +2,12 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; using osu.Game.Graphics.UserInterface; @@ -12,6 +15,7 @@ using osu.Game.Input.Bindings; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Osu.UI; using osu.Game.Screens.Edit; +using osu.Game.Screens.Edit.Components.RadioButtons; namespace osu.Game.Rulesets.Osu.Edit { @@ -52,10 +56,13 @@ namespace osu.Game.Rulesets.Osu.Edit Precision = 1f }; + public Bindable GridType { get; } = new Bindable(); + private ExpandableSlider startPositionXSlider = null!; private ExpandableSlider startPositionYSlider = null!; private ExpandableSlider spacingSlider = null!; private ExpandableSlider gridLinesRotationSlider = null!; + private EditorRadioButtonCollection gridTypeButtons = null!; public OsuGridToolboxGroup() : base("grid") @@ -82,7 +89,20 @@ namespace osu.Game.Rulesets.Osu.Edit gridLinesRotationSlider = new ExpandableSlider { Current = GridLinesRotation - } + }, + gridTypeButtons = new EditorRadioButtonCollection + { + RelativeSizeAxes = Axes.X, + Items = new[] + { + new RadioButton("Square", + () => GridType.Value = PositionSnapGridType.Square, + () => new SpriteIcon { Icon = FontAwesome.Regular.Square }), + new RadioButton("Triangle", + () => GridType.Value = PositionSnapGridType.Triangle, + () => new Triangle()) + } + }, }; int gridSizeIndex = Array.IndexOf(grid_sizes, editorBeatmap.BeatmapInfo.GridSize); @@ -95,6 +115,8 @@ namespace osu.Game.Rulesets.Osu.Edit { base.LoadComplete(); + gridTypeButtons.Items.First().Select(); + StartPositionX.BindValueChanged(x => { startPositionXSlider.ContractedLabelText = $"X: {x.NewValue:N0}"; @@ -150,4 +172,10 @@ namespace osu.Game.Rulesets.Osu.Edit { } } + + public enum PositionSnapGridType + { + Square, + Triangle, + } } From 8a331057b0bb76578608c7dd4afc79e98fab82b2 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Fri, 29 Dec 2023 17:25:17 +0100 Subject: [PATCH 032/139] Make it actually possible to change grid type --- .../Edit/OsuHitObjectComposer.cs | 60 ++++++++--- .../Components/LinedPositionSnapGrid.cs | 97 +---------------- .../Compose/Components/PositionSnapGrid.cs | 102 ++++++++++++++++++ 3 files changed, 152 insertions(+), 107 deletions(-) create mode 100644 osu.Game/Screens/Edit/Compose/Components/PositionSnapGrid.cs diff --git a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs index 7b872eef38..b0ac8467c1 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs @@ -84,10 +84,6 @@ namespace osu.Game.Rulesets.Osu.Edit LayerBelowRuleset.AddRange(new Drawable[] { distanceSnapGridContainer = new Container - { - RelativeSizeAxes = Axes.Both - }, - rectangularPositionSnapGrid = new RectangularPositionSnapGrid { RelativeSizeAxes = Axes.Both } @@ -103,12 +99,7 @@ namespace osu.Game.Rulesets.Osu.Edit // we may be entering the screen with a selection already active updateDistanceSnapGrid(); - OsuGridToolboxGroup.StartPositionX.BindValueChanged(x => - rectangularPositionSnapGrid.StartPosition = new Vector2(x.NewValue, rectangularPositionSnapGrid.StartPosition.Y), true); - OsuGridToolboxGroup.StartPositionY.BindValueChanged(y => - rectangularPositionSnapGrid.StartPosition = new Vector2(rectangularPositionSnapGrid.StartPosition.X, y.NewValue), true); - OsuGridToolboxGroup.Spacing.BindValueChanged(s => rectangularPositionSnapGrid.Spacing = new Vector2(s.NewValue), true); - OsuGridToolboxGroup.GridLinesRotation.BindValueChanged(r => rectangularPositionSnapGrid.GridLineRotation = r.NewValue, true); + OsuGridToolboxGroup.GridType.BindValueChanged(updatePositionSnapGrid, true); RightToolbox.AddRange(new EditorToolboxGroup[] { @@ -119,6 +110,49 @@ namespace osu.Game.Rulesets.Osu.Edit ); } + private void updatePositionSnapGrid(ValueChangedEvent obj) + { + if (positionSnapGrid != null) + LayerBelowRuleset.Remove(positionSnapGrid, true); + + switch (obj.NewValue) + { + case PositionSnapGridType.Square: + var rectangularPositionSnapGrid = new RectangularPositionSnapGrid(); + + OsuGridToolboxGroup.Spacing.BindValueChanged(s => rectangularPositionSnapGrid.Spacing = new Vector2(s.NewValue), true); + OsuGridToolboxGroup.GridLinesRotation.BindValueChanged(r => rectangularPositionSnapGrid.GridLineRotation = r.NewValue, true); + + positionSnapGrid = rectangularPositionSnapGrid; + break; + + case PositionSnapGridType.Triangle: + var triangularPositionSnapGrid = new TriangularPositionSnapGrid(); + + OsuGridToolboxGroup.Spacing.BindValueChanged(s => triangularPositionSnapGrid.Spacing = s.NewValue, true); + OsuGridToolboxGroup.GridLinesRotation.BindValueChanged(r => triangularPositionSnapGrid.GridLineRotation = r.NewValue, true); + + positionSnapGrid = triangularPositionSnapGrid; + break; + + default: + throw new NotImplementedException($"{OsuGridToolboxGroup.GridType} has an incorrect value."); + } + + bindPositionSnapGridStartPosition(positionSnapGrid); + positionSnapGrid.RelativeSizeAxes = Axes.Both; + LayerBelowRuleset.Add(positionSnapGrid); + return; + + void bindPositionSnapGridStartPosition(PositionSnapGrid snapGrid) + { + OsuGridToolboxGroup.StartPositionX.BindValueChanged(x => + snapGrid.StartPosition = new Vector2(x.NewValue, snapGrid.StartPosition.Y), true); + OsuGridToolboxGroup.StartPositionY.BindValueChanged(y => + snapGrid.StartPosition = new Vector2(snapGrid.StartPosition.X, y.NewValue), true); + } + } + protected override ComposeBlueprintContainer CreateBlueprintContainer() => new OsuBlueprintContainer(this); @@ -159,7 +193,7 @@ namespace osu.Game.Rulesets.Osu.Edit private readonly Cached distanceSnapGridCache = new Cached(); private double? lastDistanceSnapGridTime; - private RectangularPositionSnapGrid rectangularPositionSnapGrid; + private PositionSnapGrid positionSnapGrid; protected override void Update() { @@ -217,13 +251,13 @@ namespace osu.Game.Rulesets.Osu.Edit { if (rectangularGridSnapToggle.Value == TernaryState.True) { - Vector2 pos = rectangularPositionSnapGrid.GetSnappedPosition(rectangularPositionSnapGrid.ToLocalSpace(result.ScreenSpacePosition)); + Vector2 pos = positionSnapGrid.GetSnappedPosition(positionSnapGrid.ToLocalSpace(result.ScreenSpacePosition)); // A grid which doesn't perfectly fit the playfield can produce a position that is outside of the playfield. // We need to clamp the position to the playfield bounds to ensure that the snapped position is always in bounds. pos = Vector2.Clamp(pos, Vector2.Zero, OsuPlayfield.BASE_SIZE); - result.ScreenSpacePosition = rectangularPositionSnapGrid.ToScreenSpace(pos); + result.ScreenSpacePosition = positionSnapGrid.ToScreenSpace(pos); } } diff --git a/osu.Game/Screens/Edit/Compose/Components/LinedPositionSnapGrid.cs b/osu.Game/Screens/Edit/Compose/Components/LinedPositionSnapGrid.cs index 3616bc1ca1..9ab12e4b71 100644 --- a/osu.Game/Screens/Edit/Compose/Components/LinedPositionSnapGrid.cs +++ b/osu.Game/Screens/Edit/Compose/Components/LinedPositionSnapGrid.cs @@ -5,61 +5,18 @@ using System; using System.Collections.Generic; using System.Linq; using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; -using osu.Framework.Layout; using osu.Framework.Utils; using osuTK; namespace osu.Game.Screens.Edit.Compose.Components { - public abstract partial class LinedPositionSnapGrid : CompositeDrawable + public abstract partial class LinedPositionSnapGrid : PositionSnapGrid { - private Vector2 startPosition; - - /// - /// The position of the origin of this in local coordinates. - /// - public Vector2 StartPosition - { - get => startPosition; - set - { - startPosition = value; - GridCache.Invalidate(); - } - } - - protected readonly LayoutValue GridCache = new LayoutValue(Invalidation.RequiredParentSizeToFit); - - protected LinedPositionSnapGrid() - { - Masking = true; - - AddLayout(GridCache); - } - - protected override void Update() - { - base.Update(); - - if (!GridCache.IsValid) - { - ClearInternal(); - - if (DrawWidth > 0 && DrawHeight > 0) - CreateContent(); - - GridCache.Validate(); - } - } - - protected abstract void CreateContent(); - protected void GenerateGridLines(Vector2 step, Vector2 drawSize) { int index = 0; - var currentPosition = startPosition; + var currentPosition = StartPosition; // Make lines the same width independent of display resolution. float lineWidth = DrawWidth / ScreenSpaceDrawQuad.Width; @@ -85,7 +42,7 @@ namespace osu.Game.Screens.Edit.Compose.Components generatedLines.Add(gridLine); index += 1; - currentPosition = startPosition + index * step; + currentPosition = StartPosition + index * step; } if (generatedLines.Count == 0) @@ -120,53 +77,5 @@ namespace osu.Game.Screens.Edit.Compose.Components !Precision.AlmostEquals(b, 0) && Math.Sign(a) != Math.Sign(b); } - - protected void GenerateOutline(Vector2 drawSize) - { - // Make lines the same width independent of display resolution. - float lineWidth = DrawWidth / ScreenSpaceDrawQuad.Width; - - AddRangeInternal(new[] - { - new Box - { - Colour = Colour4.White, - Alpha = 0.3f, - Origin = Anchor.CentreLeft, - RelativeSizeAxes = Axes.X, - Height = lineWidth, - Y = 0, - }, - new Box - { - Colour = Colour4.White, - Alpha = 0.3f, - Origin = Anchor.CentreLeft, - RelativeSizeAxes = Axes.X, - Height = lineWidth, - Y = drawSize.Y, - }, - new Box - { - Colour = Colour4.White, - Alpha = 0.3f, - Origin = Anchor.TopCentre, - RelativeSizeAxes = Axes.Y, - Width = lineWidth, - X = 0, - }, - new Box - { - Colour = Colour4.White, - Alpha = 0.3f, - Origin = Anchor.TopCentre, - RelativeSizeAxes = Axes.Y, - Width = lineWidth, - X = drawSize.X, - }, - }); - } - - public abstract Vector2 GetSnappedPosition(Vector2 original); } } diff --git a/osu.Game/Screens/Edit/Compose/Components/PositionSnapGrid.cs b/osu.Game/Screens/Edit/Compose/Components/PositionSnapGrid.cs new file mode 100644 index 0000000000..dd412e3cd3 --- /dev/null +++ b/osu.Game/Screens/Edit/Compose/Components/PositionSnapGrid.cs @@ -0,0 +1,102 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Layout; +using osuTK; + +namespace osu.Game.Screens.Edit.Compose.Components +{ + public abstract partial class PositionSnapGrid : CompositeDrawable + { + private Vector2 startPosition; + + /// + /// The position of the origin of this in local coordinates. + /// + public Vector2 StartPosition + { + get => startPosition; + set + { + startPosition = value; + GridCache.Invalidate(); + } + } + + protected readonly LayoutValue GridCache = new LayoutValue(Invalidation.RequiredParentSizeToFit); + + protected PositionSnapGrid() + { + Masking = true; + + AddLayout(GridCache); + } + + protected override void Update() + { + base.Update(); + + if (GridCache.IsValid) return; + + ClearInternal(); + + if (DrawWidth > 0 && DrawHeight > 0) + CreateContent(); + + GridCache.Validate(); + } + + protected abstract void CreateContent(); + + protected void GenerateOutline(Vector2 drawSize) + { + // Make lines the same width independent of display resolution. + float lineWidth = DrawWidth / ScreenSpaceDrawQuad.Width; + + AddRangeInternal(new[] + { + new Box + { + Colour = Colour4.White, + Alpha = 0.3f, + Origin = Anchor.CentreLeft, + RelativeSizeAxes = Axes.X, + Height = lineWidth, + Y = 0, + }, + new Box + { + Colour = Colour4.White, + Alpha = 0.3f, + Origin = Anchor.CentreLeft, + RelativeSizeAxes = Axes.X, + Height = lineWidth, + Y = drawSize.Y, + }, + new Box + { + Colour = Colour4.White, + Alpha = 0.3f, + Origin = Anchor.TopCentre, + RelativeSizeAxes = Axes.Y, + Width = lineWidth, + X = 0, + }, + new Box + { + Colour = Colour4.White, + Alpha = 0.3f, + Origin = Anchor.TopCentre, + RelativeSizeAxes = Axes.Y, + Width = lineWidth, + X = drawSize.X, + }, + }); + } + + public abstract Vector2 GetSnappedPosition(Vector2 original); + } +} From 847f04e63a0fb9b58bcea0ca35ea9d61880a5528 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Fri, 29 Dec 2023 17:32:09 +0100 Subject: [PATCH 033/139] reduce opacity of middle cardinal lines --- .../Screens/Edit/Compose/Components/LinedPositionSnapGrid.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/LinedPositionSnapGrid.cs b/osu.Game/Screens/Edit/Compose/Components/LinedPositionSnapGrid.cs index 9ab12e4b71..94da9c5b84 100644 --- a/osu.Game/Screens/Edit/Compose/Components/LinedPositionSnapGrid.cs +++ b/osu.Game/Screens/Edit/Compose/Components/LinedPositionSnapGrid.cs @@ -48,7 +48,7 @@ namespace osu.Game.Screens.Edit.Compose.Components if (generatedLines.Count == 0) return; - generatedLines.First().Alpha = 0.3f; + generatedLines.First().Alpha = 0.2f; AddRangeInternal(generatedLines); } From d0ca3f2b2b086ddbfae4ed5f9d6e44b471804722 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Fri, 29 Dec 2023 18:24:05 +0100 Subject: [PATCH 034/139] Add circular grid --- .../Edit/OsuGridToolboxGroup.cs | 6 +- .../Edit/OsuHitObjectComposer.cs | 8 ++ .../Components/CircularPositionSnapGrid.cs | 106 ++++++++++++++++++ 3 files changed, 119 insertions(+), 1 deletion(-) create mode 100644 osu.Game/Screens/Edit/Compose/Components/CircularPositionSnapGrid.cs diff --git a/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs b/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs index 892c89ebc2..0013f23057 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs @@ -100,7 +100,10 @@ namespace osu.Game.Rulesets.Osu.Edit () => new SpriteIcon { Icon = FontAwesome.Regular.Square }), new RadioButton("Triangle", () => GridType.Value = PositionSnapGridType.Triangle, - () => new Triangle()) + () => new Triangle()), + new RadioButton("Circle", + () => GridType.Value = PositionSnapGridType.Circle, + () => new SpriteIcon { Icon = FontAwesome.Regular.Circle }), } }, }; @@ -177,5 +180,6 @@ namespace osu.Game.Rulesets.Osu.Edit { Square, Triangle, + Circle, } } diff --git a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs index b0ac8467c1..1e7d07dbdd 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs @@ -135,6 +135,14 @@ namespace osu.Game.Rulesets.Osu.Edit positionSnapGrid = triangularPositionSnapGrid; break; + case PositionSnapGridType.Circle: + var circularPositionSnapGrid = new CircularPositionSnapGrid(); + + OsuGridToolboxGroup.Spacing.BindValueChanged(s => circularPositionSnapGrid.Spacing = s.NewValue, true); + + positionSnapGrid = circularPositionSnapGrid; + break; + default: throw new NotImplementedException($"{OsuGridToolboxGroup.GridType} has an incorrect value."); } diff --git a/osu.Game/Screens/Edit/Compose/Components/CircularPositionSnapGrid.cs b/osu.Game/Screens/Edit/Compose/Components/CircularPositionSnapGrid.cs new file mode 100644 index 0000000000..b9b0cbe389 --- /dev/null +++ b/osu.Game/Screens/Edit/Compose/Components/CircularPositionSnapGrid.cs @@ -0,0 +1,106 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using System.Linq; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Utils; +using osu.Game.Utils; +using osuTK; + +namespace osu.Game.Screens.Edit.Compose.Components +{ + public partial class CircularPositionSnapGrid : PositionSnapGrid + { + private float spacing = 1; + + /// + /// The spacing between grid lines of this . + /// + public float Spacing + { + get => spacing; + set + { + if (spacing <= 0) + throw new ArgumentException("Grid spacing must be positive."); + + spacing = value; + GridCache.Invalidate(); + } + } + + protected override void CreateContent() + { + var drawSize = DrawSize; + + // Calculate the maximum distance from the origin to the edge of the grid. + float maxDist = MathF.Max( + MathF.Max(StartPosition.Length, (StartPosition - drawSize).Length), + MathF.Max((StartPosition - new Vector2(drawSize.X, 0)).Length, (StartPosition - new Vector2(0, drawSize.Y)).Length) + ); + + generateCircles((int)(maxDist / Spacing) + 1); + + GenerateOutline(drawSize); + } + + private void generateCircles(int count) + { + // Make lines the same width independent of display resolution. + float lineWidth = 2 * DrawWidth / ScreenSpaceDrawQuad.Width; + + List generatedCircles = new List(); + + for (int i = 0; i < count; i++) + { + // Add a minimum diameter so the center circle is clearly visible. + float diameter = MathF.Max(lineWidth * 1.5f, i * Spacing * 2); + + var gridCircle = new CircularContainer + { + BorderColour = Colour4.White, + BorderThickness = lineWidth, + Alpha = 0.2f, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.None, + Width = diameter, + Height = diameter, + Position = StartPosition, + Masking = true, + Child = new Box + { + RelativeSizeAxes = Axes.Both, + AlwaysPresent = true, + Alpha = 0f, + } + }; + + generatedCircles.Add(gridCircle); + } + + if (generatedCircles.Count == 0) + return; + + generatedCircles.First().Alpha = 0.8f; + + AddRangeInternal(generatedCircles); + } + + public override Vector2 GetSnappedPosition(Vector2 original) + { + Vector2 relativeToStart = original - StartPosition; + + if (relativeToStart.LengthSquared < Precision.FLOAT_EPSILON) + return StartPosition; + + float length = relativeToStart.Length; + float wantedLength = MathF.Round(length / Spacing) * Spacing; + + return StartPosition + Vector2.Multiply(relativeToStart, wantedLength / length); + } + } +} From f649fa106f5de94592ff6d2ba55684625451bde3 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Sat, 30 Dec 2023 00:43:41 +0100 Subject: [PATCH 035/139] Added bindables and binding with BindTo --- .../Editor/TestSceneOsuEditorGrids.cs | 2 +- .../Edit/OsuGridToolboxGroup.cs | 28 +++++++++++ .../Edit/OsuHitObjectComposer.cs | 23 +++------ .../Components/CircularPositionSnapGrid.cs | 37 ++++++-------- .../Components/LinedPositionSnapGrid.cs | 4 +- .../Compose/Components/PositionSnapGrid.cs | 15 ++---- .../Components/RectangularPositionSnapGrid.cs | 46 ++++++----------- .../Components/TriangularPositionSnapGrid.cs | 49 +++++++------------ 8 files changed, 92 insertions(+), 112 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorGrids.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorGrids.cs index 299db23ccc..ff406b1b88 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorGrids.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorGrids.cs @@ -100,7 +100,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor } private void gridSizeIs(int size) - => AddAssert($"grid size is {size}", () => this.ChildrenOfType().Single().Spacing == new Vector2(size) + => AddAssert($"grid size is {size}", () => this.ChildrenOfType().Single().Spacing.Value == new Vector2(size) && EditorBeatmap.BeatmapInfo.GridSize == size); } } diff --git a/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs b/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs index 0013f23057..46e43deaae 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs @@ -16,6 +16,7 @@ using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Osu.UI; using osu.Game.Screens.Edit; using osu.Game.Screens.Edit.Components.RadioButtons; +using osuTK; namespace osu.Game.Rulesets.Osu.Edit { @@ -28,6 +29,9 @@ namespace osu.Game.Rulesets.Osu.Edit [Resolved] private EditorBeatmap editorBeatmap { get; set; } = null!; + /// + /// X position of the grid's origin. + /// public BindableFloat StartPositionX { get; } = new BindableFloat(OsuPlayfield.BASE_SIZE.X / 2) { MinValue = 0f, @@ -35,6 +39,9 @@ namespace osu.Game.Rulesets.Osu.Edit Precision = 1f }; + /// + /// Y position of the grid's origin. + /// public BindableFloat StartPositionY { get; } = new BindableFloat(OsuPlayfield.BASE_SIZE.Y / 2) { MinValue = 0f, @@ -42,6 +49,9 @@ namespace osu.Game.Rulesets.Osu.Edit Precision = 1f }; + /// + /// The spacing between grid lines. + /// public BindableFloat Spacing { get; } = new BindableFloat(4f) { MinValue = 4f, @@ -49,6 +59,9 @@ namespace osu.Game.Rulesets.Osu.Edit Precision = 1f }; + /// + /// Rotation of the grid lines in degrees. + /// public BindableFloat GridLinesRotation { get; } = new BindableFloat(0f) { MinValue = -45f, @@ -56,6 +69,18 @@ namespace osu.Game.Rulesets.Osu.Edit Precision = 1f }; + /// + /// Read-only bindable representing the grid's origin. + /// Equivalent to new Vector2(StartPositionX, StartPositionY) + /// + public Bindable StartPosition { get; } = new Bindable(); + + /// + /// Read-only bindable representing the grid's spacing in both the X and Y dimension. + /// Equivalent to new Vector2(Spacing) + /// + public Bindable SpacingVector { get; } = new Bindable(); + public Bindable GridType { get; } = new Bindable(); private ExpandableSlider startPositionXSlider = null!; @@ -124,18 +149,21 @@ namespace osu.Game.Rulesets.Osu.Edit { startPositionXSlider.ContractedLabelText = $"X: {x.NewValue:N0}"; startPositionXSlider.ExpandedLabelText = $"X Offset: {x.NewValue:N0}"; + StartPosition.Value = new Vector2(x.NewValue, StartPosition.Value.Y); }, true); StartPositionY.BindValueChanged(y => { startPositionYSlider.ContractedLabelText = $"Y: {y.NewValue:N0}"; startPositionYSlider.ExpandedLabelText = $"Y Offset: {y.NewValue:N0}"; + StartPosition.Value = new Vector2(StartPosition.Value.X, y.NewValue); }, true); Spacing.BindValueChanged(spacing => { spacingSlider.ContractedLabelText = $"S: {spacing.NewValue:N0}"; spacingSlider.ExpandedLabelText = $"Spacing: {spacing.NewValue:N0}"; + SpacingVector.Value = new Vector2(spacing.NewValue); }, true); GridLinesRotation.BindValueChanged(rotation => diff --git a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs index 1e7d07dbdd..84d5adbc52 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs @@ -120,8 +120,8 @@ namespace osu.Game.Rulesets.Osu.Edit case PositionSnapGridType.Square: var rectangularPositionSnapGrid = new RectangularPositionSnapGrid(); - OsuGridToolboxGroup.Spacing.BindValueChanged(s => rectangularPositionSnapGrid.Spacing = new Vector2(s.NewValue), true); - OsuGridToolboxGroup.GridLinesRotation.BindValueChanged(r => rectangularPositionSnapGrid.GridLineRotation = r.NewValue, true); + rectangularPositionSnapGrid.Spacing.BindTo(OsuGridToolboxGroup.SpacingVector); + rectangularPositionSnapGrid.GridLineRotation.BindTo(OsuGridToolboxGroup.GridLinesRotation); positionSnapGrid = rectangularPositionSnapGrid; break; @@ -129,8 +129,8 @@ namespace osu.Game.Rulesets.Osu.Edit case PositionSnapGridType.Triangle: var triangularPositionSnapGrid = new TriangularPositionSnapGrid(); - OsuGridToolboxGroup.Spacing.BindValueChanged(s => triangularPositionSnapGrid.Spacing = s.NewValue, true); - OsuGridToolboxGroup.GridLinesRotation.BindValueChanged(r => triangularPositionSnapGrid.GridLineRotation = r.NewValue, true); + triangularPositionSnapGrid.Spacing.BindTo(OsuGridToolboxGroup.Spacing); + triangularPositionSnapGrid.GridLineRotation.BindTo(OsuGridToolboxGroup.GridLinesRotation); positionSnapGrid = triangularPositionSnapGrid; break; @@ -138,7 +138,7 @@ namespace osu.Game.Rulesets.Osu.Edit case PositionSnapGridType.Circle: var circularPositionSnapGrid = new CircularPositionSnapGrid(); - OsuGridToolboxGroup.Spacing.BindValueChanged(s => circularPositionSnapGrid.Spacing = s.NewValue, true); + circularPositionSnapGrid.Spacing.BindTo(OsuGridToolboxGroup.Spacing); positionSnapGrid = circularPositionSnapGrid; break; @@ -147,18 +147,11 @@ namespace osu.Game.Rulesets.Osu.Edit throw new NotImplementedException($"{OsuGridToolboxGroup.GridType} has an incorrect value."); } - bindPositionSnapGridStartPosition(positionSnapGrid); + // Bind the start position to the toolbox sliders. + positionSnapGrid.StartPosition.BindTo(OsuGridToolboxGroup.StartPosition); + positionSnapGrid.RelativeSizeAxes = Axes.Both; LayerBelowRuleset.Add(positionSnapGrid); - return; - - void bindPositionSnapGridStartPosition(PositionSnapGrid snapGrid) - { - OsuGridToolboxGroup.StartPositionX.BindValueChanged(x => - snapGrid.StartPosition = new Vector2(x.NewValue, snapGrid.StartPosition.Y), true); - OsuGridToolboxGroup.StartPositionY.BindValueChanged(y => - snapGrid.StartPosition = new Vector2(snapGrid.StartPosition.X, y.NewValue), true); - } } protected override ComposeBlueprintContainer CreateBlueprintContainer() diff --git a/osu.Game/Screens/Edit/Compose/Components/CircularPositionSnapGrid.cs b/osu.Game/Screens/Edit/Compose/Components/CircularPositionSnapGrid.cs index b9b0cbe389..403a270359 100644 --- a/osu.Game/Screens/Edit/Compose/Components/CircularPositionSnapGrid.cs +++ b/osu.Game/Screens/Edit/Compose/Components/CircularPositionSnapGrid.cs @@ -4,33 +4,28 @@ using System; using System.Collections.Generic; using System.Linq; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Utils; -using osu.Game.Utils; using osuTK; namespace osu.Game.Screens.Edit.Compose.Components { public partial class CircularPositionSnapGrid : PositionSnapGrid { - private float spacing = 1; - /// /// The spacing between grid lines of this . /// - public float Spacing + public BindableFloat Spacing { get; } = new BindableFloat(1f) { - get => spacing; - set - { - if (spacing <= 0) - throw new ArgumentException("Grid spacing must be positive."); + MinValue = 0f, + }; - spacing = value; - GridCache.Invalidate(); - } + public CircularPositionSnapGrid() + { + Spacing.BindValueChanged(_ => GridCache.Invalidate()); } protected override void CreateContent() @@ -39,11 +34,11 @@ namespace osu.Game.Screens.Edit.Compose.Components // Calculate the maximum distance from the origin to the edge of the grid. float maxDist = MathF.Max( - MathF.Max(StartPosition.Length, (StartPosition - drawSize).Length), - MathF.Max((StartPosition - new Vector2(drawSize.X, 0)).Length, (StartPosition - new Vector2(0, drawSize.Y)).Length) + MathF.Max(StartPosition.Value.Length, (StartPosition.Value - drawSize).Length), + MathF.Max((StartPosition.Value - new Vector2(drawSize.X, 0)).Length, (StartPosition.Value - new Vector2(0, drawSize.Y)).Length) ); - generateCircles((int)(maxDist / Spacing) + 1); + generateCircles((int)(maxDist / Spacing.Value) + 1); GenerateOutline(drawSize); } @@ -58,7 +53,7 @@ namespace osu.Game.Screens.Edit.Compose.Components for (int i = 0; i < count; i++) { // Add a minimum diameter so the center circle is clearly visible. - float diameter = MathF.Max(lineWidth * 1.5f, i * Spacing * 2); + float diameter = MathF.Max(lineWidth * 1.5f, i * Spacing.Value * 2); var gridCircle = new CircularContainer { @@ -69,7 +64,7 @@ namespace osu.Game.Screens.Edit.Compose.Components RelativeSizeAxes = Axes.None, Width = diameter, Height = diameter, - Position = StartPosition, + Position = StartPosition.Value, Masking = true, Child = new Box { @@ -92,15 +87,15 @@ namespace osu.Game.Screens.Edit.Compose.Components public override Vector2 GetSnappedPosition(Vector2 original) { - Vector2 relativeToStart = original - StartPosition; + Vector2 relativeToStart = original - StartPosition.Value; if (relativeToStart.LengthSquared < Precision.FLOAT_EPSILON) - return StartPosition; + return StartPosition.Value; float length = relativeToStart.Length; - float wantedLength = MathF.Round(length / Spacing) * Spacing; + float wantedLength = MathF.Round(length / Spacing.Value) * Spacing.Value; - return StartPosition + Vector2.Multiply(relativeToStart, wantedLength / length); + return StartPosition.Value + Vector2.Multiply(relativeToStart, wantedLength / length); } } } diff --git a/osu.Game/Screens/Edit/Compose/Components/LinedPositionSnapGrid.cs b/osu.Game/Screens/Edit/Compose/Components/LinedPositionSnapGrid.cs index 94da9c5b84..ebdd76a4e2 100644 --- a/osu.Game/Screens/Edit/Compose/Components/LinedPositionSnapGrid.cs +++ b/osu.Game/Screens/Edit/Compose/Components/LinedPositionSnapGrid.cs @@ -16,7 +16,7 @@ namespace osu.Game.Screens.Edit.Compose.Components protected void GenerateGridLines(Vector2 step, Vector2 drawSize) { int index = 0; - var currentPosition = StartPosition; + var currentPosition = StartPosition.Value; // Make lines the same width independent of display resolution. float lineWidth = DrawWidth / ScreenSpaceDrawQuad.Width; @@ -42,7 +42,7 @@ namespace osu.Game.Screens.Edit.Compose.Components generatedLines.Add(gridLine); index += 1; - currentPosition = StartPosition + index * step; + currentPosition = StartPosition.Value + index * step; } if (generatedLines.Count == 0) diff --git a/osu.Game/Screens/Edit/Compose/Components/PositionSnapGrid.cs b/osu.Game/Screens/Edit/Compose/Components/PositionSnapGrid.cs index dd412e3cd3..36687ef73a 100644 --- a/osu.Game/Screens/Edit/Compose/Components/PositionSnapGrid.cs +++ b/osu.Game/Screens/Edit/Compose/Components/PositionSnapGrid.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 osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; @@ -11,20 +12,10 @@ namespace osu.Game.Screens.Edit.Compose.Components { public abstract partial class PositionSnapGrid : CompositeDrawable { - private Vector2 startPosition; - /// /// The position of the origin of this in local coordinates. /// - public Vector2 StartPosition - { - get => startPosition; - set - { - startPosition = value; - GridCache.Invalidate(); - } - } + public Bindable StartPosition { get; } = new Bindable(Vector2.Zero); protected readonly LayoutValue GridCache = new LayoutValue(Invalidation.RequiredParentSizeToFit); @@ -32,6 +23,8 @@ namespace osu.Game.Screens.Edit.Compose.Components { Masking = true; + StartPosition.BindValueChanged(_ => GridCache.Invalidate()); + AddLayout(GridCache); } diff --git a/osu.Game/Screens/Edit/Compose/Components/RectangularPositionSnapGrid.cs b/osu.Game/Screens/Edit/Compose/Components/RectangularPositionSnapGrid.cs index 2392921203..3bf0ef8ac3 100644 --- a/osu.Game/Screens/Edit/Compose/Components/RectangularPositionSnapGrid.cs +++ b/osu.Game/Screens/Edit/Compose/Components/RectangularPositionSnapGrid.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using osu.Framework.Bindables; using osu.Game.Utils; using osuTK; @@ -9,60 +10,43 @@ namespace osu.Game.Screens.Edit.Compose.Components { public partial class RectangularPositionSnapGrid : LinedPositionSnapGrid { - private Vector2 spacing = Vector2.One; - /// /// The spacing between grid lines of this . /// - public Vector2 Spacing - { - get => spacing; - set - { - if (spacing.X <= 0 || spacing.Y <= 0) - throw new ArgumentException("Grid spacing must be positive."); - - spacing = value; - GridCache.Invalidate(); - } - } - - private float gridLineRotation; + public Bindable Spacing { get; } = new Bindable(Vector2.One); /// /// The rotation in degrees of the grid lines of this . /// - public float GridLineRotation + public BindableFloat GridLineRotation { get; } = new BindableFloat(); + + public RectangularPositionSnapGrid() { - get => gridLineRotation; - set - { - gridLineRotation = value; - GridCache.Invalidate(); - } + Spacing.BindValueChanged(_ => GridCache.Invalidate()); + GridLineRotation.BindValueChanged(_ => GridCache.Invalidate()); } protected override void CreateContent() { var drawSize = DrawSize; - var rot = Quaternion.FromAxisAngle(Vector3.UnitZ, MathHelper.DegreesToRadians(GridLineRotation)); + var rot = Quaternion.FromAxisAngle(Vector3.UnitZ, MathHelper.DegreesToRadians(GridLineRotation.Value)); - GenerateGridLines(Vector2.Transform(new Vector2(0, -Spacing.Y), rot), drawSize); - GenerateGridLines(Vector2.Transform(new Vector2(0, Spacing.Y), rot), drawSize); + GenerateGridLines(Vector2.Transform(new Vector2(0, -Spacing.Value.Y), rot), drawSize); + GenerateGridLines(Vector2.Transform(new Vector2(0, Spacing.Value.Y), rot), drawSize); - GenerateGridLines(Vector2.Transform(new Vector2(-Spacing.X, 0), rot), drawSize); - GenerateGridLines(Vector2.Transform(new Vector2(Spacing.X, 0), rot), drawSize); + GenerateGridLines(Vector2.Transform(new Vector2(-Spacing.Value.X, 0), rot), drawSize); + GenerateGridLines(Vector2.Transform(new Vector2(Spacing.Value.X, 0), rot), drawSize); GenerateOutline(drawSize); } public override Vector2 GetSnappedPosition(Vector2 original) { - Vector2 relativeToStart = GeometryUtils.RotateVector(original - StartPosition, GridLineRotation); - Vector2 offset = Vector2.Divide(relativeToStart, Spacing); + Vector2 relativeToStart = GeometryUtils.RotateVector(original - StartPosition.Value, GridLineRotation.Value); + Vector2 offset = Vector2.Divide(relativeToStart, Spacing.Value); Vector2 roundedOffset = new Vector2(MathF.Round(offset.X), MathF.Round(offset.Y)); - return StartPosition + GeometryUtils.RotateVector(Vector2.Multiply(roundedOffset, Spacing), -GridLineRotation); + return StartPosition.Value + GeometryUtils.RotateVector(Vector2.Multiply(roundedOffset, Spacing.Value), -GridLineRotation.Value); } } } diff --git a/osu.Game/Screens/Edit/Compose/Components/TriangularPositionSnapGrid.cs b/osu.Game/Screens/Edit/Compose/Components/TriangularPositionSnapGrid.cs index c98890e294..93d2c6a74a 100644 --- a/osu.Game/Screens/Edit/Compose/Components/TriangularPositionSnapGrid.cs +++ b/osu.Game/Screens/Edit/Compose/Components/TriangularPositionSnapGrid.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using osu.Framework.Bindables; using osu.Game.Utils; using osuTK; @@ -9,37 +10,23 @@ namespace osu.Game.Screens.Edit.Compose.Components { public partial class TriangularPositionSnapGrid : LinedPositionSnapGrid { - private float spacing = 1; - /// /// The spacing between grid lines of this . /// - public float Spacing + public BindableFloat Spacing { get; } = new BindableFloat(1f) { - get => spacing; - set - { - if (spacing <= 0) - throw new ArgumentException("Grid spacing must be positive."); - - spacing = value; - GridCache.Invalidate(); - } - } - - private float gridLineRotation; + MinValue = 0f, + }; /// /// The rotation in degrees of the grid lines of this . /// - public float GridLineRotation + public BindableFloat GridLineRotation { get; } = new BindableFloat(); + + public TriangularPositionSnapGrid() { - get => gridLineRotation; - set - { - gridLineRotation = value; - GridCache.Invalidate(); - } + Spacing.BindValueChanged(_ => GridCache.Invalidate()); + GridLineRotation.BindValueChanged(_ => GridCache.Invalidate()); } private const float sqrt3 = 1.73205080757f; @@ -49,10 +36,10 @@ namespace osu.Game.Screens.Edit.Compose.Components protected override void CreateContent() { var drawSize = DrawSize; - float stepSpacing = Spacing * sqrt3_over2; - var step1 = GeometryUtils.RotateVector(new Vector2(stepSpacing, 0), -GridLineRotation - 30); - var step2 = GeometryUtils.RotateVector(new Vector2(stepSpacing, 0), -GridLineRotation - 90); - var step3 = GeometryUtils.RotateVector(new Vector2(stepSpacing, 0), -GridLineRotation - 150); + float stepSpacing = Spacing.Value * sqrt3_over2; + var step1 = GeometryUtils.RotateVector(new Vector2(stepSpacing, 0), -GridLineRotation.Value - 30); + var step2 = GeometryUtils.RotateVector(new Vector2(stepSpacing, 0), -GridLineRotation.Value - 90); + var step3 = GeometryUtils.RotateVector(new Vector2(stepSpacing, 0), -GridLineRotation.Value - 150); GenerateGridLines(step1, drawSize); GenerateGridLines(-step1, drawSize); @@ -68,16 +55,16 @@ namespace osu.Game.Screens.Edit.Compose.Components public override Vector2 GetSnappedPosition(Vector2 original) { - Vector2 relativeToStart = GeometryUtils.RotateVector(original - StartPosition, GridLineRotation); + Vector2 relativeToStart = GeometryUtils.RotateVector(original - StartPosition.Value, GridLineRotation.Value); Vector2 hex = pixelToHex(relativeToStart); - return StartPosition + GeometryUtils.RotateVector(hexToPixel(hex), -GridLineRotation); + return StartPosition.Value + GeometryUtils.RotateVector(hexToPixel(hex), -GridLineRotation.Value); } private Vector2 pixelToHex(Vector2 pixel) { - float x = pixel.X / Spacing; - float y = pixel.Y / Spacing; + float x = pixel.X / Spacing.Value; + float y = pixel.Y / Spacing.Value; // Algorithm from Charles Chambers // with modifications and comments by Chris Cox 2023 // @@ -96,7 +83,7 @@ namespace osu.Game.Screens.Edit.Compose.Components { // Taken from // with modifications for the different definition of size. - return new Vector2(Spacing * (hex.X - hex.Y / 2), Spacing * one_over_sqrt3 * 1.5f * hex.Y); + return new Vector2(Spacing.Value * (hex.X - hex.Y / 2), Spacing.Value * one_over_sqrt3 * 1.5f * hex.Y); } } } From 1c75357d77fba3723476d2bd1657e1d1fdd45efd Mon Sep 17 00:00:00 2001 From: OliBomby Date: Sat, 30 Dec 2023 01:12:23 +0100 Subject: [PATCH 036/139] fix compile --- osu.Game.Tournament/Screens/Editors/LadderEditorScreen.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tournament/Screens/Editors/LadderEditorScreen.cs b/osu.Game.Tournament/Screens/Editors/LadderEditorScreen.cs index ad00e8d47d..a7f0a52003 100644 --- a/osu.Game.Tournament/Screens/Editors/LadderEditorScreen.cs +++ b/osu.Game.Tournament/Screens/Editors/LadderEditorScreen.cs @@ -53,13 +53,14 @@ namespace osu.Game.Tournament.Screens.Editors ScrollContent.Add(grid = new RectangularPositionSnapGrid { - Spacing = new Vector2(GRID_SPACING), Anchor = Anchor.Centre, Origin = Anchor.Centre, BypassAutoSizeAxes = Axes.Both, Depth = float.MaxValue }); + grid.Spacing.Value = new Vector2(GRID_SPACING); + LadderInfo.Matches.CollectionChanged += (_, _) => updateMessage(); updateMessage(); } From 9a8c41f6ca8a4e1df4d3f98406126d40858ed419 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Sat, 30 Dec 2023 14:32:20 +0100 Subject: [PATCH 037/139] Saving exact grid spacing --- osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs b/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs index 46e43deaae..442575711a 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs @@ -133,10 +133,10 @@ namespace osu.Game.Rulesets.Osu.Edit }, }; + Spacing.Value = editorBeatmap.BeatmapInfo.GridSize; int gridSizeIndex = Array.IndexOf(grid_sizes, editorBeatmap.BeatmapInfo.GridSize); if (gridSizeIndex >= 0) currentGridSizeIndex = gridSizeIndex; - updateSpacing(); } protected override void LoadComplete() @@ -164,6 +164,7 @@ namespace osu.Game.Rulesets.Osu.Edit spacingSlider.ContractedLabelText = $"S: {spacing.NewValue:N0}"; spacingSlider.ExpandedLabelText = $"Spacing: {spacing.NewValue:N0}"; SpacingVector.Value = new Vector2(spacing.NewValue); + editorBeatmap.BeatmapInfo.GridSize = (int)spacing.NewValue; }, true); GridLinesRotation.BindValueChanged(rotation => @@ -176,15 +177,7 @@ namespace osu.Game.Rulesets.Osu.Edit private void nextGridSize() { currentGridSizeIndex = (currentGridSizeIndex + 1) % grid_sizes.Length; - updateSpacing(); - } - - private void updateSpacing() - { - int gridSize = grid_sizes[currentGridSizeIndex]; - - editorBeatmap.BeatmapInfo.GridSize = gridSize; - Spacing.Value = gridSize; + Spacing.Value = grid_sizes[currentGridSizeIndex]; } public bool OnPressed(KeyBindingPressEvent e) From 493e3a5f7a2cf9b355cf8319abe16b12c069f92d Mon Sep 17 00:00:00 2001 From: OliBomby Date: Sun, 31 Dec 2023 02:55:47 +0100 Subject: [PATCH 038/139] use G to change grid type --- .../Edit/OsuGridToolboxGroup.cs | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs b/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs index 442575711a..c07e8028b6 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs @@ -22,9 +22,9 @@ namespace osu.Game.Rulesets.Osu.Edit { public partial class OsuGridToolboxGroup : EditorToolboxGroup, IKeyBindingHandler { - private static readonly int[] grid_sizes = { 4, 8, 16, 32 }; + private static readonly PositionSnapGridType[] grid_types = Enum.GetValues(typeof(PositionSnapGridType)).Cast().ToArray(); - private int currentGridSizeIndex = grid_sizes.Length - 1; + private int currentGridTypeIndex; [Resolved] private EditorBeatmap editorBeatmap { get; set; } = null!; @@ -134,9 +134,6 @@ namespace osu.Game.Rulesets.Osu.Edit }; Spacing.Value = editorBeatmap.BeatmapInfo.GridSize; - int gridSizeIndex = Array.IndexOf(grid_sizes, editorBeatmap.BeatmapInfo.GridSize); - if (gridSizeIndex >= 0) - currentGridSizeIndex = gridSizeIndex; } protected override void LoadComplete() @@ -174,10 +171,11 @@ namespace osu.Game.Rulesets.Osu.Edit }, true); } - private void nextGridSize() + private void nextGridType() { - currentGridSizeIndex = (currentGridSizeIndex + 1) % grid_sizes.Length; - Spacing.Value = grid_sizes[currentGridSizeIndex]; + currentGridTypeIndex = (currentGridTypeIndex + 1) % grid_types.Length; + GridType.Value = grid_types[currentGridTypeIndex]; + gridTypeButtons.Items[currentGridTypeIndex].Select(); } public bool OnPressed(KeyBindingPressEvent e) @@ -185,7 +183,7 @@ namespace osu.Game.Rulesets.Osu.Edit switch (e.Action) { case GlobalAction.EditorCycleGridDisplayMode: - nextGridSize(); + nextGridType(); return true; } From e47d570e68d155375833f9a06f71820fabd7af47 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Sun, 31 Dec 2023 03:53:42 +0100 Subject: [PATCH 039/139] improve UI --- .../Edit/OsuGridToolboxGroup.cs | 38 ++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs b/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs index c07e8028b6..da5849a77a 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs @@ -6,10 +6,12 @@ using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; +using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterface; using osu.Game.Input.Bindings; using osu.Game.Rulesets.Edit; @@ -17,6 +19,7 @@ using osu.Game.Rulesets.Osu.UI; using osu.Game.Screens.Edit; using osu.Game.Screens.Edit.Components.RadioButtons; using osuTK; +using osuTK.Graphics; namespace osu.Game.Rulesets.Osu.Edit { @@ -29,6 +32,9 @@ namespace osu.Game.Rulesets.Osu.Edit [Resolved] private EditorBeatmap editorBeatmap { get; set; } = null!; + [Resolved] + private IExpandingContainer? expandingContainer { get; set; } + /// /// X position of the grid's origin. /// @@ -125,7 +131,7 @@ namespace osu.Game.Rulesets.Osu.Edit () => new SpriteIcon { Icon = FontAwesome.Regular.Square }), new RadioButton("Triangle", () => GridType.Value = PositionSnapGridType.Triangle, - () => new Triangle()), + () => new OutlineTriangle(true, 20)), new RadioButton("Circle", () => GridType.Value = PositionSnapGridType.Circle, () => new SpriteIcon { Icon = FontAwesome.Regular.Circle }), @@ -136,6 +142,36 @@ namespace osu.Game.Rulesets.Osu.Edit Spacing.Value = editorBeatmap.BeatmapInfo.GridSize; } + public partial class OutlineTriangle : BufferedContainer + { + public OutlineTriangle(bool outlineOnly, float size) + : base(cachedFrameBuffer: true) + { + Size = new Vector2(size); + + InternalChildren = new Drawable[] + { + new EquilateralTriangle { RelativeSizeAxes = Axes.Both }, + }; + + if (outlineOnly) + { + AddInternal(new EquilateralTriangle + { + Anchor = Anchor.TopCentre, + Origin = Anchor.Centre, + RelativePositionAxes = Axes.Y, + Y = 0.48f, + Colour = Color4.Black, + Size = new Vector2(size - 7), + Blending = BlendingParameters.None, + }); + } + + Blending = BlendingParameters.Additive; + } + } + protected override void LoadComplete() { base.LoadComplete(); From 904ea2e436bf694f5e95f0bf02358bd6668304b1 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Sun, 31 Dec 2023 15:48:09 +0100 Subject: [PATCH 040/139] move OutlineTriangle code down --- .../Edit/OsuGridToolboxGroup.cs | 60 +++++++++---------- 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs b/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs index da5849a77a..72e60a5515 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs @@ -142,36 +142,6 @@ namespace osu.Game.Rulesets.Osu.Edit Spacing.Value = editorBeatmap.BeatmapInfo.GridSize; } - public partial class OutlineTriangle : BufferedContainer - { - public OutlineTriangle(bool outlineOnly, float size) - : base(cachedFrameBuffer: true) - { - Size = new Vector2(size); - - InternalChildren = new Drawable[] - { - new EquilateralTriangle { RelativeSizeAxes = Axes.Both }, - }; - - if (outlineOnly) - { - AddInternal(new EquilateralTriangle - { - Anchor = Anchor.TopCentre, - Origin = Anchor.Centre, - RelativePositionAxes = Axes.Y, - Y = 0.48f, - Colour = Color4.Black, - Size = new Vector2(size - 7), - Blending = BlendingParameters.None, - }); - } - - Blending = BlendingParameters.Additive; - } - } - protected override void LoadComplete() { base.LoadComplete(); @@ -229,6 +199,36 @@ namespace osu.Game.Rulesets.Osu.Edit public void OnReleased(KeyBindingReleaseEvent e) { } + + public partial class OutlineTriangle : BufferedContainer + { + public OutlineTriangle(bool outlineOnly, float size) + : base(cachedFrameBuffer: true) + { + Size = new Vector2(size); + + InternalChildren = new Drawable[] + { + new EquilateralTriangle { RelativeSizeAxes = Axes.Both }, + }; + + if (outlineOnly) + { + AddInternal(new EquilateralTriangle + { + Anchor = Anchor.TopCentre, + Origin = Anchor.Centre, + RelativePositionAxes = Axes.Y, + Y = 0.48f, + Colour = Color4.Black, + Size = new Vector2(size - 7), + Blending = BlendingParameters.None, + }); + } + + Blending = BlendingParameters.Additive; + } + } } public enum PositionSnapGridType From 33e559f83502f84f93d328fea61d9e9fc7d83679 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Sun, 31 Dec 2023 16:41:22 +0100 Subject: [PATCH 041/139] add integer keyboard step to sliders --- osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs b/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs index 72e60a5515..6ed7054159 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs @@ -107,19 +107,23 @@ namespace osu.Game.Rulesets.Osu.Edit { startPositionXSlider = new ExpandableSlider { - Current = StartPositionX + Current = StartPositionX, + KeyboardStep = 1, }, startPositionYSlider = new ExpandableSlider { - Current = StartPositionY + Current = StartPositionY, + KeyboardStep = 1, }, spacingSlider = new ExpandableSlider { - Current = Spacing + Current = Spacing, + KeyboardStep = 1, }, gridLinesRotationSlider = new ExpandableSlider { - Current = GridLinesRotation + Current = GridLinesRotation, + KeyboardStep = 1, }, gridTypeButtons = new EditorRadioButtonCollection { From 20e338b8920d33180434ce6c4a7f362118a6fdfe Mon Sep 17 00:00:00 2001 From: OliBomby Date: Sun, 31 Dec 2023 16:45:21 +0100 Subject: [PATCH 042/139] also hide grid from points button when not hovered --- .../Edit/OsuGridToolboxGroup.cs | 41 +++++++++++++------ 1 file changed, 28 insertions(+), 13 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs b/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs index 6ed7054159..981148858d 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs @@ -125,20 +125,29 @@ namespace osu.Game.Rulesets.Osu.Edit Current = GridLinesRotation, KeyboardStep = 1, }, - gridTypeButtons = new EditorRadioButtonCollection + new FillFlowContainer { RelativeSizeAxes = Axes.X, - Items = new[] + AutoSizeAxes = Axes.Y, + Spacing = new Vector2(0f, 10f), + Children = new Drawable[] { - new RadioButton("Square", - () => GridType.Value = PositionSnapGridType.Square, - () => new SpriteIcon { Icon = FontAwesome.Regular.Square }), - new RadioButton("Triangle", - () => GridType.Value = PositionSnapGridType.Triangle, - () => new OutlineTriangle(true, 20)), - new RadioButton("Circle", - () => GridType.Value = PositionSnapGridType.Circle, - () => new SpriteIcon { Icon = FontAwesome.Regular.Circle }), + gridTypeButtons = new EditorRadioButtonCollection + { + RelativeSizeAxes = Axes.X, + Items = new[] + { + new RadioButton("Square", + () => GridType.Value = PositionSnapGridType.Square, + () => new SpriteIcon { Icon = FontAwesome.Regular.Square }), + new RadioButton("Triangle", + () => GridType.Value = PositionSnapGridType.Triangle, + () => new OutlineTriangle(true, 20)), + new RadioButton("Circle", + () => GridType.Value = PositionSnapGridType.Circle, + () => new SpriteIcon { Icon = FontAwesome.Regular.Circle }), + } + }, } }, }; @@ -176,8 +185,14 @@ namespace osu.Game.Rulesets.Osu.Edit GridLinesRotation.BindValueChanged(rotation => { - gridLinesRotationSlider.ContractedLabelText = $"R: {rotation.NewValue:N0}"; - gridLinesRotationSlider.ExpandedLabelText = $"Rotation: {rotation.NewValue:N0}"; + gridLinesRotationSlider.ContractedLabelText = $"R: {rotation.NewValue:#,0.##}"; + gridLinesRotationSlider.ExpandedLabelText = $"Rotation: {rotation.NewValue:#,0.##}"; + }, true); + + expandingContainer?.Expanded.BindValueChanged(v => + { + gridTypeButtons.FadeTo(v.NewValue ? 1f : 0f, 500, Easing.OutQuint); + gridTypeButtons.BypassAutoSizeAxes = !v.NewValue ? Axes.Y : Axes.None; }, true); } From 8425c7226c20fcafe961ca4e6c72b3d718dc7105 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Sun, 31 Dec 2023 16:54:04 +0100 Subject: [PATCH 043/139] fix rectangular and triangular grid tests --- .../Editing/TestSceneRectangularPositionSnapGrid.cs | 13 ++++++++----- .../Editing/TestSceneTriangularPositionSnapGrid.cs | 12 ++++++++---- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneRectangularPositionSnapGrid.cs b/osu.Game.Tests/Visual/Editing/TestSceneRectangularPositionSnapGrid.cs index a0042cf605..19903737f6 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneRectangularPositionSnapGrid.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneRectangularPositionSnapGrid.cs @@ -52,12 +52,15 @@ namespace osu.Game.Tests.Visual.Editing { RectangularPositionSnapGrid grid = null; - AddStep("create grid", () => Child = grid = new RectangularPositionSnapGrid() + AddStep("create grid", () => { - RelativeSizeAxes = Axes.Both, - StartPosition = position, - Spacing = spacing, - GridLineRotation = rotation + Child = grid = new RectangularPositionSnapGrid + { + RelativeSizeAxes = Axes.Both, + }; + grid.StartPosition.Value = position; + grid.Spacing.Value = spacing; + grid.GridLineRotation.Value = rotation; }); AddStep("add snapping cursor", () => Add(new SnappingCursorContainer diff --git a/osu.Game.Tests/Visual/Editing/TestSceneTriangularPositionSnapGrid.cs b/osu.Game.Tests/Visual/Editing/TestSceneTriangularPositionSnapGrid.cs index 2f5ffd8423..b1f82fa114 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneTriangularPositionSnapGrid.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneTriangularPositionSnapGrid.cs @@ -52,11 +52,15 @@ namespace osu.Game.Tests.Visual.Editing { TriangularPositionSnapGrid grid = null; - AddStep("create grid", () => Child = grid = new TriangularPositionSnapGrid(position) + AddStep("create grid", () => { - RelativeSizeAxes = Axes.Both, - Spacing = spacing, - GridLineRotation = rotation + Child = grid = new TriangularPositionSnapGrid + { + RelativeSizeAxes = Axes.Both, + }; + grid.StartPosition.Value = position; + grid.Spacing.Value = spacing; + grid.GridLineRotation.Value = rotation; }); AddStep("add snapping cursor", () => Add(new SnappingCursorContainer From 31d17994807dd4fe36b2cc73b699753884f21d19 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Sun, 31 Dec 2023 16:56:10 +0100 Subject: [PATCH 044/139] Create TestSceneCircularPositionSnapGrid.cs --- .../TestSceneCircularPositionSnapGrid.cs | 111 ++++++++++++++++++ 1 file changed, 111 insertions(+) create mode 100644 osu.Game.Tests/Visual/Editing/TestSceneCircularPositionSnapGrid.cs diff --git a/osu.Game.Tests/Visual/Editing/TestSceneCircularPositionSnapGrid.cs b/osu.Game.Tests/Visual/Editing/TestSceneCircularPositionSnapGrid.cs new file mode 100644 index 0000000000..4481199c94 --- /dev/null +++ b/osu.Game.Tests/Visual/Editing/TestSceneCircularPositionSnapGrid.cs @@ -0,0 +1,111 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +#nullable disable + +using System; +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Input.Events; +using osu.Game.Screens.Edit.Compose.Components; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Tests.Visual.Editing +{ + public partial class TestSceneCircularPositionSnapGrid : OsuManualInputManagerTestScene + { + private Container content; + protected override Container Content => content; + + [BackgroundDependencyLoader] + private void load() + { + base.Content.AddRange(new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Colour4.Gray + }, + content = new Container + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding(10), + } + }); + } + + private static readonly object[][] test_cases = + { + new object[] { new Vector2(0, 0), 10, 0f }, + new object[] { new Vector2(240, 180), 10, 10f }, + new object[] { new Vector2(160, 120), 30, -10f }, + new object[] { new Vector2(480, 360), 100, 0f }, + }; + + [TestCaseSource(nameof(test_cases))] + public void TestCircularGrid(Vector2 position, float spacing, float rotation) + { + CircularPositionSnapGrid grid = null; + + AddStep("create grid", () => + { + Child = grid = new CircularPositionSnapGrid + { + RelativeSizeAxes = Axes.Both, + }; + grid.StartPosition.Value = position; + grid.Spacing.Value = spacing; + }); + + AddStep("add snapping cursor", () => Add(new SnappingCursorContainer + { + RelativeSizeAxes = Axes.Both, + GetSnapPosition = pos => grid.GetSnappedPosition(grid.ToLocalSpace(pos)) + })); + } + + private partial class SnappingCursorContainer : CompositeDrawable + { + public Func GetSnapPosition; + + private readonly Drawable cursor; + + public SnappingCursorContainer() + { + RelativeSizeAxes = Axes.Both; + + InternalChild = cursor = new Circle + { + Origin = Anchor.Centre, + Size = new Vector2(50), + Colour = Color4.Red + }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + updatePosition(GetContainingInputManager().CurrentState.Mouse.Position); + } + + protected override bool OnMouseMove(MouseMoveEvent e) + { + base.OnMouseMove(e); + + updatePosition(e.ScreenSpaceMousePosition); + return true; + } + + private void updatePosition(Vector2 screenSpacePosition) + { + cursor.Position = GetSnapPosition.Invoke(screenSpacePosition); + } + } + } +} From 9796fcff520b52a150cb469c046d13a42a0cc543 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Sun, 31 Dec 2023 16:59:46 +0100 Subject: [PATCH 045/139] Merge position snap grid tests into single file --- .../TestSceneCircularPositionSnapGrid.cs | 111 ----------------- ...apGrid.cs => TestScenePositionSnapGrid.cs} | 51 +++++++- .../TestSceneTriangularPositionSnapGrid.cs | 112 ------------------ 3 files changed, 48 insertions(+), 226 deletions(-) delete mode 100644 osu.Game.Tests/Visual/Editing/TestSceneCircularPositionSnapGrid.cs rename osu.Game.Tests/Visual/Editing/{TestSceneRectangularPositionSnapGrid.cs => TestScenePositionSnapGrid.cs} (66%) delete mode 100644 osu.Game.Tests/Visual/Editing/TestSceneTriangularPositionSnapGrid.cs diff --git a/osu.Game.Tests/Visual/Editing/TestSceneCircularPositionSnapGrid.cs b/osu.Game.Tests/Visual/Editing/TestSceneCircularPositionSnapGrid.cs deleted file mode 100644 index 4481199c94..0000000000 --- a/osu.Game.Tests/Visual/Editing/TestSceneCircularPositionSnapGrid.cs +++ /dev/null @@ -1,111 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -#nullable disable - -using System; -using NUnit.Framework; -using osu.Framework.Allocation; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; -using osu.Framework.Input.Events; -using osu.Game.Screens.Edit.Compose.Components; -using osuTK; -using osuTK.Graphics; - -namespace osu.Game.Tests.Visual.Editing -{ - public partial class TestSceneCircularPositionSnapGrid : OsuManualInputManagerTestScene - { - private Container content; - protected override Container Content => content; - - [BackgroundDependencyLoader] - private void load() - { - base.Content.AddRange(new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = Colour4.Gray - }, - content = new Container - { - RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding(10), - } - }); - } - - private static readonly object[][] test_cases = - { - new object[] { new Vector2(0, 0), 10, 0f }, - new object[] { new Vector2(240, 180), 10, 10f }, - new object[] { new Vector2(160, 120), 30, -10f }, - new object[] { new Vector2(480, 360), 100, 0f }, - }; - - [TestCaseSource(nameof(test_cases))] - public void TestCircularGrid(Vector2 position, float spacing, float rotation) - { - CircularPositionSnapGrid grid = null; - - AddStep("create grid", () => - { - Child = grid = new CircularPositionSnapGrid - { - RelativeSizeAxes = Axes.Both, - }; - grid.StartPosition.Value = position; - grid.Spacing.Value = spacing; - }); - - AddStep("add snapping cursor", () => Add(new SnappingCursorContainer - { - RelativeSizeAxes = Axes.Both, - GetSnapPosition = pos => grid.GetSnappedPosition(grid.ToLocalSpace(pos)) - })); - } - - private partial class SnappingCursorContainer : CompositeDrawable - { - public Func GetSnapPosition; - - private readonly Drawable cursor; - - public SnappingCursorContainer() - { - RelativeSizeAxes = Axes.Both; - - InternalChild = cursor = new Circle - { - Origin = Anchor.Centre, - Size = new Vector2(50), - Colour = Color4.Red - }; - } - - protected override void LoadComplete() - { - base.LoadComplete(); - - updatePosition(GetContainingInputManager().CurrentState.Mouse.Position); - } - - protected override bool OnMouseMove(MouseMoveEvent e) - { - base.OnMouseMove(e); - - updatePosition(e.ScreenSpaceMousePosition); - return true; - } - - private void updatePosition(Vector2 screenSpacePosition) - { - cursor.Position = GetSnapPosition.Invoke(screenSpacePosition); - } - } - } -} diff --git a/osu.Game.Tests/Visual/Editing/TestSceneRectangularPositionSnapGrid.cs b/osu.Game.Tests/Visual/Editing/TestScenePositionSnapGrid.cs similarity index 66% rename from osu.Game.Tests/Visual/Editing/TestSceneRectangularPositionSnapGrid.cs rename to osu.Game.Tests/Visual/Editing/TestScenePositionSnapGrid.cs index 19903737f6..7e66edc2dd 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneRectangularPositionSnapGrid.cs +++ b/osu.Game.Tests/Visual/Editing/TestScenePositionSnapGrid.cs @@ -16,7 +16,7 @@ using osuTK.Graphics; namespace osu.Game.Tests.Visual.Editing { - public partial class TestSceneRectangularPositionSnapGrid : OsuManualInputManagerTestScene + public partial class TestScenePositionSnapGrid : OsuManualInputManagerTestScene { private Container content; protected override Container Content => content; @@ -42,8 +42,8 @@ namespace osu.Game.Tests.Visual.Editing private static readonly object[][] test_cases = { new object[] { new Vector2(0, 0), new Vector2(10, 10), 0f }, - new object[] { new Vector2(240, 180), new Vector2(10, 15), 30f }, - new object[] { new Vector2(160, 120), new Vector2(30, 20), -30f }, + new object[] { new Vector2(240, 180), new Vector2(10, 15), 10f }, + new object[] { new Vector2(160, 120), new Vector2(30, 20), -10f }, new object[] { new Vector2(480, 360), new Vector2(100, 100), 0f }, }; @@ -70,6 +70,51 @@ namespace osu.Game.Tests.Visual.Editing })); } + [TestCaseSource(nameof(test_cases))] + public void TestTriangularGrid(Vector2 position, Vector2 spacing, float rotation) + { + TriangularPositionSnapGrid grid = null; + + AddStep("create grid", () => + { + Child = grid = new TriangularPositionSnapGrid + { + RelativeSizeAxes = Axes.Both, + }; + grid.StartPosition.Value = position; + grid.Spacing.Value = spacing.X; + grid.GridLineRotation.Value = rotation; + }); + + AddStep("add snapping cursor", () => Add(new SnappingCursorContainer + { + RelativeSizeAxes = Axes.Both, + GetSnapPosition = pos => grid.GetSnappedPosition(grid.ToLocalSpace(pos)) + })); + } + + [TestCaseSource(nameof(test_cases))] + public void TestCircularGrid(Vector2 position, Vector2 spacing, float rotation) + { + CircularPositionSnapGrid grid = null; + + AddStep("create grid", () => + { + Child = grid = new CircularPositionSnapGrid + { + RelativeSizeAxes = Axes.Both, + }; + grid.StartPosition.Value = position; + grid.Spacing.Value = spacing.X; + }); + + AddStep("add snapping cursor", () => Add(new SnappingCursorContainer + { + RelativeSizeAxes = Axes.Both, + GetSnapPosition = pos => grid.GetSnappedPosition(grid.ToLocalSpace(pos)) + })); + } + private partial class SnappingCursorContainer : CompositeDrawable { public Func GetSnapPosition; diff --git a/osu.Game.Tests/Visual/Editing/TestSceneTriangularPositionSnapGrid.cs b/osu.Game.Tests/Visual/Editing/TestSceneTriangularPositionSnapGrid.cs deleted file mode 100644 index b1f82fa114..0000000000 --- a/osu.Game.Tests/Visual/Editing/TestSceneTriangularPositionSnapGrid.cs +++ /dev/null @@ -1,112 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -#nullable disable - -using System; -using NUnit.Framework; -using osu.Framework.Allocation; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; -using osu.Framework.Input.Events; -using osu.Game.Screens.Edit.Compose.Components; -using osuTK; -using osuTK.Graphics; - -namespace osu.Game.Tests.Visual.Editing -{ - public partial class TestSceneTriangularPositionSnapGrid : OsuManualInputManagerTestScene - { - private Container content; - protected override Container Content => content; - - [BackgroundDependencyLoader] - private void load() - { - base.Content.AddRange(new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = Colour4.Gray - }, - content = new Container - { - RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding(10), - } - }); - } - - private static readonly object[][] test_cases = - { - new object[] { new Vector2(0, 0), 10, 0f }, - new object[] { new Vector2(240, 180), 10, 10f }, - new object[] { new Vector2(160, 120), 30, -10f }, - new object[] { new Vector2(480, 360), 100, 0f }, - }; - - [TestCaseSource(nameof(test_cases))] - public void TestTriangularGrid(Vector2 position, float spacing, float rotation) - { - TriangularPositionSnapGrid grid = null; - - AddStep("create grid", () => - { - Child = grid = new TriangularPositionSnapGrid - { - RelativeSizeAxes = Axes.Both, - }; - grid.StartPosition.Value = position; - grid.Spacing.Value = spacing; - grid.GridLineRotation.Value = rotation; - }); - - AddStep("add snapping cursor", () => Add(new SnappingCursorContainer - { - RelativeSizeAxes = Axes.Both, - GetSnapPosition = pos => grid.GetSnappedPosition(grid.ToLocalSpace(pos)) - })); - } - - private partial class SnappingCursorContainer : CompositeDrawable - { - public Func GetSnapPosition; - - private readonly Drawable cursor; - - public SnappingCursorContainer() - { - RelativeSizeAxes = Axes.Both; - - InternalChild = cursor = new Circle - { - Origin = Anchor.Centre, - Size = new Vector2(50), - Colour = Color4.Red - }; - } - - protected override void LoadComplete() - { - base.LoadComplete(); - - updatePosition(GetContainingInputManager().CurrentState.Mouse.Position); - } - - protected override bool OnMouseMove(MouseMoveEvent e) - { - base.OnMouseMove(e); - - updatePosition(e.ScreenSpaceMousePosition); - return true; - } - - private void updatePosition(Vector2 screenSpacePosition) - { - cursor.Position = GetSnapPosition.Invoke(screenSpacePosition); - } - } - } -} From c5edf4328338ac1ab2ef42f0b8ed004c674aed3a Mon Sep 17 00:00:00 2001 From: OliBomby Date: Sun, 31 Dec 2023 19:53:32 +0100 Subject: [PATCH 046/139] fix grid test --- .../Editor/TestSceneOsuEditorGrids.cs | 74 ++++++++++++------- 1 file changed, 48 insertions(+), 26 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorGrids.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorGrids.cs index ff406b1b88..baeb0639d5 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorGrids.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorGrids.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 System.Linq; using NUnit.Framework; using osu.Framework.Testing; @@ -9,6 +10,7 @@ using osu.Game.Rulesets.Osu.Edit; using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles; using osu.Game.Screens.Edit.Compose.Components; using osu.Game.Tests.Visual; +using osu.Game.Utils; using osuTK; using osuTK.Input; @@ -25,22 +27,22 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor AddStep("select second object", () => EditorBeatmap.SelectedHitObjects.Add(EditorBeatmap.HitObjects.ElementAt(1))); AddUntilStep("distance snap grid visible", () => this.ChildrenOfType().Any()); - rectangularGridActive(false); + gridActive(false); AddStep("enable rectangular grid", () => InputManager.Key(Key.Y)); AddStep("select second object", () => EditorBeatmap.SelectedHitObjects.Add(EditorBeatmap.HitObjects.ElementAt(1))); AddUntilStep("distance snap grid still visible", () => this.ChildrenOfType().Any()); - rectangularGridActive(true); + gridActive(true); AddStep("disable distance snap grid", () => InputManager.Key(Key.T)); AddUntilStep("distance snap grid hidden", () => !this.ChildrenOfType().Any()); AddStep("select second object", () => EditorBeatmap.SelectedHitObjects.Add(EditorBeatmap.HitObjects.ElementAt(1))); - rectangularGridActive(true); + gridActive(true); AddStep("disable rectangular grid", () => InputManager.Key(Key.Y)); AddUntilStep("distance snap grid still hidden", () => !this.ChildrenOfType().Any()); - rectangularGridActive(false); + gridActive(false); } [Test] @@ -58,49 +60,69 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor [Test] public void TestGridSnapMomentaryToggle() { - rectangularGridActive(false); + gridActive(false); AddStep("hold shift", () => InputManager.PressKey(Key.ShiftLeft)); - rectangularGridActive(true); + gridActive(true); AddStep("release shift", () => InputManager.ReleaseKey(Key.ShiftLeft)); - rectangularGridActive(false); + gridActive(false); } - private void rectangularGridActive(bool active) + private void gridActive(bool active) where T : PositionSnapGrid { AddStep("choose placement tool", () => InputManager.Key(Key.Number2)); - AddStep("move cursor to (1, 1)", () => + AddStep("move cursor to spacing + (1, 1)", () => { - var composer = Editor.ChildrenOfType().Single(); - InputManager.MoveMouseTo(composer.ToScreenSpace(new Vector2(1, 1))); + var composer = Editor.ChildrenOfType().Single(); + InputManager.MoveMouseTo(composer.ToScreenSpace(uniqueSnappingPosition(composer) + new Vector2(1, 1))); }); if (active) - AddAssert("placement blueprint at (0, 0)", () => Precision.AlmostEquals(Editor.ChildrenOfType().Single().HitObject.Position, new Vector2(0, 0))); + { + AddAssert("placement blueprint at spacing + (0, 0)", () => + { + var composer = Editor.ChildrenOfType().Single(); + return Precision.AlmostEquals(Editor.ChildrenOfType().Single().HitObject.Position, + uniqueSnappingPosition(composer)); + }); + } else - AddAssert("placement blueprint at (1, 1)", () => Precision.AlmostEquals(Editor.ChildrenOfType().Single().HitObject.Position, new Vector2(1, 1))); + { + AddAssert("placement blueprint at spacing + (1, 1)", () => + { + var composer = Editor.ChildrenOfType().Single(); + return Precision.AlmostEquals(Editor.ChildrenOfType().Single().HitObject.Position, + uniqueSnappingPosition(composer) + new Vector2(1, 1)); + }); + } + } + + private Vector2 uniqueSnappingPosition(PositionSnapGrid grid) + { + return grid switch + { + RectangularPositionSnapGrid rectangular => rectangular.StartPosition.Value + GeometryUtils.RotateVector(rectangular.Spacing.Value, -rectangular.GridLineRotation.Value), + TriangularPositionSnapGrid triangular => triangular.StartPosition.Value + GeometryUtils.RotateVector(new Vector2(triangular.Spacing.Value / 2, triangular.Spacing.Value / 2 * MathF.Sqrt(3)), -triangular.GridLineRotation.Value), + CircularPositionSnapGrid circular => circular.StartPosition.Value + GeometryUtils.RotateVector(new Vector2(circular.Spacing.Value, 0), -45), + _ => Vector2.Zero + }; } [Test] - public void TestGridSizeToggling() + public void TestGridTypeToggling() { AddStep("enable rectangular grid", () => InputManager.Key(Key.Y)); AddUntilStep("rectangular grid visible", () => this.ChildrenOfType().Any()); - gridSizeIs(4); + gridActive(true); - nextGridSizeIs(8); - nextGridSizeIs(16); - nextGridSizeIs(32); - nextGridSizeIs(4); + nextGridTypeIs(); + nextGridTypeIs(); + nextGridTypeIs(); } - private void nextGridSizeIs(int size) + private void nextGridTypeIs() where T : PositionSnapGrid { - AddStep("toggle to next grid size", () => InputManager.Key(Key.G)); - gridSizeIs(size); + AddStep("toggle to next grid type", () => InputManager.Key(Key.G)); + gridActive(true); } - - private void gridSizeIs(int size) - => AddAssert($"grid size is {size}", () => this.ChildrenOfType().Single().Spacing.Value == new Vector2(size) - && EditorBeatmap.BeatmapInfo.GridSize == size); } } From 594b6fe1672ddd4983e06986496519a6661c2352 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Sun, 31 Dec 2023 21:57:11 +0100 Subject: [PATCH 047/139] Add back the old keybind for cycling grid spacing --- .../Editor/TestSceneOsuEditorGrids.cs | 25 ++++++++++++++++++- .../Edit/OsuGridToolboxGroup.cs | 10 ++++---- 2 files changed, 29 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorGrids.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorGrids.cs index baeb0639d5..5636bb51b9 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorGrids.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorGrids.cs @@ -107,6 +107,29 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor }; } + [Test] + public void TestGridSizeToggling() + { + AddStep("enable rectangular grid", () => InputManager.Key(Key.Y)); + AddUntilStep("rectangular grid visible", () => this.ChildrenOfType().Any()); + gridSizeIs(4); + + nextGridSizeIs(8); + nextGridSizeIs(16); + nextGridSizeIs(32); + nextGridSizeIs(4); + } + + private void nextGridSizeIs(int size) + { + AddStep("toggle to next grid size", () => InputManager.Key(Key.G)); + gridSizeIs(size); + } + + private void gridSizeIs(int size) + => AddAssert($"grid size is {size}", () => this.ChildrenOfType().Single().Spacing.Value == new Vector2(size) + && EditorBeatmap.BeatmapInfo.GridSize == size); + [Test] public void TestGridTypeToggling() { @@ -121,7 +144,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor private void nextGridTypeIs() where T : PositionSnapGrid { - AddStep("toggle to next grid type", () => InputManager.Key(Key.G)); + AddStep("toggle to next grid type", () => InputManager.Key(Key.H)); gridActive(true); } } diff --git a/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs b/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs index 981148858d..237ccf3e58 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs @@ -100,6 +100,8 @@ namespace osu.Game.Rulesets.Osu.Edit { } + private const float max_automatic_spacing = 64; + [BackgroundDependencyLoader] private void load() { @@ -196,11 +198,9 @@ namespace osu.Game.Rulesets.Osu.Edit }, true); } - private void nextGridType() + private void nextGridSize() { - currentGridTypeIndex = (currentGridTypeIndex + 1) % grid_types.Length; - GridType.Value = grid_types[currentGridTypeIndex]; - gridTypeButtons.Items[currentGridTypeIndex].Select(); + Spacing.Value = Spacing.Value * 2 >= max_automatic_spacing ? Spacing.Value / 8 : Spacing.Value * 2; } public bool OnPressed(KeyBindingPressEvent e) @@ -208,7 +208,7 @@ namespace osu.Game.Rulesets.Osu.Edit switch (e.Action) { case GlobalAction.EditorCycleGridDisplayMode: - nextGridType(); + nextGridSize(); return true; } From 39f4a1aa8e19a6570c24bef2a71e0c078e7a1049 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Mon, 1 Jan 2024 15:34:05 +0100 Subject: [PATCH 048/139] conflict fixes --- .../Editor/TestSceneOsuEditorGrids.cs | 18 ------------------ .../Edit/OsuGridToolboxGroup.cs | 5 ----- 2 files changed, 23 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorGrids.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorGrids.cs index 5636bb51b9..21427ba281 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorGrids.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorGrids.cs @@ -129,23 +129,5 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor private void gridSizeIs(int size) => AddAssert($"grid size is {size}", () => this.ChildrenOfType().Single().Spacing.Value == new Vector2(size) && EditorBeatmap.BeatmapInfo.GridSize == size); - - [Test] - public void TestGridTypeToggling() - { - AddStep("enable rectangular grid", () => InputManager.Key(Key.Y)); - AddUntilStep("rectangular grid visible", () => this.ChildrenOfType().Any()); - gridActive(true); - - nextGridTypeIs(); - nextGridTypeIs(); - nextGridTypeIs(); - } - - private void nextGridTypeIs() where T : PositionSnapGrid - { - AddStep("toggle to next grid type", () => InputManager.Key(Key.H)); - gridActive(true); - } } } diff --git a/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs b/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs index 237ccf3e58..76e735449a 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs @@ -1,7 +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 System; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -25,10 +24,6 @@ namespace osu.Game.Rulesets.Osu.Edit { public partial class OsuGridToolboxGroup : EditorToolboxGroup, IKeyBindingHandler { - private static readonly PositionSnapGridType[] grid_types = Enum.GetValues(typeof(PositionSnapGridType)).Cast().ToArray(); - - private int currentGridTypeIndex; - [Resolved] private EditorBeatmap editorBeatmap { get; set; } = null!; From de14da95fa6d0230af1aeef7e9b0afd5caaa059e Mon Sep 17 00:00:00 2001 From: OliBomby Date: Mon, 1 Jan 2024 15:44:20 +0100 Subject: [PATCH 049/139] Remove other grid types --- .../Editor/TestSceneOsuEditorGrids.cs | 3 - .../Edit/OsuGridToolboxGroup.cs | 79 -------------- .../Edit/OsuHitObjectComposer.cs | 41 ++----- .../Editing/TestScenePositionSnapGrid.cs | 45 -------- .../Components/CircularPositionSnapGrid.cs | 101 ------------------ .../Components/TriangularPositionSnapGrid.cs | 89 --------------- 6 files changed, 7 insertions(+), 351 deletions(-) delete mode 100644 osu.Game/Screens/Edit/Compose/Components/CircularPositionSnapGrid.cs delete mode 100644 osu.Game/Screens/Edit/Compose/Components/TriangularPositionSnapGrid.cs diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorGrids.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorGrids.cs index 21427ba281..7cafd10454 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorGrids.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorGrids.cs @@ -1,7 +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 System; using System.Linq; using NUnit.Framework; using osu.Framework.Testing; @@ -101,8 +100,6 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor return grid switch { RectangularPositionSnapGrid rectangular => rectangular.StartPosition.Value + GeometryUtils.RotateVector(rectangular.Spacing.Value, -rectangular.GridLineRotation.Value), - TriangularPositionSnapGrid triangular => triangular.StartPosition.Value + GeometryUtils.RotateVector(new Vector2(triangular.Spacing.Value / 2, triangular.Spacing.Value / 2 * MathF.Sqrt(3)), -triangular.GridLineRotation.Value), - CircularPositionSnapGrid circular => circular.StartPosition.Value + GeometryUtils.RotateVector(new Vector2(circular.Spacing.Value, 0), -45), _ => Vector2.Zero }; } diff --git a/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs b/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs index 76e735449a..e82ca780ad 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs @@ -1,13 +1,9 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; -using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; using osu.Game.Graphics.Containers; @@ -16,9 +12,7 @@ using osu.Game.Input.Bindings; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Osu.UI; using osu.Game.Screens.Edit; -using osu.Game.Screens.Edit.Components.RadioButtons; using osuTK; -using osuTK.Graphics; namespace osu.Game.Rulesets.Osu.Edit { @@ -82,13 +76,10 @@ namespace osu.Game.Rulesets.Osu.Edit /// public Bindable SpacingVector { get; } = new Bindable(); - public Bindable GridType { get; } = new Bindable(); - private ExpandableSlider startPositionXSlider = null!; private ExpandableSlider startPositionYSlider = null!; private ExpandableSlider spacingSlider = null!; private ExpandableSlider gridLinesRotationSlider = null!; - private EditorRadioButtonCollection gridTypeButtons = null!; public OsuGridToolboxGroup() : base("grid") @@ -122,31 +113,6 @@ namespace osu.Game.Rulesets.Osu.Edit Current = GridLinesRotation, KeyboardStep = 1, }, - new FillFlowContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Spacing = new Vector2(0f, 10f), - Children = new Drawable[] - { - gridTypeButtons = new EditorRadioButtonCollection - { - RelativeSizeAxes = Axes.X, - Items = new[] - { - new RadioButton("Square", - () => GridType.Value = PositionSnapGridType.Square, - () => new SpriteIcon { Icon = FontAwesome.Regular.Square }), - new RadioButton("Triangle", - () => GridType.Value = PositionSnapGridType.Triangle, - () => new OutlineTriangle(true, 20)), - new RadioButton("Circle", - () => GridType.Value = PositionSnapGridType.Circle, - () => new SpriteIcon { Icon = FontAwesome.Regular.Circle }), - } - }, - } - }, }; Spacing.Value = editorBeatmap.BeatmapInfo.GridSize; @@ -156,8 +122,6 @@ namespace osu.Game.Rulesets.Osu.Edit { base.LoadComplete(); - gridTypeButtons.Items.First().Select(); - StartPositionX.BindValueChanged(x => { startPositionXSlider.ContractedLabelText = $"X: {x.NewValue:N0}"; @@ -185,12 +149,6 @@ namespace osu.Game.Rulesets.Osu.Edit gridLinesRotationSlider.ContractedLabelText = $"R: {rotation.NewValue:#,0.##}"; gridLinesRotationSlider.ExpandedLabelText = $"Rotation: {rotation.NewValue:#,0.##}"; }, true); - - expandingContainer?.Expanded.BindValueChanged(v => - { - gridTypeButtons.FadeTo(v.NewValue ? 1f : 0f, 500, Easing.OutQuint); - gridTypeButtons.BypassAutoSizeAxes = !v.NewValue ? Axes.Y : Axes.None; - }, true); } private void nextGridSize() @@ -213,42 +171,5 @@ namespace osu.Game.Rulesets.Osu.Edit public void OnReleased(KeyBindingReleaseEvent e) { } - - public partial class OutlineTriangle : BufferedContainer - { - public OutlineTriangle(bool outlineOnly, float size) - : base(cachedFrameBuffer: true) - { - Size = new Vector2(size); - - InternalChildren = new Drawable[] - { - new EquilateralTriangle { RelativeSizeAxes = Axes.Both }, - }; - - if (outlineOnly) - { - AddInternal(new EquilateralTriangle - { - Anchor = Anchor.TopCentre, - Origin = Anchor.Centre, - RelativePositionAxes = Axes.Y, - Y = 0.48f, - Colour = Color4.Black, - Size = new Vector2(size - 7), - Blending = BlendingParameters.None, - }); - } - - Blending = BlendingParameters.Additive; - } - } - } - - public enum PositionSnapGridType - { - Square, - Triangle, - Circle, } } diff --git a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs index 84d5adbc52..51bb74926f 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs @@ -99,7 +99,7 @@ namespace osu.Game.Rulesets.Osu.Edit // we may be entering the screen with a selection already active updateDistanceSnapGrid(); - OsuGridToolboxGroup.GridType.BindValueChanged(updatePositionSnapGrid, true); + updatePositionSnapGrid(); RightToolbox.AddRange(new EditorToolboxGroup[] { @@ -110,45 +110,18 @@ namespace osu.Game.Rulesets.Osu.Edit ); } - private void updatePositionSnapGrid(ValueChangedEvent obj) + private void updatePositionSnapGrid() { if (positionSnapGrid != null) LayerBelowRuleset.Remove(positionSnapGrid, true); - switch (obj.NewValue) - { - case PositionSnapGridType.Square: - var rectangularPositionSnapGrid = new RectangularPositionSnapGrid(); + var rectangularPositionSnapGrid = new RectangularPositionSnapGrid(); - rectangularPositionSnapGrid.Spacing.BindTo(OsuGridToolboxGroup.SpacingVector); - rectangularPositionSnapGrid.GridLineRotation.BindTo(OsuGridToolboxGroup.GridLinesRotation); + rectangularPositionSnapGrid.StartPosition.BindTo(OsuGridToolboxGroup.StartPosition); + rectangularPositionSnapGrid.Spacing.BindTo(OsuGridToolboxGroup.SpacingVector); + rectangularPositionSnapGrid.GridLineRotation.BindTo(OsuGridToolboxGroup.GridLinesRotation); - positionSnapGrid = rectangularPositionSnapGrid; - break; - - case PositionSnapGridType.Triangle: - var triangularPositionSnapGrid = new TriangularPositionSnapGrid(); - - triangularPositionSnapGrid.Spacing.BindTo(OsuGridToolboxGroup.Spacing); - triangularPositionSnapGrid.GridLineRotation.BindTo(OsuGridToolboxGroup.GridLinesRotation); - - positionSnapGrid = triangularPositionSnapGrid; - break; - - case PositionSnapGridType.Circle: - var circularPositionSnapGrid = new CircularPositionSnapGrid(); - - circularPositionSnapGrid.Spacing.BindTo(OsuGridToolboxGroup.Spacing); - - positionSnapGrid = circularPositionSnapGrid; - break; - - default: - throw new NotImplementedException($"{OsuGridToolboxGroup.GridType} has an incorrect value."); - } - - // Bind the start position to the toolbox sliders. - positionSnapGrid.StartPosition.BindTo(OsuGridToolboxGroup.StartPosition); + positionSnapGrid = rectangularPositionSnapGrid; positionSnapGrid.RelativeSizeAxes = Axes.Both; LayerBelowRuleset.Add(positionSnapGrid); diff --git a/osu.Game.Tests/Visual/Editing/TestScenePositionSnapGrid.cs b/osu.Game.Tests/Visual/Editing/TestScenePositionSnapGrid.cs index 7e66edc2dd..2721bc3602 100644 --- a/osu.Game.Tests/Visual/Editing/TestScenePositionSnapGrid.cs +++ b/osu.Game.Tests/Visual/Editing/TestScenePositionSnapGrid.cs @@ -70,51 +70,6 @@ namespace osu.Game.Tests.Visual.Editing })); } - [TestCaseSource(nameof(test_cases))] - public void TestTriangularGrid(Vector2 position, Vector2 spacing, float rotation) - { - TriangularPositionSnapGrid grid = null; - - AddStep("create grid", () => - { - Child = grid = new TriangularPositionSnapGrid - { - RelativeSizeAxes = Axes.Both, - }; - grid.StartPosition.Value = position; - grid.Spacing.Value = spacing.X; - grid.GridLineRotation.Value = rotation; - }); - - AddStep("add snapping cursor", () => Add(new SnappingCursorContainer - { - RelativeSizeAxes = Axes.Both, - GetSnapPosition = pos => grid.GetSnappedPosition(grid.ToLocalSpace(pos)) - })); - } - - [TestCaseSource(nameof(test_cases))] - public void TestCircularGrid(Vector2 position, Vector2 spacing, float rotation) - { - CircularPositionSnapGrid grid = null; - - AddStep("create grid", () => - { - Child = grid = new CircularPositionSnapGrid - { - RelativeSizeAxes = Axes.Both, - }; - grid.StartPosition.Value = position; - grid.Spacing.Value = spacing.X; - }); - - AddStep("add snapping cursor", () => Add(new SnappingCursorContainer - { - RelativeSizeAxes = Axes.Both, - GetSnapPosition = pos => grid.GetSnappedPosition(grid.ToLocalSpace(pos)) - })); - } - private partial class SnappingCursorContainer : CompositeDrawable { public Func GetSnapPosition; diff --git a/osu.Game/Screens/Edit/Compose/Components/CircularPositionSnapGrid.cs b/osu.Game/Screens/Edit/Compose/Components/CircularPositionSnapGrid.cs deleted file mode 100644 index 403a270359..0000000000 --- a/osu.Game/Screens/Edit/Compose/Components/CircularPositionSnapGrid.cs +++ /dev/null @@ -1,101 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using System; -using System.Collections.Generic; -using System.Linq; -using osu.Framework.Bindables; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; -using osu.Framework.Utils; -using osuTK; - -namespace osu.Game.Screens.Edit.Compose.Components -{ - public partial class CircularPositionSnapGrid : PositionSnapGrid - { - /// - /// The spacing between grid lines of this . - /// - public BindableFloat Spacing { get; } = new BindableFloat(1f) - { - MinValue = 0f, - }; - - public CircularPositionSnapGrid() - { - Spacing.BindValueChanged(_ => GridCache.Invalidate()); - } - - protected override void CreateContent() - { - var drawSize = DrawSize; - - // Calculate the maximum distance from the origin to the edge of the grid. - float maxDist = MathF.Max( - MathF.Max(StartPosition.Value.Length, (StartPosition.Value - drawSize).Length), - MathF.Max((StartPosition.Value - new Vector2(drawSize.X, 0)).Length, (StartPosition.Value - new Vector2(0, drawSize.Y)).Length) - ); - - generateCircles((int)(maxDist / Spacing.Value) + 1); - - GenerateOutline(drawSize); - } - - private void generateCircles(int count) - { - // Make lines the same width independent of display resolution. - float lineWidth = 2 * DrawWidth / ScreenSpaceDrawQuad.Width; - - List generatedCircles = new List(); - - for (int i = 0; i < count; i++) - { - // Add a minimum diameter so the center circle is clearly visible. - float diameter = MathF.Max(lineWidth * 1.5f, i * Spacing.Value * 2); - - var gridCircle = new CircularContainer - { - BorderColour = Colour4.White, - BorderThickness = lineWidth, - Alpha = 0.2f, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.None, - Width = diameter, - Height = diameter, - Position = StartPosition.Value, - Masking = true, - Child = new Box - { - RelativeSizeAxes = Axes.Both, - AlwaysPresent = true, - Alpha = 0f, - } - }; - - generatedCircles.Add(gridCircle); - } - - if (generatedCircles.Count == 0) - return; - - generatedCircles.First().Alpha = 0.8f; - - AddRangeInternal(generatedCircles); - } - - public override Vector2 GetSnappedPosition(Vector2 original) - { - Vector2 relativeToStart = original - StartPosition.Value; - - if (relativeToStart.LengthSquared < Precision.FLOAT_EPSILON) - return StartPosition.Value; - - float length = relativeToStart.Length; - float wantedLength = MathF.Round(length / Spacing.Value) * Spacing.Value; - - return StartPosition.Value + Vector2.Multiply(relativeToStart, wantedLength / length); - } - } -} diff --git a/osu.Game/Screens/Edit/Compose/Components/TriangularPositionSnapGrid.cs b/osu.Game/Screens/Edit/Compose/Components/TriangularPositionSnapGrid.cs deleted file mode 100644 index 93d2c6a74a..0000000000 --- a/osu.Game/Screens/Edit/Compose/Components/TriangularPositionSnapGrid.cs +++ /dev/null @@ -1,89 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using System; -using osu.Framework.Bindables; -using osu.Game.Utils; -using osuTK; - -namespace osu.Game.Screens.Edit.Compose.Components -{ - public partial class TriangularPositionSnapGrid : LinedPositionSnapGrid - { - /// - /// The spacing between grid lines of this . - /// - public BindableFloat Spacing { get; } = new BindableFloat(1f) - { - MinValue = 0f, - }; - - /// - /// The rotation in degrees of the grid lines of this . - /// - public BindableFloat GridLineRotation { get; } = new BindableFloat(); - - public TriangularPositionSnapGrid() - { - Spacing.BindValueChanged(_ => GridCache.Invalidate()); - GridLineRotation.BindValueChanged(_ => GridCache.Invalidate()); - } - - private const float sqrt3 = 1.73205080757f; - private const float sqrt3_over2 = 0.86602540378f; - private const float one_over_sqrt3 = 0.57735026919f; - - protected override void CreateContent() - { - var drawSize = DrawSize; - float stepSpacing = Spacing.Value * sqrt3_over2; - var step1 = GeometryUtils.RotateVector(new Vector2(stepSpacing, 0), -GridLineRotation.Value - 30); - var step2 = GeometryUtils.RotateVector(new Vector2(stepSpacing, 0), -GridLineRotation.Value - 90); - var step3 = GeometryUtils.RotateVector(new Vector2(stepSpacing, 0), -GridLineRotation.Value - 150); - - GenerateGridLines(step1, drawSize); - GenerateGridLines(-step1, drawSize); - - GenerateGridLines(step2, drawSize); - GenerateGridLines(-step2, drawSize); - - GenerateGridLines(step3, drawSize); - GenerateGridLines(-step3, drawSize); - - GenerateOutline(drawSize); - } - - public override Vector2 GetSnappedPosition(Vector2 original) - { - Vector2 relativeToStart = GeometryUtils.RotateVector(original - StartPosition.Value, GridLineRotation.Value); - Vector2 hex = pixelToHex(relativeToStart); - - return StartPosition.Value + GeometryUtils.RotateVector(hexToPixel(hex), -GridLineRotation.Value); - } - - private Vector2 pixelToHex(Vector2 pixel) - { - float x = pixel.X / Spacing.Value; - float y = pixel.Y / Spacing.Value; - // Algorithm from Charles Chambers - // with modifications and comments by Chris Cox 2023 - // - float t = sqrt3 * y + 1; // scaled y, plus phase - float temp1 = MathF.Floor(t + x); // (y+x) diagonal, this calc needs floor - float temp2 = t - x; // (y-x) diagonal, no floor needed - float temp3 = 2 * x + 1; // scaled horizontal, no floor needed, needs +1 to get correct phase - float qf = (temp1 + temp3) / 3.0f; // pseudo x with fraction - float rf = (temp1 + temp2) / 3.0f; // pseudo y with fraction - float q = MathF.Floor(qf); // pseudo x, quantized and thus requires floor - float r = MathF.Floor(rf); // pseudo y, quantized and thus requires floor - return new Vector2(q, r); - } - - private Vector2 hexToPixel(Vector2 hex) - { - // Taken from - // with modifications for the different definition of size. - return new Vector2(Spacing.Value * (hex.X - hex.Y / 2), Spacing.Value * one_over_sqrt3 * 1.5f * hex.Y); - } - } -} From 460c584dca79ec4dc40df0f49e6721edcb6e6fa9 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Mon, 1 Jan 2024 16:21:33 +0100 Subject: [PATCH 050/139] fix code quality --- osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs b/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs index e82ca780ad..21cce553b1 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs @@ -6,7 +6,6 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; -using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterface; using osu.Game.Input.Bindings; using osu.Game.Rulesets.Edit; @@ -21,9 +20,6 @@ namespace osu.Game.Rulesets.Osu.Edit [Resolved] private EditorBeatmap editorBeatmap { get; set; } = null!; - [Resolved] - private IExpandingContainer? expandingContainer { get; set; } - /// /// X position of the grid's origin. /// From 1428cbfbc34f29eb869d8867a5d4f76453fd1cfd Mon Sep 17 00:00:00 2001 From: OliBomby Date: Thu, 1 Feb 2024 16:56:57 +0100 Subject: [PATCH 051/139] Remove Masking from PositionSnapGrid This caused issues in rendering the outline of the grid because the outline was getting masked at some resolutions. --- .../Components/LinedPositionSnapGrid.cs | 128 +++++++++++++++--- .../Compose/Components/PositionSnapGrid.cs | 2 - 2 files changed, 106 insertions(+), 24 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/LinedPositionSnapGrid.cs b/osu.Game/Screens/Edit/Compose/Components/LinedPositionSnapGrid.cs index ebdd76a4e2..8a7f6b5344 100644 --- a/osu.Game/Screens/Edit/Compose/Components/LinedPositionSnapGrid.cs +++ b/osu.Game/Screens/Edit/Compose/Components/LinedPositionSnapGrid.cs @@ -15,18 +15,29 @@ namespace osu.Game.Screens.Edit.Compose.Components { protected void GenerateGridLines(Vector2 step, Vector2 drawSize) { + if (Precision.AlmostEquals(step, Vector2.Zero)) + return; + int index = 0; - var currentPosition = StartPosition.Value; // Make lines the same width independent of display resolution. float lineWidth = DrawWidth / ScreenSpaceDrawQuad.Width; - float lineLength = drawSize.Length * 2; + float rotation = MathHelper.RadiansToDegrees(MathF.Atan2(step.Y, step.X)); List generatedLines = new List(); - while (lineDefinitelyIntersectsBox(currentPosition, step.PerpendicularLeft, drawSize) || - isMovingTowardsBox(currentPosition, step, drawSize)) + while (true) { + Vector2 currentPosition = StartPosition.Value + index++ * step; + + if (!lineDefinitelyIntersectsBox(currentPosition, step.PerpendicularLeft, drawSize, out var p1, out var p2)) + { + if (!isMovingTowardsBox(currentPosition, step, drawSize)) + break; + + continue; + } + var gridLine = new Box { Colour = Colour4.White, @@ -34,15 +45,12 @@ namespace osu.Game.Screens.Edit.Compose.Components Origin = Anchor.Centre, RelativeSizeAxes = Axes.None, Width = lineWidth, - Height = lineLength, - Position = currentPosition, - Rotation = MathHelper.RadiansToDegrees(MathF.Atan2(step.Y, step.X)), + Height = Vector2.Distance(p1, p2), + Position = (p1 + p2) / 2, + Rotation = rotation, }; generatedLines.Add(gridLine); - - index += 1; - currentPosition = StartPosition.Value + index * step; } if (generatedLines.Count == 0) @@ -59,23 +67,99 @@ namespace osu.Game.Screens.Edit.Compose.Components (currentPosition + step - box).LengthSquared < (currentPosition - box).LengthSquared; } - private bool lineDefinitelyIntersectsBox(Vector2 lineStart, Vector2 lineDir, Vector2 box) + /// + /// Determines if the line starting at and going in the direction of + /// definitely intersects the box on (0, 0) with the given width and height and returns the intersection points if it does. + /// + /// The start point of the line. + /// The direction of the line. + /// The width and height of the box. + /// The first intersection point. + /// The second intersection point. + /// Whether the line definitely intersects the box. + private bool lineDefinitelyIntersectsBox(Vector2 lineStart, Vector2 lineDir, Vector2 box, out Vector2 p1, out Vector2 p2) { - var p2 = lineStart + lineDir; + p1 = Vector2.Zero; + p2 = Vector2.Zero; - double d1 = det(Vector2.Zero); - double d2 = det(new Vector2(box.X, 0)); - double d3 = det(new Vector2(0, box.Y)); - double d4 = det(box); + if (Precision.AlmostEquals(lineDir.X, 0)) + { + // If the line is vertical, we only need to check if the X coordinate of the line is within the box. + if (!Precision.DefinitelyBigger(lineStart.X, 0) || !Precision.DefinitelyBigger(box.X, lineStart.X)) + return false; - return definitelyDifferentSign(d1, d2) || definitelyDifferentSign(d3, d4) || - definitelyDifferentSign(d1, d3) || definitelyDifferentSign(d2, d4); + p1 = new Vector2(lineStart.X, 0); + p2 = new Vector2(lineStart.X, box.Y); + return true; + } - double det(Vector2 p) => (p.X - lineStart.X) * (p2.Y - lineStart.Y) - (p.Y - lineStart.Y) * (p2.X - lineStart.X); + if (Precision.AlmostEquals(lineDir.Y, 0)) + { + // If the line is horizontal, we only need to check if the Y coordinate of the line is within the box. + if (!Precision.DefinitelyBigger(lineStart.Y, 0) || !Precision.DefinitelyBigger(box.Y, lineStart.Y)) + return false; - bool definitelyDifferentSign(double a, double b) => !Precision.AlmostEquals(a, 0) && - !Precision.AlmostEquals(b, 0) && - Math.Sign(a) != Math.Sign(b); + p1 = new Vector2(0, lineStart.Y); + p2 = new Vector2(box.X, lineStart.Y); + return true; + } + + float m = lineDir.Y / lineDir.X; + float mInv = lineDir.X / lineDir.Y; // Use this to improve numerical stability if X is close to zero. + float b = lineStart.Y - m * lineStart.X; + + // Calculate intersection points with the sides of the box. + var p = new List(4); + + if (0 <= b && b <= box.Y) + p.Add(new Vector2(0, b)); + if (0 <= (box.Y - b) * mInv && (box.Y - b) * mInv <= box.X) + p.Add(new Vector2((box.Y - b) * mInv, box.Y)); + if (0 <= m * box.X + b && m * box.X + b <= box.Y) + p.Add(new Vector2(box.X, m * box.X + b)); + if (0 <= -b * mInv && -b * mInv <= box.X) + p.Add(new Vector2(-b * mInv, 0)); + + switch (p.Count) + { + case 4: + // If there are 4 intersection points, the line is a diagonal of the box. + if (m > 0) + { + p1 = Vector2.Zero; + p2 = box; + } + else + { + p1 = new Vector2(0, box.Y); + p2 = new Vector2(box.X, 0); + } + + break; + + case 3: + // If there are 3 intersection points, the line goes through a corner of the box. + if (p[0] == p[1]) + { + p1 = p[0]; + p2 = p[2]; + } + else + { + p1 = p[0]; + p2 = p[1]; + } + + break; + + case 2: + p1 = p[0]; + p2 = p[1]; + + break; + } + + return !Precision.AlmostEquals(p1, p2); } } } diff --git a/osu.Game/Screens/Edit/Compose/Components/PositionSnapGrid.cs b/osu.Game/Screens/Edit/Compose/Components/PositionSnapGrid.cs index 36687ef73a..e576ac1e49 100644 --- a/osu.Game/Screens/Edit/Compose/Components/PositionSnapGrid.cs +++ b/osu.Game/Screens/Edit/Compose/Components/PositionSnapGrid.cs @@ -21,8 +21,6 @@ namespace osu.Game.Screens.Edit.Compose.Components protected PositionSnapGrid() { - Masking = true; - StartPosition.BindValueChanged(_ => GridCache.Invalidate()); AddLayout(GridCache); From 5f7028b5741f88e5c68e05cbd878ecc110d32eec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 21 Mar 2024 17:45:56 +0100 Subject: [PATCH 052/139] Add failing tests --- .../Formats/LegacyScoreDecoderTest.cs | 89 +++++++++++++++++++ 1 file changed, 89 insertions(+) diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyScoreDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyScoreDecoderTest.cs index 5dae86d9e9..050259c2fa 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyScoreDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyScoreDecoderTest.cs @@ -31,6 +31,7 @@ using osu.Game.Rulesets.Taiko; using osu.Game.Scoring; using osu.Game.Scoring.Legacy; using osu.Game.Tests.Resources; +using osuTK; namespace osu.Game.Tests.Beatmaps.Formats { @@ -178,6 +179,94 @@ namespace osu.Game.Tests.Beatmaps.Formats Assert.That(decodedAfterEncode.Replay.Frames[1].Time, Is.EqualTo(second_frame_time)); } + [Test] + public void TestNegativeFrameSkipped() + { + var ruleset = new OsuRuleset().RulesetInfo; + var scoreInfo = TestResources.CreateTestScoreInfo(ruleset); + var beatmap = new TestBeatmap(ruleset); + + var score = new Score + { + ScoreInfo = scoreInfo, + Replay = new Replay + { + Frames = new List + { + new OsuReplayFrame(0, new Vector2()), + new OsuReplayFrame(1000, OsuPlayfield.BASE_SIZE), + new OsuReplayFrame(500, OsuPlayfield.BASE_SIZE / 2), + new OsuReplayFrame(2000, OsuPlayfield.BASE_SIZE), + } + } + }; + + var decodedAfterEncode = encodeThenDecode(LegacyScoreEncoder.LATEST_VERSION, score, beatmap); + + Assert.That(decodedAfterEncode.Replay.Frames, Has.Count.EqualTo(3)); + Assert.That(decodedAfterEncode.Replay.Frames[0].Time, Is.EqualTo(0)); + Assert.That(decodedAfterEncode.Replay.Frames[1].Time, Is.EqualTo(1000)); + Assert.That(decodedAfterEncode.Replay.Frames[2].Time, Is.EqualTo(2000)); + } + + [Test] + public void FirstTwoFramesSwappedIfInWrongOrder() + { + var ruleset = new OsuRuleset().RulesetInfo; + var scoreInfo = TestResources.CreateTestScoreInfo(ruleset); + var beatmap = new TestBeatmap(ruleset); + + var score = new Score + { + ScoreInfo = scoreInfo, + Replay = new Replay + { + Frames = new List + { + new OsuReplayFrame(100, new Vector2()), + new OsuReplayFrame(50, OsuPlayfield.BASE_SIZE / 2), + new OsuReplayFrame(1000, OsuPlayfield.BASE_SIZE), + } + } + }; + + var decodedAfterEncode = encodeThenDecode(LegacyScoreEncoder.LATEST_VERSION, score, beatmap); + + Assert.That(decodedAfterEncode.Replay.Frames, Has.Count.EqualTo(3)); + Assert.That(decodedAfterEncode.Replay.Frames[0].Time, Is.EqualTo(0)); + Assert.That(decodedAfterEncode.Replay.Frames[1].Time, Is.EqualTo(100)); + Assert.That(decodedAfterEncode.Replay.Frames[2].Time, Is.EqualTo(1000)); + } + + [Test] + public void FirstTwoFramesPulledTowardThirdIfTheyAreAfterIt() + { + var ruleset = new OsuRuleset().RulesetInfo; + var scoreInfo = TestResources.CreateTestScoreInfo(ruleset); + var beatmap = new TestBeatmap(ruleset); + + var score = new Score + { + ScoreInfo = scoreInfo, + Replay = new Replay + { + Frames = new List + { + new OsuReplayFrame(0, new Vector2()), + new OsuReplayFrame(500, OsuPlayfield.BASE_SIZE / 2), + new OsuReplayFrame(-1500, OsuPlayfield.BASE_SIZE), + } + } + }; + + var decodedAfterEncode = encodeThenDecode(LegacyScoreEncoder.LATEST_VERSION, score, beatmap); + + Assert.That(decodedAfterEncode.Replay.Frames, Has.Count.EqualTo(3)); + Assert.That(decodedAfterEncode.Replay.Frames[0].Time, Is.EqualTo(-1500)); + Assert.That(decodedAfterEncode.Replay.Frames[1].Time, Is.EqualTo(-1500)); + Assert.That(decodedAfterEncode.Replay.Frames[2].Time, Is.EqualTo(-1500)); + } + [Test] public void TestCultureInvariance() { From 990a07af0eb7070b2e92ed37c033bf183721e299 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 21 Mar 2024 21:01:51 +0100 Subject: [PATCH 053/139] Rewrite handling of legacy replay frame quirks to match stable closer --- osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs | 77 +++++++++---------- 1 file changed, 37 insertions(+), 40 deletions(-) diff --git a/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs b/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs index b16cdffe82..af514a4b59 100644 --- a/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs +++ b/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs @@ -21,6 +21,7 @@ using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Replays; using osu.Game.Rulesets.Scoring; +using osuTK; using SharpCompress.Compressors.LZMA; namespace osu.Game.Scoring.Legacy @@ -240,15 +241,7 @@ namespace osu.Game.Scoring.Legacy private void readLegacyReplay(Replay replay, StreamReader reader) { float lastTime = beatmapOffset; - bool negativeFrameEncounted = false; - ReplayFrame currentFrame = null; - - // the negative time amount that must be "paid back" by positive frames before we start including frames again. - // When a negative frame occurs in a replay, all future frames are skipped until the sum total of their times - // is equal to or greater than the time of that negative frame. - // This value will be negative if we are in a time deficit, ie we have a negative frame that must be paid back. - // Otherwise it will be 0. - float timeDeficit = 0; + var legacyFrames = new List(); string[] frames = reader.ReadToEnd().Split(','); @@ -271,40 +264,44 @@ namespace osu.Game.Scoring.Legacy lastTime += diff; - if (i < 2 && mouseX == 256 && mouseY == -500) - // at the start of the replay, stable places two replay frames, at time 0 and SkipBoundary - 1, respectively. - // both frames use a position of (256, -500). - // ignore these frames as they serve no real purpose (and can even mislead ruleset-specific handlers - see mania) - continue; - - // negative frames are only counted towards the deficit after the very beginning of the replay. - // When the two skip frames are present (see directly above), the third frame will have a large - // negative time roughly equal to SkipBoundary. This shouldn't be counted towards the deficit, otherwise - // any replay data before the skip would be, well, skipped. - // - // On testing against stable, it appears that stable ignores the negative time of only the first - // negative frame of the first three replay frames, regardless of if the skip frames are present. - // Hence the condition here. - // But there is a possibility this is incorrect and may need to be revisited later. - if (i > 2 || negativeFrameEncounted) - { - timeDeficit += diff; - timeDeficit = Math.Min(0, timeDeficit); - } - - if (diff < 0) - negativeFrameEncounted = true; - - // still paying back the deficit from a negative frame. Skip this frame. - if (timeDeficit < 0) - continue; - - currentFrame = convertFrame(new LegacyReplayFrame(lastTime, + legacyFrames.Add(new LegacyReplayFrame(lastTime, mouseX, mouseY, - (ReplayButtonState)Parsing.ParseInt(split[3])), currentFrame); + (ReplayButtonState)Parsing.ParseInt(split[3]))); + } - replay.Frames.Add(currentFrame); + // https://github.com/peppy/osu-stable-reference/blob/e53980dd76857ee899f66ce519ba1597e7874f28/osu!/GameModes/Play/ReplayWatcher.cs#L62-L67 + if (legacyFrames.Count >= 2 && legacyFrames[1].Time < legacyFrames[0].Time) + { + legacyFrames[1].Time = legacyFrames[0].Time; + legacyFrames[0].Time = 0; + } + + // https://github.com/peppy/osu-stable-reference/blob/e53980dd76857ee899f66ce519ba1597e7874f28/osu!/GameModes/Play/ReplayWatcher.cs#L69-L71 + if (legacyFrames.Count >= 3 && legacyFrames[0].Time > legacyFrames[2].Time) + legacyFrames[0].Time = legacyFrames[1].Time = legacyFrames[2].Time; + + // at the start of the replay, stable places two replay frames, at time 0 and SkipBoundary - 1, respectively. + // both frames use a position of (256, -500). + // ignore these frames as they serve no real purpose (and can even mislead ruleset-specific handlers - see mania) + if (legacyFrames.Count >= 2 && legacyFrames[1].Position == new Vector2(256, -500)) + legacyFrames.RemoveAt(1); + + if (legacyFrames.Count >= 1 && legacyFrames[0].Position == new Vector2(256, -500)) + legacyFrames.RemoveAt(0); + + ReplayFrame currentFrame = null; + + foreach (var legacyFrame in legacyFrames) + { + // never allow backwards time traversal in relation to the current frame. + // this handles frames with negative delta. + // this doesn't match stable 100% as stable will do something similar to adding an interpolated "intermediate frame" + // at the point wherein time flow changes from backwards to forwards, but it'll do for now. + if (currentFrame != null && legacyFrame.Time < currentFrame.Time) + continue; + + replay.Frames.Add(currentFrame = convertFrame(legacyFrame, currentFrame)); } } From beee76d64ab5b7604787603a2c9c8f559910d604 Mon Sep 17 00:00:00 2001 From: DavidBeh <67109172+DavidBeh@users.noreply.github.com> Date: Mon, 22 Apr 2024 20:25:43 +0200 Subject: [PATCH 054/139] enabled and fixed judgements for the magnetised mod --- osu.Game.Rulesets.Osu/Mods/OsuModMagnetised.cs | 13 ++++++++++--- osu.Game.Rulesets.Osu/Objects/Slider.cs | 9 +++++++++ 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModMagnetised.cs b/osu.Game.Rulesets.Osu/Mods/OsuModMagnetised.cs index b49fb931d1..860f96965a 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModMagnetised.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModMagnetised.cs @@ -13,7 +13,6 @@ using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects.Drawables; -using osu.Game.Rulesets.Osu.UI; using osu.Game.Rulesets.UI; using osuTK; @@ -37,12 +36,16 @@ namespace osu.Game.Rulesets.Osu.Mods MaxValue = 1.0f, }; + // Bindable Setting for Show Judgements + [SettingSource("Show Judgements", "Whether to show judgements or not.")] + public BindableBool ShowJudgements { get; } = new BindableBool(true); + public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) { // Hide judgment displays and follow points as they won't make any sense. // Judgements can potentially be turned on in a future where they display at a position relative to their drawable counterpart. - drawableRuleset.Playfield.DisplayJudgements.Value = false; - (drawableRuleset.Playfield as OsuPlayfield)?.FollowPoints.Hide(); + drawableRuleset.Playfield.DisplayJudgements.Value = ShowJudgements.Value; + //(drawableRuleset.Playfield as OsuPlayfield)?.FollowPoints.Hide(); } public void Update(Playfield playfield) @@ -78,6 +81,10 @@ namespace osu.Game.Rulesets.Osu.Mods float x = (float)Interpolation.DampContinuously(hitObject.X, destination.X, dampLength, clock.ElapsedFrameTime); float y = (float)Interpolation.DampContinuously(hitObject.Y, destination.Y, dampLength, clock.ElapsedFrameTime); + // I added these two lines + if (hitObject is DrawableOsuHitObject h) + h.HitObject.Position = new Vector2(x, y); + hitObject.Position = new Vector2(x, y); } } diff --git a/osu.Game.Rulesets.Osu/Objects/Slider.cs b/osu.Game.Rulesets.Osu/Objects/Slider.cs index cc3ffd376e..2660933a70 100644 --- a/osu.Game.Rulesets.Osu/Objects/Slider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Slider.cs @@ -8,6 +8,7 @@ using osu.Game.Rulesets.Objects.Types; using System.Collections.Generic; using osu.Game.Rulesets.Objects; using System.Linq; +using System.Runtime.CompilerServices; using System.Threading; using Newtonsoft.Json; using osu.Framework.Bindables; @@ -24,6 +25,8 @@ namespace osu.Game.Rulesets.Osu.Objects { public class Slider : OsuHitObject, IHasPathWithRepeats, IHasSliderVelocity, IHasGenerateTicks { + private static readonly ConditionalWeakTable> SliderProgress = new ConditionalWeakTable>(); + public double EndTime => StartTime + this.SpanCount() * Path.Distance / Velocity; [JsonIgnore] @@ -201,6 +204,7 @@ namespace osu.Game.Rulesets.Osu.Objects Position = Position + Path.PositionAt(e.PathProgress), StackHeight = StackHeight, }); + SliderProgress.AddOrUpdate(NestedHitObjects.Last(), new StrongBox(e.PathProgress)); break; case SliderEventType.Head: @@ -232,6 +236,7 @@ namespace osu.Game.Rulesets.Osu.Objects Position = Position + Path.PositionAt(e.PathProgress), StackHeight = StackHeight, }); + SliderProgress.Add(NestedHitObjects.Last(), new StrongBox(e.PathProgress)); break; } } @@ -248,6 +253,10 @@ namespace osu.Game.Rulesets.Osu.Objects if (TailCircle != null) TailCircle.Position = EndPosition; + + foreach (var hitObject in NestedHitObjects) + if (hitObject is SliderTick or SliderRepeat) + ((OsuHitObject)hitObject).Position = Position + Path.PositionAt(SliderProgress.TryGetValue(hitObject, out var progress) ? progress?.Value ?? 0 : 0); } protected void UpdateNestedSamples() From 331f1f31b08c508d4d51008a402df2b9d6b46238 Mon Sep 17 00:00:00 2001 From: DavidBeh <67109172+DavidBeh@users.noreply.github.com> Date: Tue, 23 Apr 2024 19:19:11 +0200 Subject: [PATCH 055/139] Attempt to position DrawableOsuJudgement based on its DrawableOsuHitObject instead of DrawableOsuHitObject.HitObject --- osu.Game.Rulesets.Osu/Mods/OsuModMagnetised.cs | 4 ++-- .../Objects/Drawables/DrawableOsuJudgement.cs | 9 ++++++--- .../Objects/Drawables/DrawableSliderTick.cs | 2 +- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModMagnetised.cs b/osu.Game.Rulesets.Osu/Mods/OsuModMagnetised.cs index 860f96965a..f0ad284019 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModMagnetised.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModMagnetised.cs @@ -82,9 +82,9 @@ namespace osu.Game.Rulesets.Osu.Mods float y = (float)Interpolation.DampContinuously(hitObject.Y, destination.Y, dampLength, clock.ElapsedFrameTime); // I added these two lines - if (hitObject is DrawableOsuHitObject h) + /*if (hitObject is DrawableOsuHitObject h) h.HitObject.Position = new Vector2(x, y); - +*/ hitObject.Position = new Vector2(x, y); } } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs index 76ae7340ff..0960748320 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs @@ -39,10 +39,13 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables Lighting.ResetAnimation(); Lighting.SetColourFrom(JudgedObject, Result); - if (JudgedObject?.HitObject is OsuHitObject osuObject) + if (JudgedObject is DrawableOsuHitObject osuObject) { - Position = osuObject.StackedEndPosition; - Scale = new Vector2(osuObject.Scale); + Position = osuObject.ToSpaceOfOtherDrawable(Vector2.Zero, Parent); + // Works only for normal hit circles, also with magnetised: + // Position = osuObject.Position; + + Scale = new Vector2(osuObject.HitObject.Scale); } } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTick.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTick.cs index 73c061afbd..0c7ba180f2 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTick.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTick.cs @@ -20,7 +20,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables public const float DEFAULT_TICK_SIZE = 16; - protected DrawableSlider DrawableSlider => (DrawableSlider)ParentHitObject; + public DrawableSlider DrawableSlider => (DrawableSlider)ParentHitObject; private SkinnableDrawable scaleContainer; From 3a914b9337ce15cff315162431fef5769903ba8e Mon Sep 17 00:00:00 2001 From: DavidBeh <67109172+DavidBeh@users.noreply.github.com> Date: Tue, 23 Apr 2024 23:24:51 +0200 Subject: [PATCH 056/139] Fixed judgements with MG mod without causing side effects --- osu.Game.Rulesets.Osu/Mods/OsuModMagnetised.cs | 4 ---- .../Objects/Drawables/DrawableOsuJudgement.cs | 5 +---- osu.Game.Rulesets.Osu/Objects/Slider.cs | 8 +------- 3 files changed, 2 insertions(+), 15 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModMagnetised.cs b/osu.Game.Rulesets.Osu/Mods/OsuModMagnetised.cs index f0ad284019..c64b5a18bc 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModMagnetised.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModMagnetised.cs @@ -81,10 +81,6 @@ namespace osu.Game.Rulesets.Osu.Mods float x = (float)Interpolation.DampContinuously(hitObject.X, destination.X, dampLength, clock.ElapsedFrameTime); float y = (float)Interpolation.DampContinuously(hitObject.Y, destination.Y, dampLength, clock.ElapsedFrameTime); - // I added these two lines - /*if (hitObject is DrawableOsuHitObject h) - h.HitObject.Position = new Vector2(x, y); -*/ hitObject.Position = new Vector2(x, y); } } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs index 0960748320..d0270c68f6 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs @@ -41,10 +41,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables if (JudgedObject is DrawableOsuHitObject osuObject) { - Position = osuObject.ToSpaceOfOtherDrawable(Vector2.Zero, Parent); - // Works only for normal hit circles, also with magnetised: - // Position = osuObject.Position; - + Position = osuObject.ToSpaceOfOtherDrawable(osuObject.OriginPosition, Parent!); Scale = new Vector2(osuObject.HitObject.Scale); } } diff --git a/osu.Game.Rulesets.Osu/Objects/Slider.cs b/osu.Game.Rulesets.Osu/Objects/Slider.cs index 2660933a70..5b52996e22 100644 --- a/osu.Game.Rulesets.Osu/Objects/Slider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Slider.cs @@ -8,7 +8,6 @@ using osu.Game.Rulesets.Objects.Types; using System.Collections.Generic; using osu.Game.Rulesets.Objects; using System.Linq; -using System.Runtime.CompilerServices; using System.Threading; using Newtonsoft.Json; using osu.Framework.Bindables; @@ -25,7 +24,6 @@ namespace osu.Game.Rulesets.Osu.Objects { public class Slider : OsuHitObject, IHasPathWithRepeats, IHasSliderVelocity, IHasGenerateTicks { - private static readonly ConditionalWeakTable> SliderProgress = new ConditionalWeakTable>(); public double EndTime => StartTime + this.SpanCount() * Path.Distance / Velocity; @@ -204,7 +202,6 @@ namespace osu.Game.Rulesets.Osu.Objects Position = Position + Path.PositionAt(e.PathProgress), StackHeight = StackHeight, }); - SliderProgress.AddOrUpdate(NestedHitObjects.Last(), new StrongBox(e.PathProgress)); break; case SliderEventType.Head: @@ -236,7 +233,6 @@ namespace osu.Game.Rulesets.Osu.Objects Position = Position + Path.PositionAt(e.PathProgress), StackHeight = StackHeight, }); - SliderProgress.Add(NestedHitObjects.Last(), new StrongBox(e.PathProgress)); break; } } @@ -254,9 +250,7 @@ namespace osu.Game.Rulesets.Osu.Objects if (TailCircle != null) TailCircle.Position = EndPosition; - foreach (var hitObject in NestedHitObjects) - if (hitObject is SliderTick or SliderRepeat) - ((OsuHitObject)hitObject).Position = Position + Path.PositionAt(SliderProgress.TryGetValue(hitObject, out var progress) ? progress?.Value ?? 0 : 0); + // Positions of other nested hitobjects are not updated } protected void UpdateNestedSamples() From f863ea30e10fce9dce2d388a0e8b0f7f7532c9f1 Mon Sep 17 00:00:00 2001 From: DavidBeh <67109172+DavidBeh@users.noreply.github.com> Date: Tue, 23 Apr 2024 23:31:23 +0200 Subject: [PATCH 057/139] Made judgements always on and disabled follow paths again --- osu.Game.Rulesets.Osu/Mods/OsuModMagnetised.cs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModMagnetised.cs b/osu.Game.Rulesets.Osu/Mods/OsuModMagnetised.cs index c64b5a18bc..97ec669703 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModMagnetised.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModMagnetised.cs @@ -13,6 +13,7 @@ using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects.Drawables; +using osu.Game.Rulesets.Osu.UI; using osu.Game.Rulesets.UI; using osuTK; @@ -36,16 +37,11 @@ namespace osu.Game.Rulesets.Osu.Mods MaxValue = 1.0f, }; - // Bindable Setting for Show Judgements - [SettingSource("Show Judgements", "Whether to show judgements or not.")] - public BindableBool ShowJudgements { get; } = new BindableBool(true); - public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) { // Hide judgment displays and follow points as they won't make any sense. // Judgements can potentially be turned on in a future where they display at a position relative to their drawable counterpart. - drawableRuleset.Playfield.DisplayJudgements.Value = ShowJudgements.Value; - //(drawableRuleset.Playfield as OsuPlayfield)?.FollowPoints.Hide(); + (drawableRuleset.Playfield as OsuPlayfield)?.FollowPoints.Hide(); } public void Update(Playfield playfield) From 7dac5afd90bf3fa7194e46c20c9b10e3fe7450e9 Mon Sep 17 00:00:00 2001 From: DavidBeh <67109172+DavidBeh@users.noreply.github.com> Date: Tue, 23 Apr 2024 23:57:27 +0200 Subject: [PATCH 058/139] Enabled judgements for repel (but not in depth). Updated comments in repel, mag, depth --- osu.Game.Rulesets.Osu/Mods/OsuModDepth.cs | 6 ++++-- osu.Game.Rulesets.Osu/Mods/OsuModMagnetised.cs | 2 +- osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs | 3 +-- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModDepth.cs b/osu.Game.Rulesets.Osu/Mods/OsuModDepth.cs index a9111eec1f..6d4a621f4d 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModDepth.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModDepth.cs @@ -47,10 +47,12 @@ namespace osu.Game.Rulesets.Osu.Mods public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) { - // Hide judgment displays and follow points as they won't make any sense. + // Hide follow points as they won't make any sense. // Judgements can potentially be turned on in a future where they display at a position relative to their drawable counterpart. - drawableRuleset.Playfield.DisplayJudgements.Value = false; (drawableRuleset.Playfield as OsuPlayfield)?.FollowPoints.Hide(); + // Hide judgements as they don't move with the drawables after appearing, which does look bad. + // They would need to either move with them or disappear sooner. + drawableRuleset.Playfield.DisplayJudgements.Value = false; } private void applyTransform(DrawableHitObject drawable, ArmedState state) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModMagnetised.cs b/osu.Game.Rulesets.Osu/Mods/OsuModMagnetised.cs index 97ec669703..b2553e295c 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModMagnetised.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModMagnetised.cs @@ -39,7 +39,7 @@ namespace osu.Game.Rulesets.Osu.Mods public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) { - // Hide judgment displays and follow points as they won't make any sense. + // Hide follow points as they won't make any sense. // Judgements can potentially be turned on in a future where they display at a position relative to their drawable counterpart. (drawableRuleset.Playfield as OsuPlayfield)?.FollowPoints.Hide(); } diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs index ced98f0cd5..302e17432e 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs @@ -38,9 +38,8 @@ namespace osu.Game.Rulesets.Osu.Mods public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) { - // Hide judgment displays and follow points as they won't make any sense. + // Hide follow points as they won't make any sense. // Judgements can potentially be turned on in a future where they display at a position relative to their drawable counterpart. - drawableRuleset.Playfield.DisplayJudgements.Value = false; (drawableRuleset.Playfield as OsuPlayfield)?.FollowPoints.Hide(); } From 16190a4ed7efe012de82aa9cf2b25925556e4a3c Mon Sep 17 00:00:00 2001 From: DavidBeh <67109172+DavidBeh@users.noreply.github.com> Date: Wed, 24 Apr 2024 00:23:45 +0200 Subject: [PATCH 059/139] Made judgements follow DrawableOsuHitObjects. Enabled judgements for depth mod --- osu.Game.Rulesets.Osu/Mods/OsuModDepth.cs | 3 --- .../Objects/Drawables/DrawableOsuJudgement.cs | 11 +++++++++++ 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModDepth.cs b/osu.Game.Rulesets.Osu/Mods/OsuModDepth.cs index 6d4a621f4d..306dcee839 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModDepth.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModDepth.cs @@ -50,9 +50,6 @@ namespace osu.Game.Rulesets.Osu.Mods // Hide follow points as they won't make any sense. // Judgements can potentially be turned on in a future where they display at a position relative to their drawable counterpart. (drawableRuleset.Playfield as OsuPlayfield)?.FollowPoints.Hide(); - // Hide judgements as they don't move with the drawables after appearing, which does look bad. - // They would need to either move with them or disappear sooner. - drawableRuleset.Playfield.DisplayJudgements.Value = false; } private void applyTransform(DrawableHitObject drawable, ArmedState state) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs index d0270c68f6..64bf25cceb 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs @@ -46,6 +46,17 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables } } + + protected override void Update() + { + base.Update(); + if (JudgedObject is DrawableOsuHitObject osuObject && Parent != null && osuObject.HitObject != null) + { + Position = osuObject.ToSpaceOfOtherDrawable(osuObject.OriginPosition, Parent!); + Scale = new Vector2(osuObject.HitObject.Scale); + } + } + protected override void ApplyHitAnimations() { bool hitLightingEnabled = config.Get(OsuSetting.HitLighting); From 8c2a4eb78ab0b900f552f91641ba7b526d33443f Mon Sep 17 00:00:00 2001 From: DavidBeh <67109172+DavidBeh@users.noreply.github.com> Date: Wed, 24 Apr 2024 12:40:23 +0200 Subject: [PATCH 060/139] Fix formatting --- osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs | 2 +- osu.Game.Rulesets.Osu/Objects/Slider.cs | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs index 64bf25cceb..ffbf45291f 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs @@ -46,10 +46,10 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables } } - protected override void Update() { base.Update(); + if (JudgedObject is DrawableOsuHitObject osuObject && Parent != null && osuObject.HitObject != null) { Position = osuObject.ToSpaceOfOtherDrawable(osuObject.OriginPosition, Parent!); diff --git a/osu.Game.Rulesets.Osu/Objects/Slider.cs b/osu.Game.Rulesets.Osu/Objects/Slider.cs index 5b52996e22..248f40208a 100644 --- a/osu.Game.Rulesets.Osu/Objects/Slider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Slider.cs @@ -24,7 +24,6 @@ namespace osu.Game.Rulesets.Osu.Objects { public class Slider : OsuHitObject, IHasPathWithRepeats, IHasSliderVelocity, IHasGenerateTicks { - public double EndTime => StartTime + this.SpanCount() * Path.Distance / Velocity; [JsonIgnore] From e3afd89dc879d6b21c41abc2df749729d314fedc Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 16 May 2024 04:49:33 +0300 Subject: [PATCH 061/139] Allow specifying height of `ShearedButton`s Also includes a test case in `TestSceneShearedButton`s to ensure the buttons' shear factors don't change on different heights (I've encountered issues with that previously). --- .../UserInterface/TestSceneShearedButtons.cs | 51 +++++++++++++++++-- .../Graphics/UserInterface/ShearedButton.cs | 9 ++-- .../Mods/ModFooterInformationDisplay.cs | 2 +- 3 files changed, 53 insertions(+), 9 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneShearedButtons.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneShearedButtons.cs index 118d32ee70..8db22f2d65 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneShearedButtons.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneShearedButtons.cs @@ -7,11 +7,13 @@ using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; using osu.Framework.Testing; using osu.Framework.Utils; using osu.Game.Graphics.UserInterface; using osu.Game.Overlays; +using osuTK; using osuTK.Input; namespace osu.Game.Tests.Visual.UserInterface @@ -35,7 +37,7 @@ namespace osu.Game.Tests.Visual.UserInterface if (bigButton) { - Child = button = new ShearedButton(400) + Child = button = new ShearedButton(400, 80) { LighterColour = Colour4.FromHex("#FFFFFF"), DarkerColour = Colour4.FromHex("#FFCC22"), @@ -44,13 +46,12 @@ namespace osu.Game.Tests.Visual.UserInterface Anchor = Anchor.Centre, Origin = Anchor.Centre, Text = "Let's GO!", - Height = 80, Action = () => actionFired = true, }; } else { - Child = button = new ShearedButton(200) + Child = button = new ShearedButton(200, 80) { LighterColour = Colour4.FromHex("#FF86DD"), DarkerColour = Colour4.FromHex("#DE31AE"), @@ -58,7 +59,6 @@ namespace osu.Game.Tests.Visual.UserInterface Anchor = Anchor.Centre, Origin = Anchor.Centre, Text = "Press me", - Height = 80, Action = () => actionFired = true, }; } @@ -171,5 +171,48 @@ namespace osu.Game.Tests.Visual.UserInterface void setToggleDisabledState(bool disabled) => AddStep($"{(disabled ? "disable" : "enable")} toggle", () => button.Active.Disabled = disabled); } + + [Test] + public void TestButtons() + { + AddStep("create buttons", () => Children = new[] + { + new FillFlowContainer + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Direction = FillDirection.Horizontal, + AutoSizeAxes = Axes.Both, + Scale = new Vector2(2.5f), + Children = new Drawable[] + { + new ShearedButton(120) + { + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + Text = "Test", + Action = () => { }, + Padding = new MarginPadding(), + }, + new ShearedButton(120, 40) + { + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + Text = "Test", + Action = () => { }, + Padding = new MarginPadding { Left = -1f }, + }, + new ShearedButton(120, 70) + { + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + Text = "Test", + Action = () => { }, + Padding = new MarginPadding { Left = 3f }, + }, + } + } + }); + } } } diff --git a/osu.Game/Graphics/UserInterface/ShearedButton.cs b/osu.Game/Graphics/UserInterface/ShearedButton.cs index b1e7066a01..caf1f76d88 100644 --- a/osu.Game/Graphics/UserInterface/ShearedButton.cs +++ b/osu.Game/Graphics/UserInterface/ShearedButton.cs @@ -17,7 +17,7 @@ namespace osu.Game.Graphics.UserInterface { public partial class ShearedButton : OsuClickableContainer { - public const float HEIGHT = 50; + public const float DEFAULT_HEIGHT = 50; public const float CORNER_RADIUS = 7; public const float BORDER_THICKNESS = 2; @@ -85,10 +85,11 @@ namespace osu.Game.Graphics.UserInterface /// If a value is provided (or the argument is omitted entirely), the button will autosize in width to fit the text. /// /// - public ShearedButton(float? width = null) + /// The height of the button. + public ShearedButton(float? width = null, float height = DEFAULT_HEIGHT) { - Height = HEIGHT; - Padding = new MarginPadding { Horizontal = shear * 50 }; + Height = height; + Padding = new MarginPadding { Horizontal = shear * height }; Content.CornerRadius = CORNER_RADIUS; Content.Shear = new Vector2(shear, 0); diff --git a/osu.Game/Overlays/Mods/ModFooterInformationDisplay.cs b/osu.Game/Overlays/Mods/ModFooterInformationDisplay.cs index 7fccf0cc13..8668879850 100644 --- a/osu.Game/Overlays/Mods/ModFooterInformationDisplay.cs +++ b/osu.Game/Overlays/Mods/ModFooterInformationDisplay.cs @@ -36,7 +36,7 @@ namespace osu.Game.Overlays.Mods Origin = Anchor.BottomRight, Anchor = Anchor.BottomRight, AutoSizeAxes = Axes.X, - Height = ShearedButton.HEIGHT, + Height = ShearedButton.DEFAULT_HEIGHT, Shear = new Vector2(ShearedOverlayContainer.SHEAR, 0), CornerRadius = ShearedButton.CORNER_RADIUS, BorderThickness = ShearedButton.BORDER_THICKNESS, From 266f0803624415acbf6bde6eb3a9b63f7ea9b83c Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 16 May 2024 05:01:49 +0300 Subject: [PATCH 062/139] Allow customising content of `ShearedButton`s --- .../Graphics/UserInterface/ShearedButton.cs | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/ShearedButton.cs b/osu.Game/Graphics/UserInterface/ShearedButton.cs index caf1f76d88..0fd21502a1 100644 --- a/osu.Game/Graphics/UserInterface/ShearedButton.cs +++ b/osu.Game/Graphics/UserInterface/ShearedButton.cs @@ -75,6 +75,8 @@ namespace osu.Game.Graphics.UserInterface private readonly Container backgroundLayer; private readonly Box flashLayer; + protected readonly Container ButtonContent; + /// /// Creates a new /// @@ -110,12 +112,16 @@ namespace osu.Game.Graphics.UserInterface { RelativeSizeAxes = Axes.Both }, - text = new OsuSpriteText + ButtonContent = new Container { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Font = OsuFont.TorusAlternate.With(size: 17), - Shear = new Vector2(-shear, 0) + AutoSizeAxes = Axes.Both, + Shear = new Vector2(-shear, 0), + Child = text = new OsuSpriteText + { + Font = OsuFont.TorusAlternate.With(size: 17), + } }, } }, @@ -189,7 +195,7 @@ namespace osu.Game.Graphics.UserInterface { var colourDark = darkerColour ?? ColourProvider.Background3; var colourLight = lighterColour ?? ColourProvider.Background1; - var colourText = textColour ?? ColourProvider.Content1; + var colourContent = textColour ?? ColourProvider.Content1; if (!Enabled.Value) { @@ -206,9 +212,9 @@ namespace osu.Game.Graphics.UserInterface backgroundLayer.TransformTo(nameof(BorderColour), ColourInfo.GradientVertical(colourDark, colourLight), 150, Easing.OutQuint); if (!Enabled.Value) - colourText = colourText.Opacity(0.6f); + colourContent = colourContent.Opacity(0.6f); - text.FadeColour(colourText, 150, Easing.OutQuint); + ButtonContent.FadeColour(colourContent, 150, Easing.OutQuint); } } } From 7e8d5a5b0a1d0c58aab8de8afd6dc56094fd4f00 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 16 May 2024 04:50:51 +0300 Subject: [PATCH 063/139] Add new footer back button --- osu.Game/Screens/Footer/ScreenBackButton.cs | 64 +++++++++++++++++++++ osu.Game/Screens/Footer/ScreenFooter.cs | 17 ++++-- 2 files changed, 75 insertions(+), 6 deletions(-) create mode 100644 osu.Game/Screens/Footer/ScreenBackButton.cs diff --git a/osu.Game/Screens/Footer/ScreenBackButton.cs b/osu.Game/Screens/Footer/ScreenBackButton.cs new file mode 100644 index 0000000000..c5e613ea51 --- /dev/null +++ b/osu.Game/Screens/Footer/ScreenBackButton.cs @@ -0,0 +1,64 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; +using osu.Game.Localisation; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Screens.Footer +{ + public partial class ScreenBackButton : ShearedButton + { + // todo: see https://github.com/ppy/osu-framework/issues/3271 + private const float torus_scale_factor = 1.2f; + + public const float BUTTON_WIDTH = 240; + + public ScreenBackButton() + : base(BUTTON_WIDTH, 70) + { + } + + [BackgroundDependencyLoader] + private void load() + { + ButtonContent.Child = new FillFlowContainer + { + X = -10f, + RelativeSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(20f, 0f), + Children = new Drawable[] + { + new SpriteIcon + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(20f), + Icon = FontAwesome.Solid.ChevronLeft, + }, + new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Font = OsuFont.TorusAlternate.With(size: 20 * torus_scale_factor), + Text = CommonStrings.Back, + UseFullGlyphHeight = false, + } + } + }; + + DarkerColour = Color4Extensions.FromHex("#DE31AE"); + LighterColour = Color4Extensions.FromHex("#FF86DD"); + TextColour = Color4.White; + } + } +} diff --git a/osu.Game/Screens/Footer/ScreenFooter.cs b/osu.Game/Screens/Footer/ScreenFooter.cs index 01013bb466..4a10a4cdab 100644 --- a/osu.Game/Screens/Footer/ScreenFooter.cs +++ b/osu.Game/Screens/Footer/ScreenFooter.cs @@ -7,7 +7,6 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Game.Graphics; -using osu.Game.Graphics.UserInterface; using osu.Game.Overlays; using osuTK; @@ -15,9 +14,8 @@ namespace osu.Game.Screens.Footer { public partial class ScreenFooter : InputBlockingContainer { - //Should be 60, setting to 50 for now for the sake of matching the current BackButton height. - private const int height = 50; - private const int padding = 80; + private const int height = 60; + private const int padding = 60; private readonly List overlays = new List(); @@ -68,13 +66,20 @@ namespace osu.Game.Screens.Footer }, buttons = new FillFlowContainer { - Position = new Vector2(TwoLayerButton.SIZE_EXTENDED.X + padding, 10), + Position = new Vector2(ScreenBackButton.BUTTON_WIDTH + padding, 10), Anchor = Anchor.BottomLeft, Origin = Anchor.BottomLeft, Direction = FillDirection.Horizontal, Spacing = new Vector2(7, 0), AutoSizeAxes = Axes.Both - } + }, + new ScreenBackButton + { + Margin = new MarginPadding { Bottom = 10f, Left = 12f }, + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + Action = () => { }, + }, }; } } From 9446f45acf7da4bea4f042dc68a36e2c7d42db53 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 16 May 2024 05:03:34 +0300 Subject: [PATCH 064/139] Fix shear factor of `ScreenFooterButton`s not matching anything else When shear factors differ between components that are close to each other (`BackButtonV2` and `ScreenFooterButton` in this case), they look completely ugly. --- osu.Game/Screens/Footer/ScreenFooterButton.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Footer/ScreenFooterButton.cs b/osu.Game/Screens/Footer/ScreenFooterButton.cs index b3b3c9a8ec..3a23249ae0 100644 --- a/osu.Game/Screens/Footer/ScreenFooterButton.cs +++ b/osu.Game/Screens/Footer/ScreenFooterButton.cs @@ -25,8 +25,9 @@ namespace osu.Game.Screens.Footer { public partial class ScreenFooterButton : OsuClickableContainer, IKeyBindingHandler { - // This should be 12 by design, but an extra allowance is added due to the corner radius specification. - private const float shear_width = 13.5f; + // if we go by design, both this and ShearedButton should have shear factor as 1/7, + // but ShearedButton uses 1/5 so we must follow suit for the design to stay consistent. + private const float shear = 1f / 5f; protected const int CORNER_RADIUS = 10; protected const int BUTTON_HEIGHT = 90; @@ -34,7 +35,7 @@ namespace osu.Game.Screens.Footer public Bindable OverlayState = new Bindable(); - protected static readonly Vector2 BUTTON_SHEAR = new Vector2(shear_width / BUTTON_HEIGHT, 0); + protected static readonly Vector2 BUTTON_SHEAR = new Vector2(shear, 0); [Resolved] private OverlayColourProvider colourProvider { get; set; } = null!; From 7fce4cc4948803047de66133b07060a9991ff426 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 16 May 2024 05:30:19 +0300 Subject: [PATCH 065/139] Make `ScreenFooterButtonMods` share shear factor with base class --- .../SelectV2/Footer/ScreenFooterButtonMods.cs | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/osu.Game/Screens/SelectV2/Footer/ScreenFooterButtonMods.cs b/osu.Game/Screens/SelectV2/Footer/ScreenFooterButtonMods.cs index 49961c60f8..f590a19164 100644 --- a/osu.Game/Screens/SelectV2/Footer/ScreenFooterButtonMods.cs +++ b/osu.Game/Screens/SelectV2/Footer/ScreenFooterButtonMods.cs @@ -33,12 +33,9 @@ namespace osu.Game.Screens.SelectV2.Footer { // todo: see https://github.com/ppy/osu-framework/issues/3271 private const float torus_scale_factor = 1.2f; - private const float bar_shear_width = 7f; private const float bar_height = 37f; private const float mod_display_portion = 0.65f; - private static readonly Vector2 bar_shear = new Vector2(bar_shear_width / bar_height, 0); - private readonly BindableWithCurrent> current = new BindableWithCurrent>(Array.Empty()); public Bindable> Current @@ -77,7 +74,7 @@ namespace osu.Game.Screens.SelectV2.Footer Y = -5f, Depth = float.MaxValue, Origin = Anchor.BottomLeft, - Shear = bar_shear, + Shear = BUTTON_SHEAR, CornerRadius = CORNER_RADIUS, Size = new Vector2(BUTTON_WIDTH, bar_height), Masking = true, @@ -107,7 +104,7 @@ namespace osu.Game.Screens.SelectV2.Footer { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Shear = -bar_shear, + Shear = -BUTTON_SHEAR, UseFullGlyphHeight = false, Font = OsuFont.Torus.With(size: 14 * torus_scale_factor, weight: FontWeight.Bold) } @@ -129,7 +126,7 @@ namespace osu.Game.Screens.SelectV2.Footer { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Shear = -bar_shear, + Shear = -BUTTON_SHEAR, Scale = new Vector2(0.6f), Current = { BindTarget = Current }, ExpansionMode = ExpansionMode.AlwaysContracted, @@ -138,7 +135,7 @@ namespace osu.Game.Screens.SelectV2.Footer { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Shear = -bar_shear, + Shear = -BUTTON_SHEAR, Font = OsuFont.Torus.With(size: 14 * torus_scale_factor, weight: FontWeight.Bold), Mods = { BindTarget = Current }, } @@ -304,7 +301,7 @@ namespace osu.Game.Screens.SelectV2.Footer Y = -5f; Depth = float.MaxValue; Origin = Anchor.BottomLeft; - Shear = bar_shear; + Shear = BUTTON_SHEAR; CornerRadius = CORNER_RADIUS; AutoSizeAxes = Axes.X; Height = bar_height; @@ -328,7 +325,7 @@ namespace osu.Game.Screens.SelectV2.Footer { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Shear = -bar_shear, + Shear = -BUTTON_SHEAR, Text = ModSelectOverlayStrings.Unranked.ToUpper(), Margin = new MarginPadding { Horizontal = 15 }, UseFullGlyphHeight = false, From 9265290acfea0c3d746ede43426ae479708e340b Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 16 May 2024 05:30:35 +0300 Subject: [PATCH 066/139] Change shear factor everywhere to 0.15x --- osu.Game/Graphics/UserInterface/ShearedButton.cs | 3 ++- osu.Game/Overlays/Mods/ShearedOverlayContainer.cs | 3 ++- osu.Game/Screens/Footer/ScreenFooterButton.cs | 5 ++--- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/ShearedButton.cs b/osu.Game/Graphics/UserInterface/ShearedButton.cs index 0fd21502a1..d546c18cb8 100644 --- a/osu.Game/Graphics/UserInterface/ShearedButton.cs +++ b/osu.Game/Graphics/UserInterface/ShearedButton.cs @@ -11,6 +11,7 @@ using osu.Framework.Localisation; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osu.Game.Overlays; +using osu.Game.Overlays.Mods; using osuTK; namespace osu.Game.Graphics.UserInterface @@ -66,7 +67,7 @@ namespace osu.Game.Graphics.UserInterface private readonly Box background; private readonly OsuSpriteText text; - private const float shear = 0.2f; + private const float shear = ShearedOverlayContainer.SHEAR; private Colour4? darkerColour; private Colour4? lighterColour; diff --git a/osu.Game/Overlays/Mods/ShearedOverlayContainer.cs b/osu.Game/Overlays/Mods/ShearedOverlayContainer.cs index a372ec70db..893ac89aa4 100644 --- a/osu.Game/Overlays/Mods/ShearedOverlayContainer.cs +++ b/osu.Game/Overlays/Mods/ShearedOverlayContainer.cs @@ -22,7 +22,8 @@ namespace osu.Game.Overlays.Mods { protected const float PADDING = 14; - public const float SHEAR = 0.2f; + // todo: maybe move this to a higher place since it's used for screen footer buttons etc. + public const float SHEAR = 0.15f; [Cached] protected readonly OverlayColourProvider ColourProvider; diff --git a/osu.Game/Screens/Footer/ScreenFooterButton.cs b/osu.Game/Screens/Footer/ScreenFooterButton.cs index 3a23249ae0..e0b019bfa8 100644 --- a/osu.Game/Screens/Footer/ScreenFooterButton.cs +++ b/osu.Game/Screens/Footer/ScreenFooterButton.cs @@ -18,6 +18,7 @@ using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osu.Game.Input.Bindings; using osu.Game.Overlays; +using osu.Game.Overlays.Mods; using osuTK; using osuTK.Graphics; @@ -25,9 +26,7 @@ namespace osu.Game.Screens.Footer { public partial class ScreenFooterButton : OsuClickableContainer, IKeyBindingHandler { - // if we go by design, both this and ShearedButton should have shear factor as 1/7, - // but ShearedButton uses 1/5 so we must follow suit for the design to stay consistent. - private const float shear = 1f / 5f; + private const float shear = ShearedOverlayContainer.SHEAR; protected const int CORNER_RADIUS = 10; protected const int BUTTON_HEIGHT = 90; From 310b4d90cc49fc8f26982bc0656285458d713d6c Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 16 May 2024 07:27:54 +0300 Subject: [PATCH 067/139] Move `SHEAR` constant to `OsuGame` and revert back to 0.2x (i.e. master) Discussed in [discord](https://discord.com/channels/188630481301012481/188630652340404224/1240490608934653984). --- osu.Game/Graphics/UserInterface/ShearedButton.cs | 2 +- osu.Game/Graphics/UserInterface/ShearedSearchTextBox.cs | 4 ++-- osu.Game/OsuGame.cs | 5 +++++ osu.Game/Overlays/Mods/BeatmapAttributesDisplay.cs | 2 +- osu.Game/Overlays/Mods/ModColumn.cs | 2 +- osu.Game/Overlays/Mods/ModFooterInformationDisplay.cs | 2 +- osu.Game/Overlays/Mods/ModPanel.cs | 2 +- osu.Game/Overlays/Mods/ModSelectColumn.cs | 6 +++--- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 4 ++-- osu.Game/Overlays/Mods/ModSelectPanel.cs | 8 ++++---- osu.Game/Overlays/Mods/RankingInformationDisplay.cs | 6 +++--- osu.Game/Overlays/Mods/ShearedOverlayContainer.cs | 3 --- osu.Game/Screens/Footer/ScreenFooterButton.cs | 2 +- 13 files changed, 25 insertions(+), 23 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/ShearedButton.cs b/osu.Game/Graphics/UserInterface/ShearedButton.cs index d546c18cb8..7eed388707 100644 --- a/osu.Game/Graphics/UserInterface/ShearedButton.cs +++ b/osu.Game/Graphics/UserInterface/ShearedButton.cs @@ -67,7 +67,7 @@ namespace osu.Game.Graphics.UserInterface private readonly Box background; private readonly OsuSpriteText text; - private const float shear = ShearedOverlayContainer.SHEAR; + private const float shear = OsuGame.SHEAR; private Colour4? darkerColour; private Colour4? lighterColour; diff --git a/osu.Game/Graphics/UserInterface/ShearedSearchTextBox.cs b/osu.Game/Graphics/UserInterface/ShearedSearchTextBox.cs index c3a9f8a586..5f3716f36c 100644 --- a/osu.Game/Graphics/UserInterface/ShearedSearchTextBox.cs +++ b/osu.Game/Graphics/UserInterface/ShearedSearchTextBox.cs @@ -53,7 +53,7 @@ namespace osu.Game.Graphics.UserInterface public ShearedSearchTextBox() { Height = 42; - Shear = new Vector2(ShearedOverlayContainer.SHEAR, 0); + Shear = new Vector2(OsuGame.SHEAR, 0); Masking = true; CornerRadius = corner_radius; @@ -116,7 +116,7 @@ namespace osu.Game.Graphics.UserInterface PlaceholderText = CommonStrings.InputSearch; CornerRadius = corner_radius; - TextContainer.Shear = new Vector2(-ShearedOverlayContainer.SHEAR, 0); + TextContainer.Shear = new Vector2(-OsuGame.SHEAR, 0); } protected override SpriteText CreatePlaceholder() => new SearchPlaceholder(); diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 7c89314014..af01a1b1ac 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -91,6 +91,11 @@ namespace osu.Game /// protected const float SIDE_OVERLAY_OFFSET_RATIO = 0.05f; + /// + /// A common shear factor applied to most components of the game. + /// + public const float SHEAR = 0.2f; + public Toolbar Toolbar { get; private set; } private ChatOverlay chatOverlay; diff --git a/osu.Game/Overlays/Mods/BeatmapAttributesDisplay.cs b/osu.Game/Overlays/Mods/BeatmapAttributesDisplay.cs index c58cf710bd..5b10a2844e 100644 --- a/osu.Game/Overlays/Mods/BeatmapAttributesDisplay.cs +++ b/osu.Game/Overlays/Mods/BeatmapAttributesDisplay.cs @@ -66,7 +66,7 @@ namespace osu.Game.Overlays.Mods [BackgroundDependencyLoader] private void load() { - const float shear = ShearedOverlayContainer.SHEAR; + const float shear = OsuGame.SHEAR; LeftContent.AddRange(new Drawable[] { diff --git a/osu.Game/Overlays/Mods/ModColumn.cs b/osu.Game/Overlays/Mods/ModColumn.cs index e9f21338bd..326394a207 100644 --- a/osu.Game/Overlays/Mods/ModColumn.cs +++ b/osu.Game/Overlays/Mods/ModColumn.cs @@ -106,7 +106,7 @@ namespace osu.Game.Overlays.Mods Origin = Anchor.CentreLeft, Scale = new Vector2(0.8f), RelativeSizeAxes = Axes.X, - Shear = new Vector2(-ShearedOverlayContainer.SHEAR, 0) + Shear = new Vector2(-OsuGame.SHEAR, 0) }); ItemsFlow.Padding = new MarginPadding { diff --git a/osu.Game/Overlays/Mods/ModFooterInformationDisplay.cs b/osu.Game/Overlays/Mods/ModFooterInformationDisplay.cs index 8668879850..6665a3b8dc 100644 --- a/osu.Game/Overlays/Mods/ModFooterInformationDisplay.cs +++ b/osu.Game/Overlays/Mods/ModFooterInformationDisplay.cs @@ -37,7 +37,7 @@ namespace osu.Game.Overlays.Mods Anchor = Anchor.BottomRight, AutoSizeAxes = Axes.X, Height = ShearedButton.DEFAULT_HEIGHT, - Shear = new Vector2(ShearedOverlayContainer.SHEAR, 0), + Shear = new Vector2(OsuGame.SHEAR, 0), CornerRadius = ShearedButton.CORNER_RADIUS, BorderThickness = ShearedButton.BORDER_THICKNESS, Masking = true, diff --git a/osu.Game/Overlays/Mods/ModPanel.cs b/osu.Game/Overlays/Mods/ModPanel.cs index cf173b0d6a..9f87a704c0 100644 --- a/osu.Game/Overlays/Mods/ModPanel.cs +++ b/osu.Game/Overlays/Mods/ModPanel.cs @@ -36,7 +36,7 @@ namespace osu.Game.Overlays.Mods Anchor = Anchor.Centre, Origin = Anchor.Centre, Active = { BindTarget = Active }, - Shear = new Vector2(-ShearedOverlayContainer.SHEAR, 0), + Shear = new Vector2(-OsuGame.SHEAR, 0), Scale = new Vector2(HEIGHT / ModSwitchSmall.DEFAULT_SIZE) }; } diff --git a/osu.Game/Overlays/Mods/ModSelectColumn.cs b/osu.Game/Overlays/Mods/ModSelectColumn.cs index 61b29ef65b..5ffed24e7a 100644 --- a/osu.Game/Overlays/Mods/ModSelectColumn.cs +++ b/osu.Game/Overlays/Mods/ModSelectColumn.cs @@ -70,7 +70,7 @@ namespace osu.Game.Overlays.Mods { Width = WIDTH; RelativeSizeAxes = Axes.Y; - Shear = new Vector2(ShearedOverlayContainer.SHEAR, 0); + Shear = new Vector2(OsuGame.SHEAR, 0); InternalChildren = new Drawable[] { @@ -96,7 +96,7 @@ namespace osu.Game.Overlays.Mods { RelativeSizeAxes = Axes.X, Height = header_height, - Shear = new Vector2(-ShearedOverlayContainer.SHEAR, 0), + Shear = new Vector2(-OsuGame.SHEAR, 0), Velocity = 0.7f, ClampAxes = Axes.Y }, @@ -111,7 +111,7 @@ namespace osu.Game.Overlays.Mods AutoSizeAxes = Axes.Y, Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, - Shear = new Vector2(-ShearedOverlayContainer.SHEAR, 0), + Shear = new Vector2(-OsuGame.SHEAR, 0), Padding = new MarginPadding { Horizontal = 17, diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index 25293e8e20..f521b2e38c 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -227,7 +227,7 @@ namespace osu.Game.Overlays.Mods Anchor = Anchor.BottomLeft, Origin = Anchor.BottomLeft, Direction = FillDirection.Horizontal, - Shear = new Vector2(SHEAR, 0), + Shear = new Vector2(OsuGame.SHEAR, 0), RelativeSizeAxes = Axes.Y, AutoSizeAxes = Axes.X, Margin = new MarginPadding { Horizontal = 70 }, @@ -847,7 +847,7 @@ namespace osu.Game.Overlays.Mods // DrawWidth/DrawPosition do not include shear effects, and we want to know the full extents of the columns post-shear, // so we have to manually compensate. var topLeft = column.ToSpaceOfOtherDrawable(Vector2.Zero, ScrollContent); - var bottomRight = column.ToSpaceOfOtherDrawable(new Vector2(column.DrawWidth - column.DrawHeight * SHEAR, 0), ScrollContent); + var bottomRight = column.ToSpaceOfOtherDrawable(new Vector2(column.DrawWidth - column.DrawHeight * OsuGame.SHEAR, 0), ScrollContent); bool isCurrentlyVisible = Precision.AlmostBigger(topLeft.X, leftVisibleBound) && Precision.DefinitelyBigger(rightVisibleBound, bottomRight.X); diff --git a/osu.Game/Overlays/Mods/ModSelectPanel.cs b/osu.Game/Overlays/Mods/ModSelectPanel.cs index 29f4c93e88..284356f37e 100644 --- a/osu.Game/Overlays/Mods/ModSelectPanel.cs +++ b/osu.Game/Overlays/Mods/ModSelectPanel.cs @@ -87,7 +87,7 @@ namespace osu.Game.Overlays.Mods Content.CornerRadius = CORNER_RADIUS; Content.BorderThickness = 2; - Shear = new Vector2(ShearedOverlayContainer.SHEAR, 0); + Shear = new Vector2(OsuGame.SHEAR, 0); Children = new Drawable[] { @@ -128,10 +128,10 @@ namespace osu.Game.Overlays.Mods { Font = OsuFont.TorusAlternate.With(size: 18, weight: FontWeight.SemiBold), RelativeSizeAxes = Axes.X, - Shear = new Vector2(-ShearedOverlayContainer.SHEAR, 0), + Shear = new Vector2(-OsuGame.SHEAR, 0), Margin = new MarginPadding { - Left = -18 * ShearedOverlayContainer.SHEAR + Left = -18 * OsuGame.SHEAR }, ShowTooltip = false, // Tooltip is handled by `IncompatibilityDisplayingModPanel`. }, @@ -139,7 +139,7 @@ namespace osu.Game.Overlays.Mods { Font = OsuFont.Default.With(size: 12), RelativeSizeAxes = Axes.X, - Shear = new Vector2(-ShearedOverlayContainer.SHEAR, 0), + Shear = new Vector2(-OsuGame.SHEAR, 0), ShowTooltip = false, // Tooltip is handled by `IncompatibilityDisplayingModPanel`. } } diff --git a/osu.Game/Overlays/Mods/RankingInformationDisplay.cs b/osu.Game/Overlays/Mods/RankingInformationDisplay.cs index 494f8a377f..75a8f289d8 100644 --- a/osu.Game/Overlays/Mods/RankingInformationDisplay.cs +++ b/osu.Game/Overlays/Mods/RankingInformationDisplay.cs @@ -52,7 +52,7 @@ namespace osu.Game.Overlays.Mods Anchor = Anchor.BottomRight, Origin = Anchor.BottomRight, RelativeSizeAxes = Axes.Both, - Shear = new Vector2(ShearedOverlayContainer.SHEAR, 0), + Shear = new Vector2(OsuGame.SHEAR, 0), CornerRadius = ShearedButton.CORNER_RADIUS, Masking = true, Children = new Drawable[] @@ -79,7 +79,7 @@ namespace osu.Game.Overlays.Mods { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Shear = new Vector2(-ShearedOverlayContainer.SHEAR, 0), + Shear = new Vector2(-OsuGame.SHEAR, 0), Font = OsuFont.Default.With(size: 17, weight: FontWeight.SemiBold) } } @@ -94,7 +94,7 @@ namespace osu.Game.Overlays.Mods Origin = Anchor.Centre, Child = counter = new EffectCounter { - Shear = new Vector2(-ShearedOverlayContainer.SHEAR, 0), + Shear = new Vector2(-OsuGame.SHEAR, 0), Anchor = Anchor.Centre, Origin = Anchor.Centre, Current = { BindTarget = ModMultiplier } diff --git a/osu.Game/Overlays/Mods/ShearedOverlayContainer.cs b/osu.Game/Overlays/Mods/ShearedOverlayContainer.cs index 893ac89aa4..acdd1db728 100644 --- a/osu.Game/Overlays/Mods/ShearedOverlayContainer.cs +++ b/osu.Game/Overlays/Mods/ShearedOverlayContainer.cs @@ -22,9 +22,6 @@ namespace osu.Game.Overlays.Mods { protected const float PADDING = 14; - // todo: maybe move this to a higher place since it's used for screen footer buttons etc. - public const float SHEAR = 0.15f; - [Cached] protected readonly OverlayColourProvider ColourProvider; diff --git a/osu.Game/Screens/Footer/ScreenFooterButton.cs b/osu.Game/Screens/Footer/ScreenFooterButton.cs index e0b019bfa8..40e79ed474 100644 --- a/osu.Game/Screens/Footer/ScreenFooterButton.cs +++ b/osu.Game/Screens/Footer/ScreenFooterButton.cs @@ -26,7 +26,7 @@ namespace osu.Game.Screens.Footer { public partial class ScreenFooterButton : OsuClickableContainer, IKeyBindingHandler { - private const float shear = ShearedOverlayContainer.SHEAR; + private const float shear = OsuGame.SHEAR; protected const int CORNER_RADIUS = 10; protected const int BUTTON_HEIGHT = 90; From 820cfbcb005f759d1e0f1858781d762028ff08e2 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 16 May 2024 07:47:30 +0300 Subject: [PATCH 068/139] Remove unused using directives --- osu.Game/Graphics/UserInterface/ShearedButton.cs | 1 - osu.Game/Graphics/UserInterface/ShearedSearchTextBox.cs | 1 - osu.Game/Screens/Footer/ScreenFooterButton.cs | 1 - 3 files changed, 3 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/ShearedButton.cs b/osu.Game/Graphics/UserInterface/ShearedButton.cs index 7eed388707..87d269ccd4 100644 --- a/osu.Game/Graphics/UserInterface/ShearedButton.cs +++ b/osu.Game/Graphics/UserInterface/ShearedButton.cs @@ -11,7 +11,6 @@ using osu.Framework.Localisation; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osu.Game.Overlays; -using osu.Game.Overlays.Mods; using osuTK; namespace osu.Game.Graphics.UserInterface diff --git a/osu.Game/Graphics/UserInterface/ShearedSearchTextBox.cs b/osu.Game/Graphics/UserInterface/ShearedSearchTextBox.cs index 5f3716f36c..c6565726b5 100644 --- a/osu.Game/Graphics/UserInterface/ShearedSearchTextBox.cs +++ b/osu.Game/Graphics/UserInterface/ShearedSearchTextBox.cs @@ -11,7 +11,6 @@ using osu.Framework.Graphics.UserInterface; using osu.Framework.Localisation; using osu.Game.Graphics.Sprites; using osu.Game.Overlays; -using osu.Game.Overlays.Mods; using osu.Game.Resources.Localisation.Web; using osuTK; diff --git a/osu.Game/Screens/Footer/ScreenFooterButton.cs b/osu.Game/Screens/Footer/ScreenFooterButton.cs index 40e79ed474..8fbd9f6cba 100644 --- a/osu.Game/Screens/Footer/ScreenFooterButton.cs +++ b/osu.Game/Screens/Footer/ScreenFooterButton.cs @@ -18,7 +18,6 @@ using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osu.Game.Input.Bindings; using osu.Game.Overlays; -using osu.Game.Overlays.Mods; using osuTK; using osuTK.Graphics; From d47c4cb47946877f7ba00bfe0d497137638f8230 Mon Sep 17 00:00:00 2001 From: Aurelian Date: Fri, 24 May 2024 06:28:19 +0200 Subject: [PATCH 069/139] Test for scaling slider flat --- .../Editor/TestSliderScaling.cs | 113 ++++++++++++++++++ 1 file changed, 113 insertions(+) diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSliderScaling.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSliderScaling.cs index 021fdba225..157a08df46 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSliderScaling.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSliderScaling.cs @@ -68,6 +68,119 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor AddAssert("slider length shrunk", () => slider.Path.Distance < distanceBefore); } + [Test] + [Timeout(4000)] //Catches crashes in other threads, but not ideal. Hopefully there is a improvement to this. + public void TestScalingSliderFlat( + [Values(0, 1, 2, 3)] int type_int + ) + { + Slider slider = null; + + switch (type_int) + { + case 0: + AddStep("Add linear slider", () => + { + slider = new Slider { StartTime = EditorClock.CurrentTime, Position = new Vector2(300) }; + + PathControlPoint[] points = + { + new PathControlPoint(new Vector2(0), PathType.LINEAR), + new PathControlPoint(new Vector2(50, 100)), + }; + + slider.Path = new SliderPath(points); + EditorBeatmap.Add(slider); + }); + break; + case 1: + AddStep("Add perfect curve slider", () => + { + slider = new Slider { StartTime = EditorClock.CurrentTime, Position = new Vector2(300) }; + + PathControlPoint[] points = + { + new PathControlPoint(new Vector2(0), PathType.PERFECT_CURVE), + new PathControlPoint(new Vector2(50, 25)), + new PathControlPoint(new Vector2(25, 100)), + }; + + slider.Path = new SliderPath(points); + EditorBeatmap.Add(slider); + }); + break; + case 2: + AddStep("Add bezier slider", () => + { + slider = new Slider { StartTime = EditorClock.CurrentTime, Position = new Vector2(300) }; + + PathControlPoint[] points = + { + new PathControlPoint(new Vector2(0), PathType.BEZIER), + new PathControlPoint(new Vector2(50, 25)), + new PathControlPoint(new Vector2(25, 80)), + new PathControlPoint(new Vector2(40, 100)), + }; + + slider.Path = new SliderPath(points); + EditorBeatmap.Add(slider); + }); + break; + AddStep("Add perfect curve slider", () => + { + slider = new Slider { StartTime = EditorClock.CurrentTime, Position = new Vector2(300) }; + + PathControlPoint[] points = + { + new PathControlPoint(new Vector2(0), PathType.PERFECT_CURVE), + new PathControlPoint(new Vector2(50, 25)), + new PathControlPoint(new Vector2(25, 100)), + }; + + slider.Path = new SliderPath(points); + EditorBeatmap.Add(slider); + }); + break; + case 3: + AddStep("Add catmull slider", () => + { + slider = new Slider { StartTime = EditorClock.CurrentTime, Position = new Vector2(300) }; + + PathControlPoint[] points = + { + new PathControlPoint(new Vector2(0), PathType.CATMULL), + new PathControlPoint(new Vector2(50, 25)), + new PathControlPoint(new Vector2(25, 80)), + new PathControlPoint(new Vector2(40, 100)), + }; + + slider.Path = new SliderPath(points); + EditorBeatmap.Add(slider); + }); + break; + } + + AddAssert("ensure object placed", () => EditorBeatmap.HitObjects.Count == 1); + + moveMouse(new Vector2(300)); + AddStep("select slider", () => InputManager.Click(MouseButton.Left)); + AddStep("slider is valid", () => slider.Path.GetSegmentEnds()); //To run ensureValid(); + + SelectionBoxDragHandle dragHandle = null!; + AddStep("store drag handle", () => dragHandle = Editor.ChildrenOfType().Skip(1).First()); + AddAssert("is dragHandle not null", () => dragHandle != null); + + AddStep("move mouse to handle", () => InputManager.MoveMouseTo(dragHandle)); + AddStep("begin drag", () => InputManager.PressButton(MouseButton.Left)); + moveMouse(new Vector2(0, 300)); + AddStep("end drag", () => InputManager.ReleaseButton(MouseButton.Left)); + + AddStep("move mouse to handle", () => InputManager.MoveMouseTo(dragHandle)); + AddStep("begin drag", () => InputManager.PressButton(MouseButton.Left)); + moveMouse(new Vector2(0, 300)); //Should crash here if broken, although doesn't count as failed... + AddStep("end drag", () => InputManager.ReleaseButton(MouseButton.Left)); + } + private void moveMouse(Vector2 pos) => AddStep($"move mouse to {pos}", () => InputManager.MoveMouseTo(playfield.ToScreenSpace(pos))); } From d948e0fc5c3af124d56b6d8bb80d2d904f73a704 Mon Sep 17 00:00:00 2001 From: Aurelian Date: Fri, 24 May 2024 08:26:17 +0200 Subject: [PATCH 070/139] Nearly straight sliders are treated as linear --- osu.Game/Rulesets/Objects/SliderPath.cs | 35 +++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/osu.Game/Rulesets/Objects/SliderPath.cs b/osu.Game/Rulesets/Objects/SliderPath.cs index e8e769e3fa..b0a5d02e71 100644 --- a/osu.Game/Rulesets/Objects/SliderPath.cs +++ b/osu.Game/Rulesets/Objects/SliderPath.cs @@ -269,6 +269,37 @@ namespace osu.Game.Rulesets.Objects pathCache.Validate(); } + /// + /// Checks if the array of vectors is almost straight. + /// + /// + /// The angle is first obtained based on the farthest vector from the first, + /// then we find the angle of each vector from the first, + /// and calculate the distance between the two angle vectors. + /// We than scale this distance to the distance from the first vector + /// (or by 10 if the distance is smaller), + /// and if it is greater than acceptableDifference, we return false. + /// + private static bool isAlmostStraight(Vector2[] vectors, float acceptableDifference = 0.1f) + { + if (vectors.Length <= 2 || vectors.All(x => x == vectors.First())) return true; + + Vector2 first = vectors.First(); + Vector2 farthest = vectors.MaxBy(x => Vector2.Distance(first, x)); + + Vector2 angle = Vector2.Normalize(farthest - first); + foreach (Vector2 vector in vectors) + { + if (vector == first) + continue; + + if (Math.Max(10.0f, Vector2.Distance(vector, first)) * Vector2.Distance(Vector2.Normalize(vector - first), angle) > acceptableDifference) + return false; + } + + return true; + } + private void calculatePath() { calculatedPath.Clear(); @@ -293,6 +324,10 @@ namespace osu.Game.Rulesets.Objects var segmentVertices = vertices.AsSpan().Slice(start, i - start + 1); var segmentType = ControlPoints[start].Type ?? PathType.LINEAR; + //If a segment is almost straight, treat it as linear. + if (segmentType != PathType.LINEAR && isAlmostStraight(segmentVertices.ToArray())) + segmentType = PathType.LINEAR; + // No need to calculate path when there is only 1 vertex if (segmentVertices.Length == 1) calculatedPath.Add(segmentVertices[0]); From 481883801fbdb8df3303d2cbec05fbe52cfdbdc9 Mon Sep 17 00:00:00 2001 From: Aurelian Date: Fri, 24 May 2024 08:55:10 +0200 Subject: [PATCH 071/139] Removed duplicate unreachable code --- .../Editor/TestSliderScaling.cs | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSliderScaling.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSliderScaling.cs index 157a08df46..44f85837dc 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSliderScaling.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSliderScaling.cs @@ -126,21 +126,6 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor EditorBeatmap.Add(slider); }); break; - AddStep("Add perfect curve slider", () => - { - slider = new Slider { StartTime = EditorClock.CurrentTime, Position = new Vector2(300) }; - - PathControlPoint[] points = - { - new PathControlPoint(new Vector2(0), PathType.PERFECT_CURVE), - new PathControlPoint(new Vector2(50, 25)), - new PathControlPoint(new Vector2(25, 100)), - }; - - slider.Path = new SliderPath(points); - EditorBeatmap.Add(slider); - }); - break; case 3: AddStep("Add catmull slider", () => { From fff52be59a9bc076aa30d7c9045dcf3acd1aff58 Mon Sep 17 00:00:00 2001 From: Aurelian Date: Fri, 24 May 2024 09:30:24 +0200 Subject: [PATCH 072/139] Addressed code quality issues --- .../Editor/TestSliderScaling.cs | 21 +++++++++++-------- osu.Game/Rulesets/Objects/SliderPath.cs | 5 +++-- 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSliderScaling.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSliderScaling.cs index 44f85837dc..99694f82d1 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSliderScaling.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSliderScaling.cs @@ -71,12 +71,12 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor [Test] [Timeout(4000)] //Catches crashes in other threads, but not ideal. Hopefully there is a improvement to this. public void TestScalingSliderFlat( - [Values(0, 1, 2, 3)] int type_int + [Values(0, 1, 2, 3)] int typeInt ) { - Slider slider = null; + Slider slider = null!; - switch (type_int) + switch (typeInt) { case 0: AddStep("Add linear slider", () => @@ -93,6 +93,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor EditorBeatmap.Add(slider); }); break; + case 1: AddStep("Add perfect curve slider", () => { @@ -109,14 +110,15 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor EditorBeatmap.Add(slider); }); break; - case 2: - AddStep("Add bezier slider", () => + + case 3: + AddStep("Add catmull slider", () => { slider = new Slider { StartTime = EditorClock.CurrentTime, Position = new Vector2(300) }; PathControlPoint[] points = { - new PathControlPoint(new Vector2(0), PathType.BEZIER), + new PathControlPoint(new Vector2(0), PathType.CATMULL), new PathControlPoint(new Vector2(50, 25)), new PathControlPoint(new Vector2(25, 80)), new PathControlPoint(new Vector2(40, 100)), @@ -126,14 +128,15 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor EditorBeatmap.Add(slider); }); break; - case 3: - AddStep("Add catmull slider", () => + + default: + AddStep("Add bezier slider", () => { slider = new Slider { StartTime = EditorClock.CurrentTime, Position = new Vector2(300) }; PathControlPoint[] points = { - new PathControlPoint(new Vector2(0), PathType.CATMULL), + new PathControlPoint(new Vector2(0), PathType.BEZIER), new PathControlPoint(new Vector2(50, 25)), new PathControlPoint(new Vector2(25, 80)), new PathControlPoint(new Vector2(40, 100)), diff --git a/osu.Game/Rulesets/Objects/SliderPath.cs b/osu.Game/Rulesets/Objects/SliderPath.cs index b0a5d02e71..ddea23034c 100644 --- a/osu.Game/Rulesets/Objects/SliderPath.cs +++ b/osu.Game/Rulesets/Objects/SliderPath.cs @@ -274,9 +274,9 @@ namespace osu.Game.Rulesets.Objects /// /// /// The angle is first obtained based on the farthest vector from the first, - /// then we find the angle of each vector from the first, + /// then we find the angle of each vector from the first, /// and calculate the distance between the two angle vectors. - /// We than scale this distance to the distance from the first vector + /// We than scale this distance to the distance from the first vector /// (or by 10 if the distance is smaller), /// and if it is greater than acceptableDifference, we return false. /// @@ -288,6 +288,7 @@ namespace osu.Game.Rulesets.Objects Vector2 farthest = vectors.MaxBy(x => Vector2.Distance(first, x)); Vector2 angle = Vector2.Normalize(farthest - first); + foreach (Vector2 vector in vectors) { if (vector == first) From b2c4e0e951bb9fc5e1ba50ffe4429109f11b34cc Mon Sep 17 00:00:00 2001 From: Aurelian Date: Fri, 24 May 2024 14:05:56 +0200 Subject: [PATCH 073/139] Reworked linear line check, and optimized scaled flat slider test --- .../Editor/TestSliderScaling.cs | 147 ++++++------------ osu.Game/Rulesets/Objects/SliderPath.cs | 43 +---- 2 files changed, 53 insertions(+), 137 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSliderScaling.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSliderScaling.cs index 99694f82d1..ef3824b5b0 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSliderScaling.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSliderScaling.cs @@ -6,6 +6,7 @@ using System.Linq; using NUnit.Framework; using osu.Framework.Testing; +using osu.Framework.Utils; using osu.Game.Beatmaps; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Types; @@ -68,108 +69,52 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor AddAssert("slider length shrunk", () => slider.Path.Distance < distanceBefore); } - [Test] - [Timeout(4000)] //Catches crashes in other threads, but not ideal. Hopefully there is a improvement to this. - public void TestScalingSliderFlat( - [Values(0, 1, 2, 3)] int typeInt - ) - { - Slider slider = null!; - - switch (typeInt) - { - case 0: - AddStep("Add linear slider", () => - { - slider = new Slider { StartTime = EditorClock.CurrentTime, Position = new Vector2(300) }; - - PathControlPoint[] points = - { - new PathControlPoint(new Vector2(0), PathType.LINEAR), - new PathControlPoint(new Vector2(50, 100)), - }; - - slider.Path = new SliderPath(points); - EditorBeatmap.Add(slider); - }); - break; - - case 1: - AddStep("Add perfect curve slider", () => - { - slider = new Slider { StartTime = EditorClock.CurrentTime, Position = new Vector2(300) }; - - PathControlPoint[] points = - { - new PathControlPoint(new Vector2(0), PathType.PERFECT_CURVE), - new PathControlPoint(new Vector2(50, 25)), - new PathControlPoint(new Vector2(25, 100)), - }; - - slider.Path = new SliderPath(points); - EditorBeatmap.Add(slider); - }); - break; - - case 3: - AddStep("Add catmull slider", () => - { - slider = new Slider { StartTime = EditorClock.CurrentTime, Position = new Vector2(300) }; - - PathControlPoint[] points = - { - new PathControlPoint(new Vector2(0), PathType.CATMULL), - new PathControlPoint(new Vector2(50, 25)), - new PathControlPoint(new Vector2(25, 80)), - new PathControlPoint(new Vector2(40, 100)), - }; - - slider.Path = new SliderPath(points); - EditorBeatmap.Add(slider); - }); - break; - - default: - AddStep("Add bezier slider", () => - { - slider = new Slider { StartTime = EditorClock.CurrentTime, Position = new Vector2(300) }; - - PathControlPoint[] points = - { - new PathControlPoint(new Vector2(0), PathType.BEZIER), - new PathControlPoint(new Vector2(50, 25)), - new PathControlPoint(new Vector2(25, 80)), - new PathControlPoint(new Vector2(40, 100)), - }; - - slider.Path = new SliderPath(points); - EditorBeatmap.Add(slider); - }); - break; - } - - AddAssert("ensure object placed", () => EditorBeatmap.HitObjects.Count == 1); - - moveMouse(new Vector2(300)); - AddStep("select slider", () => InputManager.Click(MouseButton.Left)); - AddStep("slider is valid", () => slider.Path.GetSegmentEnds()); //To run ensureValid(); - - SelectionBoxDragHandle dragHandle = null!; - AddStep("store drag handle", () => dragHandle = Editor.ChildrenOfType().Skip(1).First()); - AddAssert("is dragHandle not null", () => dragHandle != null); - - AddStep("move mouse to handle", () => InputManager.MoveMouseTo(dragHandle)); - AddStep("begin drag", () => InputManager.PressButton(MouseButton.Left)); - moveMouse(new Vector2(0, 300)); - AddStep("end drag", () => InputManager.ReleaseButton(MouseButton.Left)); - - AddStep("move mouse to handle", () => InputManager.MoveMouseTo(dragHandle)); - AddStep("begin drag", () => InputManager.PressButton(MouseButton.Left)); - moveMouse(new Vector2(0, 300)); //Should crash here if broken, although doesn't count as failed... - AddStep("end drag", () => InputManager.ReleaseButton(MouseButton.Left)); - } - private void moveMouse(Vector2 pos) => AddStep($"move mouse to {pos}", () => InputManager.MoveMouseTo(playfield.ToScreenSpace(pos))); } + [TestFixture] + public class TestSliderNearLinearScaling + { + [Test] + public void TestScalingSliderFlat() + { + Slider sliderPerfect = new Slider + { + Position = new Vector2(300), + Path = new SliderPath( + [ + new PathControlPoint(new Vector2(0), PathType.PERFECT_CURVE), + new PathControlPoint(new Vector2(50, 25)), + new PathControlPoint(new Vector2(25, 100)), + ]) + }; + + Slider sliderBezier = new Slider + { + Position = new Vector2(300), + Path = new SliderPath( + [ + new PathControlPoint(new Vector2(0), PathType.BEZIER), + new PathControlPoint(new Vector2(50, 25)), + new PathControlPoint(new Vector2(25, 100)), + ]) + }; + + scaleSlider(sliderPerfect, new Vector2(0.000001f, 1)); + scaleSlider(sliderBezier, new Vector2(0.000001f, 1)); + + for (int i = 0; i < 100; i++) + { + Assert.True(Precision.AlmostEquals(sliderPerfect.Path.PositionAt(i / 100.0f), sliderBezier.Path.PositionAt(i / 100.0f))); + } + } + + private void scaleSlider(Slider slider, Vector2 scale) + { + for (int i = 0; i < slider.Path.ControlPoints.Count; i++) + { + slider.Path.ControlPoints[i].Position *= scale; + } + } + } } diff --git a/osu.Game/Rulesets/Objects/SliderPath.cs b/osu.Game/Rulesets/Objects/SliderPath.cs index ddea23034c..eca14269fe 100644 --- a/osu.Game/Rulesets/Objects/SliderPath.cs +++ b/osu.Game/Rulesets/Objects/SliderPath.cs @@ -269,38 +269,6 @@ namespace osu.Game.Rulesets.Objects pathCache.Validate(); } - /// - /// Checks if the array of vectors is almost straight. - /// - /// - /// The angle is first obtained based on the farthest vector from the first, - /// then we find the angle of each vector from the first, - /// and calculate the distance between the two angle vectors. - /// We than scale this distance to the distance from the first vector - /// (or by 10 if the distance is smaller), - /// and if it is greater than acceptableDifference, we return false. - /// - private static bool isAlmostStraight(Vector2[] vectors, float acceptableDifference = 0.1f) - { - if (vectors.Length <= 2 || vectors.All(x => x == vectors.First())) return true; - - Vector2 first = vectors.First(); - Vector2 farthest = vectors.MaxBy(x => Vector2.Distance(first, x)); - - Vector2 angle = Vector2.Normalize(farthest - first); - - foreach (Vector2 vector in vectors) - { - if (vector == first) - continue; - - if (Math.Max(10.0f, Vector2.Distance(vector, first)) * Vector2.Distance(Vector2.Normalize(vector - first), angle) > acceptableDifference) - return false; - } - - return true; - } - private void calculatePath() { calculatedPath.Clear(); @@ -325,10 +293,6 @@ namespace osu.Game.Rulesets.Objects var segmentVertices = vertices.AsSpan().Slice(start, i - start + 1); var segmentType = ControlPoints[start].Type ?? PathType.LINEAR; - //If a segment is almost straight, treat it as linear. - if (segmentType != PathType.LINEAR && isAlmostStraight(segmentVertices.ToArray())) - segmentType = PathType.LINEAR; - // No need to calculate path when there is only 1 vertex if (segmentVertices.Length == 1) calculatedPath.Add(segmentVertices[0]); @@ -366,6 +330,13 @@ namespace osu.Game.Rulesets.Objects if (subControlPoints.Length != 3) break; + //If a curve's theta range almost equals zero, the radius needed to have more than a + //floating point error difference is very large and results in a nearly straight path. + //Calculate it via a bezier aproximation instead. + //0.0005 corresponds with a radius of 8000 to have a more than 0.001 shift in the X value + if (Math.Abs(new CircularArcProperties(subControlPoints).ThetaRange) <= 0.0005d) + break; + List subPath = PathApproximator.CircularArcToPiecewiseLinear(subControlPoints); // If for some reason a circular arc could not be fit to the 3 given points, fall back to a numerically stable bezier approximation. From 497701950d9582c9792f0da3ec883d69079adeb6 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Fri, 24 May 2024 18:11:28 +0200 Subject: [PATCH 074/139] fix nitpick --- .../Screens/Edit/Compose/Components/LinedPositionSnapGrid.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/LinedPositionSnapGrid.cs b/osu.Game/Screens/Edit/Compose/Components/LinedPositionSnapGrid.cs index 8a7f6b5344..79b4fa2841 100644 --- a/osu.Game/Screens/Edit/Compose/Components/LinedPositionSnapGrid.cs +++ b/osu.Game/Screens/Edit/Compose/Components/LinedPositionSnapGrid.cs @@ -28,7 +28,8 @@ namespace osu.Game.Screens.Edit.Compose.Components while (true) { - Vector2 currentPosition = StartPosition.Value + index++ * step; + Vector2 currentPosition = StartPosition.Value + index * step; + index++; if (!lineDefinitelyIntersectsBox(currentPosition, step.PerpendicularLeft, drawSize, out var p1, out var p2)) { From 6aa92bcc4559ec52a85e8e026e579c03f1d7aca0 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Sat, 25 May 2024 18:31:19 +0200 Subject: [PATCH 075/139] Add simple scale tool --- .../Edit/OsuHitObjectComposer.cs | 6 +- .../Edit/PreciseScalePopover.cs | 121 ++++++++++++++++++ .../Edit/TransformToolboxGroup.cs | 33 ++++- .../Input/Bindings/GlobalActionContainer.cs | 4 + .../GlobalActionKeyBindingStrings.cs | 5 + 5 files changed, 167 insertions(+), 2 deletions(-) create mode 100644 osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs diff --git a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs index 3ead61f64a..6f3ed9730e 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs @@ -101,7 +101,11 @@ namespace osu.Game.Rulesets.Osu.Edit RightToolbox.AddRange(new EditorToolboxGroup[] { - new TransformToolboxGroup { RotationHandler = BlueprintContainer.SelectionHandler.RotationHandler, }, + new TransformToolboxGroup + { + RotationHandler = BlueprintContainer.SelectionHandler.RotationHandler, + ScaleHandler = BlueprintContainer.SelectionHandler.ScaleHandler, + }, FreehandlSliderToolboxGroup } ); diff --git a/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs b/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs new file mode 100644 index 0000000000..62408e223d --- /dev/null +++ b/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs @@ -0,0 +1,121 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; +using osu.Game.Graphics.UserInterfaceV2; +using osu.Game.Rulesets.Osu.UI; +using osu.Game.Screens.Edit.Components.RadioButtons; +using osu.Game.Screens.Edit.Compose.Components; +using osuTK; + +namespace osu.Game.Rulesets.Osu.Edit +{ + public partial class PreciseScalePopover : OsuPopover + { + private readonly SelectionScaleHandler scaleHandler; + + private readonly Bindable scaleInfo = new Bindable(new PreciseScaleInfo(1, ScaleOrigin.PlayfieldCentre, true, true)); + + private SliderWithTextBoxInput scaleInput = null!; + private EditorRadioButtonCollection scaleOrigin = null!; + + private RadioButton selectionCentreButton = null!; + + public PreciseScalePopover(SelectionScaleHandler scaleHandler) + { + this.scaleHandler = scaleHandler; + + AllowableAnchors = new[] { Anchor.CentreLeft, Anchor.CentreRight }; + } + + [BackgroundDependencyLoader] + private void load() + { + Child = new FillFlowContainer + { + Width = 220, + AutoSizeAxes = Axes.Y, + Spacing = new Vector2(20), + Children = new Drawable[] + { + scaleInput = new SliderWithTextBoxInput("Scale:") + { + Current = new BindableNumber + { + MinValue = 0.5f, + MaxValue = 2, + Precision = 0.001f, + Value = 1, + Default = 1, + }, + Instantaneous = true + }, + scaleOrigin = new EditorRadioButtonCollection + { + RelativeSizeAxes = Axes.X, + Items = new[] + { + new RadioButton("Playfield centre", + () => scaleInfo.Value = scaleInfo.Value with { Origin = ScaleOrigin.PlayfieldCentre }, + () => new SpriteIcon { Icon = FontAwesome.Regular.Square }), + selectionCentreButton = new RadioButton("Selection centre", + () => scaleInfo.Value = scaleInfo.Value with { Origin = ScaleOrigin.SelectionCentre }, + () => new SpriteIcon { Icon = FontAwesome.Solid.VectorSquare }) + } + } + } + }; + selectionCentreButton.Selected.DisabledChanged += isDisabled => + { + selectionCentreButton.TooltipText = isDisabled ? "Select more than one object to perform selection-based scaling." : string.Empty; + }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + ScheduleAfterChildren(() => scaleInput.TakeFocus()); + scaleInput.Current.BindValueChanged(scale => scaleInfo.Value = scaleInfo.Value with { Scale = scale.NewValue }); + scaleOrigin.Items.First().Select(); + + scaleHandler.CanScaleX.BindValueChanged(e => + { + selectionCentreButton.Selected.Disabled = !e.NewValue; + }, true); + + scaleInfo.BindValueChanged(scale => + { + var newScale = new Vector2(scale.NewValue.XAxis ? scale.NewValue.Scale : 1, scale.NewValue.YAxis ? scale.NewValue.Scale : 1); + scaleHandler.Update(newScale, scale.NewValue.Origin == ScaleOrigin.PlayfieldCentre ? OsuPlayfield.BASE_SIZE / 2 : null); + }); + } + + protected override void PopIn() + { + base.PopIn(); + scaleHandler.Begin(); + } + + protected override void PopOut() + { + base.PopOut(); + + if (IsLoaded) + scaleHandler.Commit(); + } + } + + public enum ScaleOrigin + { + PlayfieldCentre, + SelectionCentre + } + + public record PreciseScaleInfo(float Scale, ScaleOrigin Origin, bool XAxis, bool YAxis); +} diff --git a/osu.Game.Rulesets.Osu/Edit/TransformToolboxGroup.cs b/osu.Game.Rulesets.Osu/Edit/TransformToolboxGroup.cs index 9499bacade..146d771e19 100644 --- a/osu.Game.Rulesets.Osu/Edit/TransformToolboxGroup.cs +++ b/osu.Game.Rulesets.Osu/Edit/TransformToolboxGroup.cs @@ -19,13 +19,20 @@ namespace osu.Game.Rulesets.Osu.Edit public partial class TransformToolboxGroup : EditorToolboxGroup, IKeyBindingHandler { private readonly Bindable canRotate = new BindableBool(); + private readonly Bindable canScale = new BindableBool(); private EditorToolButton rotateButton = null!; + private EditorToolButton scaleButton = null!; private Bindable canRotatePlayfieldOrigin = null!; private Bindable canRotateSelectionOrigin = null!; + private Bindable canScaleX = null!; + private Bindable canScaleY = null!; + private Bindable canScaleDiagonally = null!; + public SelectionRotationHandler RotationHandler { get; init; } = null!; + public SelectionScaleHandler ScaleHandler { get; init; } = null!; public TransformToolboxGroup() : base("transform") @@ -45,7 +52,9 @@ namespace osu.Game.Rulesets.Osu.Edit rotateButton = new EditorToolButton("Rotate", () => new SpriteIcon { Icon = FontAwesome.Solid.Undo }, () => new PreciseRotationPopover(RotationHandler)), - // TODO: scale + scaleButton = new EditorToolButton("Scale", + () => new SpriteIcon { Icon = FontAwesome.Solid.ArrowsAlt }, + () => new PreciseScalePopover(ScaleHandler)) } }; } @@ -66,9 +75,25 @@ namespace osu.Game.Rulesets.Osu.Edit canRotate.Value = RotationHandler.CanRotatePlayfieldOrigin.Value || RotationHandler.CanRotateSelectionOrigin.Value; } + // aggregate three values into canScale + canScaleX = ScaleHandler.CanScaleX.GetBoundCopy(); + canScaleX.BindValueChanged(_ => updateCanScaleAggregate()); + + canScaleY = ScaleHandler.CanScaleY.GetBoundCopy(); + canScaleY.BindValueChanged(_ => updateCanScaleAggregate()); + + canScaleDiagonally = ScaleHandler.CanScaleDiagonally.GetBoundCopy(); + canScaleDiagonally.BindValueChanged(_ => updateCanScaleAggregate()); + + void updateCanScaleAggregate() + { + canScale.Value = ScaleHandler.CanScaleX.Value || ScaleHandler.CanScaleY.Value || ScaleHandler.CanScaleDiagonally.Value; + } + // bindings to `Enabled` on the buttons are decoupled on purpose // due to the weird `OsuButton` behaviour of resetting `Enabled` to `false` when `Action` is set. canRotate.BindValueChanged(_ => rotateButton.Enabled.Value = canRotate.Value, true); + canScale.BindValueChanged(_ => scaleButton.Enabled.Value = canScale.Value, true); } public bool OnPressed(KeyBindingPressEvent e) @@ -82,6 +107,12 @@ namespace osu.Game.Rulesets.Osu.Edit rotateButton.TriggerClick(); return true; } + + case GlobalAction.EditorToggleScaleControl: + { + scaleButton.TriggerClick(); + return true; + } } return false; diff --git a/osu.Game/Input/Bindings/GlobalActionContainer.cs b/osu.Game/Input/Bindings/GlobalActionContainer.cs index 09db7461d6..394cb98089 100644 --- a/osu.Game/Input/Bindings/GlobalActionContainer.cs +++ b/osu.Game/Input/Bindings/GlobalActionContainer.cs @@ -142,6 +142,7 @@ namespace osu.Game.Input.Bindings new KeyBinding(new[] { InputKey.Control, InputKey.Shift, InputKey.MouseWheelRight }, GlobalAction.EditorCyclePreviousBeatSnapDivisor), new KeyBinding(new[] { InputKey.Control, InputKey.Shift, InputKey.MouseWheelLeft }, GlobalAction.EditorCycleNextBeatSnapDivisor), new KeyBinding(new[] { InputKey.Control, InputKey.R }, GlobalAction.EditorToggleRotateControl), + new KeyBinding(new[] { InputKey.S }, GlobalAction.EditorToggleScaleControl), }; private static IEnumerable inGameKeyBindings => new[] @@ -411,6 +412,9 @@ namespace osu.Game.Input.Bindings [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.EditorToggleRotateControl))] EditorToggleRotateControl, + [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.EditorToggleScaleControl))] + EditorToggleScaleControl, + [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.IncreaseOffset))] IncreaseOffset, diff --git a/osu.Game/Localisation/GlobalActionKeyBindingStrings.cs b/osu.Game/Localisation/GlobalActionKeyBindingStrings.cs index 18a1d3e4fe..2e44b96625 100644 --- a/osu.Game/Localisation/GlobalActionKeyBindingStrings.cs +++ b/osu.Game/Localisation/GlobalActionKeyBindingStrings.cs @@ -369,6 +369,11 @@ namespace osu.Game.Localisation /// public static LocalisableString EditorToggleRotateControl => new TranslatableString(getKey(@"editor_toggle_rotate_control"), @"Toggle rotate control"); + /// + /// "Toggle scale control" + /// + public static LocalisableString EditorToggleScaleControl => new TranslatableString(getKey(@"editor_toggle_scale_control"), @"Toggle scale control"); + /// /// "Increase mod speed" /// From 88314dc584f186cf8a99c631d16047f321135292 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Sat, 25 May 2024 18:41:31 +0200 Subject: [PATCH 076/139] select all input text on popup for an easy typing experience --- osu.Game.Rulesets.Osu/Edit/PreciseRotationPopover.cs | 6 +++++- osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs | 6 +++++- osu.Game/Graphics/UserInterfaceV2/LabelledTextBox.cs | 2 ++ osu.Game/Graphics/UserInterfaceV2/SliderWithTextBoxInput.cs | 2 ++ 4 files changed, 14 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/PreciseRotationPopover.cs b/osu.Game.Rulesets.Osu/Edit/PreciseRotationPopover.cs index 88c3d7414b..812d622ae5 100644 --- a/osu.Game.Rulesets.Osu/Edit/PreciseRotationPopover.cs +++ b/osu.Game.Rulesets.Osu/Edit/PreciseRotationPopover.cs @@ -78,7 +78,11 @@ namespace osu.Game.Rulesets.Osu.Edit { base.LoadComplete(); - ScheduleAfterChildren(() => angleInput.TakeFocus()); + ScheduleAfterChildren(() => + { + angleInput.TakeFocus(); + angleInput.SelectAll(); + }); angleInput.Current.BindValueChanged(angle => rotationInfo.Value = rotationInfo.Value with { Degrees = angle.NewValue }); rotationOrigin.Items.First().Select(); diff --git a/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs b/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs index 62408e223d..ed52da56d3 100644 --- a/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs +++ b/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs @@ -80,7 +80,11 @@ namespace osu.Game.Rulesets.Osu.Edit { base.LoadComplete(); - ScheduleAfterChildren(() => scaleInput.TakeFocus()); + ScheduleAfterChildren(() => + { + scaleInput.TakeFocus(); + scaleInput.SelectAll(); + }); scaleInput.Current.BindValueChanged(scale => scaleInfo.Value = scaleInfo.Value with { Scale = scale.NewValue }); scaleOrigin.Items.First().Select(); diff --git a/osu.Game/Graphics/UserInterfaceV2/LabelledTextBox.cs b/osu.Game/Graphics/UserInterfaceV2/LabelledTextBox.cs index 863ad5a173..8dfe729ce7 100644 --- a/osu.Game/Graphics/UserInterfaceV2/LabelledTextBox.cs +++ b/osu.Game/Graphics/UserInterfaceV2/LabelledTextBox.cs @@ -50,6 +50,8 @@ namespace osu.Game.Graphics.UserInterfaceV2 Component.BorderColour = colours.Blue; } + public bool SelectAll() => Component.SelectAll(); + protected virtual OsuTextBox CreateTextBox() => new OsuTextBox(); public override bool AcceptsFocus => true; diff --git a/osu.Game/Graphics/UserInterfaceV2/SliderWithTextBoxInput.cs b/osu.Game/Graphics/UserInterfaceV2/SliderWithTextBoxInput.cs index 4c16cb4951..f1f4fe3b46 100644 --- a/osu.Game/Graphics/UserInterfaceV2/SliderWithTextBoxInput.cs +++ b/osu.Game/Graphics/UserInterfaceV2/SliderWithTextBoxInput.cs @@ -87,6 +87,8 @@ namespace osu.Game.Graphics.UserInterfaceV2 public bool TakeFocus() => GetContainingFocusManager().ChangeFocus(textBox); + public bool SelectAll() => textBox.SelectAll(); + private bool updatingFromTextBox; private void textChanged(ValueChangedEvent change) From 4eeebdf60cd70eedcf6692cbad36cc8df6abc8de Mon Sep 17 00:00:00 2001 From: OliBomby Date: Sat, 25 May 2024 20:17:27 +0200 Subject: [PATCH 077/139] calculate max scale bounds for scale slider --- .../Edit/OsuSelectionScaleHandler.cs | 47 ++++++++++--------- .../Edit/PreciseScalePopover.cs | 35 ++++++++++++-- .../Components/SelectionScaleHandler.cs | 8 ++++ 3 files changed, 64 insertions(+), 26 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs index af03c4d925..331e8de3f1 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs @@ -67,7 +67,7 @@ namespace osu.Game.Rulesets.Osu.Edit objectsInScale = selectedMovableObjects.ToDictionary(ho => ho, ho => new OriginalHitObjectState(ho)); OriginalSurroundingQuad = objectsInScale.Count == 1 && objectsInScale.First().Key is Slider slider - ? GeometryUtils.GetSurroundingQuad(slider.Path.ControlPoints.Select(p => p.Position)) + ? GeometryUtils.GetSurroundingQuad(slider.Path.ControlPoints.Select(p => slider.Position + p.Position)) : GeometryUtils.GetSurroundingQuad(objectsInScale.Keys); defaultOrigin = OriginalSurroundingQuad.Value.Centre; } @@ -92,7 +92,7 @@ namespace osu.Game.Rulesets.Osu.Edit } else { - scale = getClampedScale(OriginalSurroundingQuad.Value, actualOrigin, scale); + scale = GetClampedScale(scale, actualOrigin); foreach (var (ho, originalState) in objectsInScale) { @@ -155,30 +155,33 @@ namespace osu.Game.Rulesets.Osu.Edit return (xInBounds, yInBounds); } - /// - /// Clamp scale for multi-object-scaling where selection does not exceed playfield bounds or flip. - /// - /// The quad surrounding the hitobjects - /// The origin from which the scale operation is performed - /// The scale to be clamped - /// The clamped scale vector - private Vector2 getClampedScale(Quad selectionQuad, Vector2 origin, Vector2 scale) + public override Vector2 GetClampedScale(Vector2 scale, Vector2? origin = null) { //todo: this is not always correct for selections involving sliders. This approximation assumes each point is scaled independently, but sliderends move with the sliderhead. + if (objectsInScale == null) + return scale; - var tl1 = Vector2.Divide(-origin, selectionQuad.TopLeft - origin); - var tl2 = Vector2.Divide(OsuPlayfield.BASE_SIZE - origin, selectionQuad.TopLeft - origin); - var br1 = Vector2.Divide(-origin, selectionQuad.BottomRight - origin); - var br2 = Vector2.Divide(OsuPlayfield.BASE_SIZE - origin, selectionQuad.BottomRight - origin); + Debug.Assert(defaultOrigin != null && OriginalSurroundingQuad != null); - if (!Precision.AlmostEquals(selectionQuad.TopLeft.X - origin.X, 0)) - scale.X = selectionQuad.TopLeft.X - origin.X < 0 ? MathHelper.Clamp(scale.X, tl2.X, tl1.X) : MathHelper.Clamp(scale.X, tl1.X, tl2.X); - if (!Precision.AlmostEquals(selectionQuad.TopLeft.Y - origin.Y, 0)) - scale.Y = selectionQuad.TopLeft.Y - origin.Y < 0 ? MathHelper.Clamp(scale.Y, tl2.Y, tl1.Y) : MathHelper.Clamp(scale.Y, tl1.Y, tl2.Y); - if (!Precision.AlmostEquals(selectionQuad.BottomRight.X - origin.X, 0)) - scale.X = selectionQuad.BottomRight.X - origin.X < 0 ? MathHelper.Clamp(scale.X, br2.X, br1.X) : MathHelper.Clamp(scale.X, br1.X, br2.X); - if (!Precision.AlmostEquals(selectionQuad.BottomRight.Y - origin.Y, 0)) - scale.Y = selectionQuad.BottomRight.Y - origin.Y < 0 ? MathHelper.Clamp(scale.Y, br2.Y, br1.Y) : MathHelper.Clamp(scale.Y, br1.Y, br2.Y); + if (objectsInScale.Count == 1 && objectsInScale.First().Key is Slider slider) + origin = slider.Position; + + Vector2 actualOrigin = origin ?? defaultOrigin.Value; + var selectionQuad = OriginalSurroundingQuad.Value; + + var tl1 = Vector2.Divide(-actualOrigin, selectionQuad.TopLeft - actualOrigin); + var tl2 = Vector2.Divide(OsuPlayfield.BASE_SIZE - actualOrigin, selectionQuad.TopLeft - actualOrigin); + var br1 = Vector2.Divide(-actualOrigin, selectionQuad.BottomRight - actualOrigin); + var br2 = Vector2.Divide(OsuPlayfield.BASE_SIZE - actualOrigin, selectionQuad.BottomRight - actualOrigin); + + if (!Precision.AlmostEquals(selectionQuad.TopLeft.X - actualOrigin.X, 0)) + scale.X = selectionQuad.TopLeft.X - actualOrigin.X < 0 ? MathHelper.Clamp(scale.X, tl2.X, tl1.X) : MathHelper.Clamp(scale.X, tl1.X, tl2.X); + if (!Precision.AlmostEquals(selectionQuad.TopLeft.Y - actualOrigin.Y, 0)) + scale.Y = selectionQuad.TopLeft.Y - actualOrigin.Y < 0 ? MathHelper.Clamp(scale.Y, tl2.Y, tl1.Y) : MathHelper.Clamp(scale.Y, tl1.Y, tl2.Y); + if (!Precision.AlmostEquals(selectionQuad.BottomRight.X - actualOrigin.X, 0)) + scale.X = selectionQuad.BottomRight.X - actualOrigin.X < 0 ? MathHelper.Clamp(scale.X, br2.X, br1.X) : MathHelper.Clamp(scale.X, br1.X, br2.X); + if (!Precision.AlmostEquals(selectionQuad.BottomRight.Y - actualOrigin.Y, 0)) + scale.Y = selectionQuad.BottomRight.Y - actualOrigin.Y < 0 ? MathHelper.Clamp(scale.Y, br2.Y, br1.Y) : MathHelper.Clamp(scale.Y, br1.Y, br2.Y); return Vector2.ComponentMax(scale, new Vector2(Precision.FLOAT_EPSILON)); } diff --git a/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs b/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs index ed52da56d3..50195ebd1e 100644 --- a/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs +++ b/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.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 System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -22,6 +23,7 @@ namespace osu.Game.Rulesets.Osu.Edit private readonly Bindable scaleInfo = new Bindable(new PreciseScaleInfo(1, ScaleOrigin.PlayfieldCentre, true, true)); private SliderWithTextBoxInput scaleInput = null!; + private BindableNumber scaleInputBindable = null!; private EditorRadioButtonCollection scaleOrigin = null!; private RadioButton selectionCentreButton = null!; @@ -45,7 +47,7 @@ namespace osu.Game.Rulesets.Osu.Edit { scaleInput = new SliderWithTextBoxInput("Scale:") { - Current = new BindableNumber + Current = scaleInputBindable = new BindableNumber { MinValue = 0.5f, MaxValue = 2, @@ -61,10 +63,10 @@ namespace osu.Game.Rulesets.Osu.Edit Items = new[] { new RadioButton("Playfield centre", - () => scaleInfo.Value = scaleInfo.Value with { Origin = ScaleOrigin.PlayfieldCentre }, + () => setOrigin(ScaleOrigin.PlayfieldCentre), () => new SpriteIcon { Icon = FontAwesome.Regular.Square }), selectionCentreButton = new RadioButton("Selection centre", - () => scaleInfo.Value = scaleInfo.Value with { Origin = ScaleOrigin.SelectionCentre }, + () => setOrigin(ScaleOrigin.SelectionCentre), () => new SpriteIcon { Icon = FontAwesome.Solid.VectorSquare }) } } @@ -96,14 +98,39 @@ namespace osu.Game.Rulesets.Osu.Edit scaleInfo.BindValueChanged(scale => { var newScale = new Vector2(scale.NewValue.XAxis ? scale.NewValue.Scale : 1, scale.NewValue.YAxis ? scale.NewValue.Scale : 1); - scaleHandler.Update(newScale, scale.NewValue.Origin == ScaleOrigin.PlayfieldCentre ? OsuPlayfield.BASE_SIZE / 2 : null); + scaleHandler.Update(newScale, getOriginPosition(scale.NewValue)); }); } + private void updateMaxScale() + { + if (!scaleHandler.OriginalSurroundingQuad.HasValue) + return; + + const float max_scale = 10; + var scale = scaleHandler.GetClampedScale(new Vector2(max_scale), getOriginPosition(scaleInfo.Value)); + + if (!scaleInfo.Value.XAxis) + scale.X = max_scale; + if (!scaleInfo.Value.YAxis) + scale.Y = max_scale; + + scaleInputBindable.MaxValue = MathF.Max(1, MathF.Min(scale.X, scale.Y)); + } + + private void setOrigin(ScaleOrigin origin) + { + scaleInfo.Value = scaleInfo.Value with { Origin = origin }; + updateMaxScale(); + } + + private Vector2? getOriginPosition(PreciseScaleInfo scale) => scale.Origin == ScaleOrigin.PlayfieldCentre ? OsuPlayfield.BASE_SIZE / 2 : null; + protected override void PopIn() { base.PopIn(); scaleHandler.Begin(); + updateMaxScale(); } protected override void PopOut() diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionScaleHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionScaleHandler.cs index a96f627e56..fb421c2329 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionScaleHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionScaleHandler.cs @@ -34,6 +34,14 @@ namespace osu.Game.Screens.Edit.Compose.Components public Quad? OriginalSurroundingQuad { get; protected set; } + /// + /// Clamp scale where selection does not exceed playfield bounds or flip. + /// + /// The origin from which the scale operation is performed + /// The scale to be clamped + /// The clamped scale vector + public virtual Vector2 GetClampedScale(Vector2 scale, Vector2? origin = null) => scale; + /// /// Performs a single, instant, atomic scale operation. /// From 37530eebccdc6b7bacee3742946830ac61e2e815 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Sat, 25 May 2024 20:35:06 +0200 Subject: [PATCH 078/139] Enable scale buttons at the correct times --- osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs | 2 ++ osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs | 2 +- osu.Game.Rulesets.Osu/Edit/TransformToolboxGroup.cs | 8 ++++---- .../Edit/Compose/Components/SelectionScaleHandler.cs | 10 ++++++++++ 4 files changed, 17 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs index 331e8de3f1..e45494977f 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs @@ -53,6 +53,8 @@ namespace osu.Game.Rulesets.Osu.Edit CanScaleX.Value = quad.Width > 0; CanScaleY.Value = quad.Height > 0; CanScaleDiagonally.Value = CanScaleX.Value && CanScaleY.Value; + CanScaleSelectionOrigin.Value = CanScaleX.Value || CanScaleY.Value; + CanScalePlayfieldOrigin.Value = selectedMovableObjects.Any(); } private Dictionary? objectsInScale; diff --git a/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs b/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs index 50195ebd1e..355064f000 100644 --- a/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs +++ b/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs @@ -90,7 +90,7 @@ namespace osu.Game.Rulesets.Osu.Edit scaleInput.Current.BindValueChanged(scale => scaleInfo.Value = scaleInfo.Value with { Scale = scale.NewValue }); scaleOrigin.Items.First().Select(); - scaleHandler.CanScaleX.BindValueChanged(e => + scaleHandler.CanScaleSelectionOrigin.BindValueChanged(e => { selectionCentreButton.Selected.Disabled = !e.NewValue; }, true); diff --git a/osu.Game.Rulesets.Osu/Edit/TransformToolboxGroup.cs b/osu.Game.Rulesets.Osu/Edit/TransformToolboxGroup.cs index 146d771e19..e1f53846dc 100644 --- a/osu.Game.Rulesets.Osu/Edit/TransformToolboxGroup.cs +++ b/osu.Game.Rulesets.Osu/Edit/TransformToolboxGroup.cs @@ -29,7 +29,7 @@ namespace osu.Game.Rulesets.Osu.Edit private Bindable canScaleX = null!; private Bindable canScaleY = null!; - private Bindable canScaleDiagonally = null!; + private Bindable canScalePlayfieldOrigin = null!; public SelectionRotationHandler RotationHandler { get; init; } = null!; public SelectionScaleHandler ScaleHandler { get; init; } = null!; @@ -82,12 +82,12 @@ namespace osu.Game.Rulesets.Osu.Edit canScaleY = ScaleHandler.CanScaleY.GetBoundCopy(); canScaleY.BindValueChanged(_ => updateCanScaleAggregate()); - canScaleDiagonally = ScaleHandler.CanScaleDiagonally.GetBoundCopy(); - canScaleDiagonally.BindValueChanged(_ => updateCanScaleAggregate()); + canScalePlayfieldOrigin = ScaleHandler.CanScalePlayfieldOrigin.GetBoundCopy(); + canScalePlayfieldOrigin.BindValueChanged(_ => updateCanScaleAggregate()); void updateCanScaleAggregate() { - canScale.Value = ScaleHandler.CanScaleX.Value || ScaleHandler.CanScaleY.Value || ScaleHandler.CanScaleDiagonally.Value; + canScale.Value = ScaleHandler.CanScaleX.Value || ScaleHandler.CanScaleY.Value || ScaleHandler.CanScalePlayfieldOrigin.Value; } // bindings to `Enabled` on the buttons are decoupled on purpose diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionScaleHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionScaleHandler.cs index fb421c2329..495cce7ad6 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionScaleHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionScaleHandler.cs @@ -32,6 +32,16 @@ namespace osu.Game.Screens.Edit.Compose.Components /// public Bindable CanScaleDiagonally { get; private set; } = new BindableBool(); + /// + /// Whether scaling anchored by the selection origin can currently be performed. + /// + public Bindable CanScaleSelectionOrigin { get; private set; } = new BindableBool(); + + /// + /// Whether scaling anchored by the center of the playfield can currently be performed. + /// + public Bindable CanScalePlayfieldOrigin { get; private set; } = new BindableBool(); + public Quad? OriginalSurroundingQuad { get; protected set; } /// From d4489545f275fb29eae60ea96fbe4c84d8f82cf2 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Sat, 25 May 2024 21:44:08 +0200 Subject: [PATCH 079/139] add axis toggles --- .../Edit/PreciseScalePopover.cs | 36 ++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs b/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs index 355064f000..b76d778b3d 100644 --- a/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs +++ b/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs @@ -8,6 +8,7 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterfaceV2; using osu.Game.Rulesets.Osu.UI; using osu.Game.Screens.Edit.Components.RadioButtons; @@ -28,6 +29,9 @@ namespace osu.Game.Rulesets.Osu.Edit private RadioButton selectionCentreButton = null!; + private OsuCheckbox xCheckBox = null!; + private OsuCheckbox yCheckBox = null!; + public PreciseScalePopover(SelectionScaleHandler scaleHandler) { this.scaleHandler = scaleHandler; @@ -69,7 +73,28 @@ namespace osu.Game.Rulesets.Osu.Edit () => setOrigin(ScaleOrigin.SelectionCentre), () => new SpriteIcon { Icon = FontAwesome.Solid.VectorSquare }) } - } + }, + new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Spacing = new Vector2(4), + Children = new Drawable[] + { + xCheckBox = new OsuCheckbox(false) + { + RelativeSizeAxes = Axes.X, + LabelText = "X-axis", + Current = { Value = true }, + }, + yCheckBox = new OsuCheckbox(false) + { + RelativeSizeAxes = Axes.X, + LabelText = "Y-axis", + Current = { Value = true }, + }, + } + }, } }; selectionCentreButton.Selected.DisabledChanged += isDisabled => @@ -90,6 +115,9 @@ namespace osu.Game.Rulesets.Osu.Edit scaleInput.Current.BindValueChanged(scale => scaleInfo.Value = scaleInfo.Value with { Scale = scale.NewValue }); scaleOrigin.Items.First().Select(); + xCheckBox.Current.BindValueChanged(x => setAxis(x.NewValue, yCheckBox.Current.Value)); + yCheckBox.Current.BindValueChanged(y => setAxis(xCheckBox.Current.Value, y.NewValue)); + scaleHandler.CanScaleSelectionOrigin.BindValueChanged(e => { selectionCentreButton.Selected.Disabled = !e.NewValue; @@ -126,6 +154,12 @@ namespace osu.Game.Rulesets.Osu.Edit private Vector2? getOriginPosition(PreciseScaleInfo scale) => scale.Origin == ScaleOrigin.PlayfieldCentre ? OsuPlayfield.BASE_SIZE / 2 : null; + private void setAxis(bool x, bool y) + { + scaleInfo.Value = scaleInfo.Value with { XAxis = x, YAxis = y }; + updateMaxScale(); + } + protected override void PopIn() { base.PopIn(); From d81be56adf67a46c0302695b273ac7c0f6c8e212 Mon Sep 17 00:00:00 2001 From: Aurelian Date: Mon, 27 May 2024 19:35:33 +0200 Subject: [PATCH 080/139] Test for accuracy of perfect curves --- .../Editor/TestSliderScaling.cs | 125 ++++++++++++++---- 1 file changed, 99 insertions(+), 26 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSliderScaling.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSliderScaling.cs index ef3824b5b0..2ffde0d3a3 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSliderScaling.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSliderScaling.cs @@ -3,8 +3,10 @@ #nullable disable +using System; using System.Linq; using NUnit.Framework; +using osu.Framework.Graphics.Primitives; using osu.Framework.Testing; using osu.Framework.Utils; using osu.Game.Beatmaps; @@ -72,48 +74,119 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor private void moveMouse(Vector2 pos) => AddStep($"move mouse to {pos}", () => InputManager.MoveMouseTo(playfield.ToScreenSpace(pos))); } + [TestFixture] public class TestSliderNearLinearScaling { + private readonly Random rng = new Random(1337); + [Test] public void TestScalingSliderFlat() { - Slider sliderPerfect = new Slider - { - Position = new Vector2(300), - Path = new SliderPath( - [ - new PathControlPoint(new Vector2(0), PathType.PERFECT_CURVE), - new PathControlPoint(new Vector2(50, 25)), - new PathControlPoint(new Vector2(25, 100)), - ]) - }; + SliderPath sliderPathPerfect = new SliderPath( + [ + new PathControlPoint(new Vector2(0), PathType.PERFECT_CURVE), + new PathControlPoint(new Vector2(50, 25)), + new PathControlPoint(new Vector2(25, 100)), + ]); - Slider sliderBezier = new Slider - { - Position = new Vector2(300), - Path = new SliderPath( - [ - new PathControlPoint(new Vector2(0), PathType.BEZIER), - new PathControlPoint(new Vector2(50, 25)), - new PathControlPoint(new Vector2(25, 100)), - ]) - }; + SliderPath sliderPathBezier = new SliderPath( + [ + new PathControlPoint(new Vector2(0), PathType.BEZIER), + new PathControlPoint(new Vector2(50, 25)), + new PathControlPoint(new Vector2(25, 100)), + ]); - scaleSlider(sliderPerfect, new Vector2(0.000001f, 1)); - scaleSlider(sliderBezier, new Vector2(0.000001f, 1)); + scaleSlider(sliderPathPerfect, new Vector2(0.000001f, 1)); + scaleSlider(sliderPathBezier, new Vector2(0.000001f, 1)); for (int i = 0; i < 100; i++) { - Assert.True(Precision.AlmostEquals(sliderPerfect.Path.PositionAt(i / 100.0f), sliderBezier.Path.PositionAt(i / 100.0f))); + Assert.True(Precision.AlmostEquals(sliderPathPerfect.PositionAt(i / 100.0f), sliderPathBezier.PositionAt(i / 100.0f))); } } - private void scaleSlider(Slider slider, Vector2 scale) + [Test] + public void TestPerfectCurveMatchesTheoretical() { - for (int i = 0; i < slider.Path.ControlPoints.Count; i++) + for (int i = 0; i < 20000; i++) { - slider.Path.ControlPoints[i].Position *= scale; + //Only test points that are in the screen's bounds + float p1X = 640.0f * (float)rng.NextDouble(); + float p2X = 640.0f * (float)rng.NextDouble(); + + float p1Y = 480.0f * (float)rng.NextDouble(); + float p2Y = 480.0f * (float)rng.NextDouble(); + SliderPath sliderPathPerfect = new SliderPath( + [ + new PathControlPoint(new Vector2(0, 0), PathType.PERFECT_CURVE), + new PathControlPoint(new Vector2(p1X, p1Y)), + new PathControlPoint(new Vector2(p2X, p2Y)), + ]); + + assertMatchesPerfectCircle(sliderPathPerfect); + + scaleSlider(sliderPathPerfect, new Vector2(0.00001f, 1)); + + assertMatchesPerfectCircle(sliderPathPerfect); + } + } + + private void assertMatchesPerfectCircle(SliderPath path) + { + if (path.ControlPoints.Count != 3) + return; + + //Replication of PathApproximator.CircularArcToPiecewiseLinear + CircularArcProperties circularArcProperties = new CircularArcProperties(path.ControlPoints.Select(x => x.Position).ToArray()); + + if (!circularArcProperties.IsValid) + return; + + //Addresses cases where circularArcProperties.ThetaRange>0.5 + //Occurs in code in PathControlPointVisualiser.ensureValidPathType + RectangleF boundingBox = PathApproximator.CircularArcBoundingBox(path.ControlPoints.Select(x => x.Position).ToArray()); + if (boundingBox.Width >= 640 || boundingBox.Height >= 480) + return; + + int subpoints = (2f * circularArcProperties.Radius <= 0.1f) ? 2 : Math.Max(2, (int)Math.Ceiling(circularArcProperties.ThetaRange / (2.0 * Math.Acos(1f - 0.1f / circularArcProperties.Radius)))); + + //ignore cases where subpoints is int.MaxValue, result will be garbage + //as well, having this many subpoints will cause an out of memory error, so can't happen during normal useage + if (subpoints == int.MaxValue) + return; + + for (int i = 0; i < Math.Min(subpoints, 100); i++) + { + float progress = (float)rng.NextDouble(); + + //To avoid errors from interpolating points, ensure we check only positions that would be subpoints. + progress = (float)Math.Ceiling(progress * (subpoints - 1)) / (subpoints - 1); + + //Special case - if few subpoints, ensure checking every single one rather than randomly + if (subpoints < 100) + progress = i / (float)(subpoints - 1); + + //edge points cause issue with interpolation, so ignore the last two points and first + if (progress == 0.0f || progress >= (subpoints - 2) / (float)(subpoints - 1)) + continue; + + double theta = circularArcProperties.ThetaStart + circularArcProperties.Direction * progress * circularArcProperties.ThetaRange; + Vector2 vector = new Vector2((float)Math.Cos(theta), (float)Math.Sin(theta)) * circularArcProperties.Radius; + + Assert.True(Precision.AlmostEquals(circularArcProperties.Centre + vector, path.PositionAt(progress), 0.01f), + "A perfect circle with points " + string.Join(", ", path.ControlPoints.Select(x => x.Position)) + " and radius" + circularArcProperties.Radius + "from SliderPath does not almost equal a theoretical perfect circle with " + subpoints + " subpoints" + + ": " + (circularArcProperties.Centre + vector) + " - " + path.PositionAt(progress) + + " = " + (circularArcProperties.Centre + vector - path.PositionAt(progress)) + ); + } + } + + private void scaleSlider(SliderPath path, Vector2 scale) + { + for (int i = 0; i < path.ControlPoints.Count; i++) + { + path.ControlPoints[i].Position *= scale; } } } From 172cfdf88d4fb6ffb1b4dd35c5183b272407629e Mon Sep 17 00:00:00 2001 From: Aurelian Date: Mon, 27 May 2024 19:41:38 +0200 Subject: [PATCH 081/139] Added missing brackets for formulas --- osu.Game.Rulesets.Osu.Tests/Editor/TestSliderScaling.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSliderScaling.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSliderScaling.cs index 2ffde0d3a3..52a170b84e 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSliderScaling.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSliderScaling.cs @@ -149,7 +149,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor if (boundingBox.Width >= 640 || boundingBox.Height >= 480) return; - int subpoints = (2f * circularArcProperties.Radius <= 0.1f) ? 2 : Math.Max(2, (int)Math.Ceiling(circularArcProperties.ThetaRange / (2.0 * Math.Acos(1f - 0.1f / circularArcProperties.Radius)))); + int subpoints = (2f * circularArcProperties.Radius <= 0.1f) ? 2 : Math.Max(2, (int)Math.Ceiling(circularArcProperties.ThetaRange / (2.0 * Math.Acos(1f - (0.1f / circularArcProperties.Radius))))); //ignore cases where subpoints is int.MaxValue, result will be garbage //as well, having this many subpoints will cause an out of memory error, so can't happen during normal useage @@ -171,7 +171,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor if (progress == 0.0f || progress >= (subpoints - 2) / (float)(subpoints - 1)) continue; - double theta = circularArcProperties.ThetaStart + circularArcProperties.Direction * progress * circularArcProperties.ThetaRange; + double theta = circularArcProperties.ThetaStart + (circularArcProperties.Direction * progress * circularArcProperties.ThetaRange); Vector2 vector = new Vector2((float)Math.Cos(theta), (float)Math.Sin(theta)) * circularArcProperties.Radius; Assert.True(Precision.AlmostEquals(circularArcProperties.Centre + vector, path.PositionAt(progress), 0.01f), From 6c4def1c097298abbda31be5338ba791cd12dc1d Mon Sep 17 00:00:00 2001 From: Aurelian Date: Mon, 27 May 2024 20:32:18 +0200 Subject: [PATCH 082/139] Added check for infinite subpoints for PerfectCurve --- osu.Game/Rulesets/Objects/SliderPath.cs | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/osu.Game/Rulesets/Objects/SliderPath.cs b/osu.Game/Rulesets/Objects/SliderPath.cs index eca14269fe..aa2570c336 100644 --- a/osu.Game/Rulesets/Objects/SliderPath.cs +++ b/osu.Game/Rulesets/Objects/SliderPath.cs @@ -330,11 +330,18 @@ namespace osu.Game.Rulesets.Objects if (subControlPoints.Length != 3) break; - //If a curve's theta range almost equals zero, the radius needed to have more than a - //floating point error difference is very large and results in a nearly straight path. - //Calculate it via a bezier aproximation instead. - //0.0005 corresponds with a radius of 8000 to have a more than 0.001 shift in the X value - if (Math.Abs(new CircularArcProperties(subControlPoints).ThetaRange) <= 0.0005d) + CircularArcProperties circularArcProperties = new CircularArcProperties(subControlPoints); + + //if false, we'll end up breaking anyways when calculating subPath + if (!circularArcProperties.IsValid) + break; + + //Coppied from PathApproximator.CircularArcToPiecewiseLinear + int subPoints = (2f * circularArcProperties.Radius <= 0.1f) ? 2 : Math.Max(2, (int)Math.Ceiling(circularArcProperties.ThetaRange / (2.0 * Math.Acos(1f - (0.1f / circularArcProperties.Radius))))); + + //if the amount of subpoints is int.MaxValue, causes an out of memory issue, so we default to bezier + //this only occurs for very large radii, so the result should be essentially a straight line anyways + if (subPoints == int.MaxValue) break; List subPath = PathApproximator.CircularArcToPiecewiseLinear(subControlPoints); From 96af0e1ec3c6c84ecd28178b5ae1b8ff6f4d3b43 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 28 May 2024 13:07:11 +0200 Subject: [PATCH 083/139] Add failing test case for catch conversion Test is an abridged / cropped version of https://osu.ppy.sh/beatmapsets/971028#fruits/2062131 to demonstrate the specific failure case (unfortunately can't use the whole beatmap due to other conversion failures). --- .../CatchBeatmapConversionTest.cs | 1 + ...tiplier-precision-expected-conversion.json | 1 + .../high-speed-multiplier-precision.osu | 238 ++++++++++++++++++ 3 files changed, 240 insertions(+) create mode 100644 osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/high-speed-multiplier-precision-expected-conversion.json create mode 100644 osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/high-speed-multiplier-precision.osu diff --git a/osu.Game.Rulesets.Catch.Tests/CatchBeatmapConversionTest.cs b/osu.Game.Rulesets.Catch.Tests/CatchBeatmapConversionTest.cs index 81e0675aaa..f4c36d5188 100644 --- a/osu.Game.Rulesets.Catch.Tests/CatchBeatmapConversionTest.cs +++ b/osu.Game.Rulesets.Catch.Tests/CatchBeatmapConversionTest.cs @@ -54,6 +54,7 @@ namespace osu.Game.Rulesets.Catch.Tests [TestCase("3949367", new[] { typeof(CatchModDoubleTime), typeof(CatchModEasy) })] [TestCase("112643")] [TestCase("1041052", new[] { typeof(CatchModHardRock) })] + [TestCase("high-speed-multiplier-precision")] public new void Test(string name, params Type[] mods) => base.Test(name, mods); protected override IEnumerable CreateConvertValue(HitObject hitObject) diff --git a/osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/high-speed-multiplier-precision-expected-conversion.json b/osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/high-speed-multiplier-precision-expected-conversion.json new file mode 100644 index 0000000000..a562074fe9 --- /dev/null +++ b/osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/high-speed-multiplier-precision-expected-conversion.json @@ -0,0 +1 @@ +{"Mappings":[{"StartTime":265568.0,"Objects":[{"StartTime":265568.0,"Position":486.0,"HyperDash":false},{"StartTime":265658.0,"Position":465.1873,"HyperDash":false},{"StartTime":265749.0,"Position":463.208435,"HyperDash":false},{"StartTime":265840.0,"Position":465.146484,"HyperDash":false},{"StartTime":265967.0,"Position":459.5862,"HyperDash":false}]}]} \ No newline at end of file diff --git a/osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/high-speed-multiplier-precision.osu b/osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/high-speed-multiplier-precision.osu new file mode 100644 index 0000000000..ff641d9b0a --- /dev/null +++ b/osu.Game.Rulesets.Catch.Tests/Resources/Testing/Beatmaps/high-speed-multiplier-precision.osu @@ -0,0 +1,238 @@ +osu file format v14 + +[General] +AudioFilename: audio.mp3 +AudioLeadIn: 0 +PreviewTime: 226943 +Countdown: 0 +SampleSet: Soft +StackLeniency: 0.7 +Mode: 2 +LetterboxInBreaks: 0 +WidescreenStoryboard: 1 + +[Editor] +Bookmarks: 85568,86768,90968,265568 +DistanceSpacing: 0.9 +BeatDivisor: 12 +GridSize: 16 +TimelineZoom: 1 + +[Metadata] +Title:Snow +TitleUnicode:Snow +Artist:Ricky Montgomery +ArtistUnicode:Ricky Montgomery +Creator:Crowley +Version:Bury Me Six Feet in Snow +Source: +Tags:indie the honeysticks alternative english +BeatmapID:2062131 +BeatmapSetID:971028 + +[Difficulty] +HPDrainRate:6 +CircleSize:4.2 +OverallDifficulty:8.3 +ApproachRate:8.3 +SliderMultiplier:3.59999990463257 +SliderTickRate:1 + +[Events] +//Background and Video events +0,0,"me.jpg",0,0 +//Break Periods +//Storyboard Layer 0 (Background) +//Storyboard Layer 1 (Fail) +//Storyboard Layer 2 (Pass) +//Storyboard Layer 3 (Foreground) +//Storyboard Layer 4 (Overlay) +//Storyboard Sound Samples + +[TimingPoints] +368,1200,2,2,1,30,1,0 +368,-66.6666666666667,2,2,1,30,0,0 +29168,-58.8235294117647,2,2,1,40,0,0 +30368,-58.8235294117647,2,2,2,40,0,0 +30568,-58.8235294117647,2,2,1,40,0,0 +31368,-58.8235294117647,2,2,2,40,0,0 +31568,-58.8235294117647,2,2,1,40,0,0 +32768,-58.8235294117647,2,2,2,40,0,0 +33568,-58.8235294117647,2,2,2,40,0,0 +33968,-58.8235294117647,2,2,1,40,0,0 +35168,-58.8235294117647,2,2,2,40,0,0 +35968,-58.8235294117647,2,2,1,40,0,0 +36168,-58.8235294117647,2,2,2,40,0,0 +36368,-58.8235294117647,2,2,1,40,0,0 +37568,-58.8235294117647,2,2,2,40,0,0 +37968,-58.8235294117647,2,2,1,40,0,0 +38368,-58.8235294117647,2,2,2,40,0,0 +38768,-58.8235294117647,2,2,1,40,0,0 +39968,-58.8235294117647,2,2,2,40,0,0 +40168,-58.8235294117647,2,2,1,40,0,0 +40968,-58.8235294117647,2,2,2,40,0,0 +41168,-58.8235294117647,2,2,1,40,0,0 +42368,-58.8235294117647,2,2,2,40,0,0 +43168,-58.8235294117647,2,2,2,40,0,0 +43568,-58.8235294117647,2,2,1,40,0,0 +44768,-58.8235294117647,2,2,2,40,0,0 +45768,-58.8235294117647,2,2,2,40,0,0 +45968,-58.8235294117647,2,2,1,50,0,0 +47168,-58.8235294117647,2,2,2,50,0,0 +48368,-62.5,2,2,1,50,0,0 +67568,-58.8235294117647,2,2,1,70,0,1 +84668,-58.8235294117647,2,2,1,5,0,1 +84768,-58.8235294117647,2,2,1,70,0,1 +85068,-58.8235294117647,2,2,1,5,0,1 +85168,-58.8235294117647,2,2,1,70,0,1 +85468,-58.8235294117647,2,2,1,5,0,1 +85568,-58.8235294117647,2,2,1,70,0,1 +86768,-58.8235294117647,2,2,1,30,0,0 +91168,-58.8235294117647,2,2,1,50,0,0 +91568,1200,2,2,1,50,1,0 +91568,-58.8235294117647,2,2,1,50,0,1 +91643,-58.8235294117647,2,2,1,50,0,0 +92768,-58.8235294117647,2,2,2,50,0,0 +92968,-58.8235294117647,2,2,1,50,0,0 +95168,-58.8235294117647,2,2,2,50,0,0 +95368,-58.8235294117647,2,2,1,50,0,0 +97568,-58.8235294117647,2,2,2,50,0,0 +97768,-58.8235294117647,2,2,1,50,0,0 +99968,-58.8235294117647,2,2,2,50,0,0 +100168,-58.8235294117647,2,2,1,50,0,0 +100768,-58.8235294117647,2,2,2,50,0,0 +101168,-58.8235294117647,2,2,1,50,0,0 +102368,-58.8235294117647,2,2,2,50,0,0 +102568,-58.8235294117647,2,2,1,50,0,0 +104768,-58.8235294117647,2,2,2,50,0,0 +104968,-58.8235294117647,2,2,1,50,0,0 +107168,-58.8235294117647,2,2,2,50,0,0 +107368,-58.8235294117647,2,2,1,50,0,0 +108968,-58.8235294117647,2,2,2,50,0,0 +109168,-58.8235294117647,2,2,1,50,0,0 +109568,-58.8235294117647,2,2,2,50,0,0 +109968,-58.8235294117647,2,2,1,50,0,0 +110368,-58.8235294117647,2,2,2,50,0,0 +110768,-100,2,2,1,40,0,0 +127568,-62.5,2,2,2,50,0,0 +127968,-62.5,2,2,1,50,0,0 +128168,-62.5,2,2,2,50,0,0 +129968,-58.8235294117647,2,2,1,50,0,0 +131168,-58.8235294117647,2,2,2,50,0,0 +131368,-58.8235294117647,2,2,1,50,0,0 +133568,-58.8235294117647,2,2,2,50,0,0 +133768,-58.8235294117647,2,2,1,50,0,0 +135968,-58.8235294117647,2,2,2,50,0,0 +136168,-58.8235294117647,2,2,1,50,0,0 +138368,-58.8235294117647,2,2,2,50,0,0 +138568,-58.8235294117647,2,2,1,50,0,0 +139168,-58.8235294117647,2,2,2,50,0,0 +139368,-58.8235294117647,2,2,1,50,0,0 +139568,-58.8235294117647,2,2,1,50,0,0 +140768,-58.8235294117647,2,2,2,50,0,0 +140968,-58.8235294117647,2,2,1,50,0,0 +143168,-58.8235294117647,2,2,2,50,0,0 +143368,-58.8235294117647,2,2,1,50,0,0 +145568,-58.8235294117647,2,2,2,50,0,0 +145768,-58.8235294117647,2,2,1,50,0,0 +147368,-58.8235294117647,2,2,2,50,0,0 +147768,-58.8235294117647,2,2,1,50,0,0 +147968,-58.8235294117647,2,2,1,60,0,0 +148768,-58.8235294117647,2,2,2,60,0,0 +149168,-58.8235294117647,2,2,1,70,0,1 +158268,-58.8235294117647,2,2,2,70,0,1 +158568,-58.8235294117647,2,2,1,70,0,1 +166268,-58.8235294117647,2,2,1,5,0,1 +166368,-58.8235294117647,2,2,1,70,0,1 +166668,-58.8235294117647,2,2,1,5,0,1 +166768,-58.8235294117647,2,2,1,70,0,1 +167068,-58.8235294117647,2,2,1,5,0,1 +167168,-58.8235294117647,2,2,1,70,0,1 +168368,-62.5,2,2,1,50,0,0 +172368,-62.5,2,2,1,50,0,1 +173168,-62.5,2,2,1,50,0,0 +185168,-62.5,2,2,1,60,0,0 +185468,-62.5,2,2,1,5,0,0 +185568,-62.5,2,2,1,60,0,0 +185868,-62.5,2,2,1,5,0,0 +185968,-62.5,2,2,1,60,0,0 +186268,-62.5,2,2,1,5,0,0 +186368,-62.5,2,2,1,60,0,0 +186668,-62.5,2,2,1,5,0,0 +186768,-52.6315789473684,2,2,1,60,0,0 +187068,-62.5,2,2,1,5,0,0 +187168,-62.5,2,2,1,60,0,0 +187468,-62.5,2,2,1,5,0,0 +187568,-62.5,2,2,1,20,0,0 +187768,-62.5,2,2,1,24,0,0 +187968,-62.5,2,2,1,28,0,0 +188168,-62.5,2,2,1,32,0,0 +188368,-62.5,2,2,1,36,0,0 +188568,-62.5,2,2,1,40,0,0 +188768,1200,2,2,1,50,1,1 +188768,-58.8235294117647,2,2,1,50,0,1 +188843,-58.8235294117647,2,2,1,50,0,0 +189968,-58.8235294117647,2,2,2,50,0,0 +190168,-58.8235294117647,2,2,1,50,0,0 +192368,-58.8235294117647,2,2,2,50,0,0 +192568,-58.8235294117647,2,2,1,50,0,0 +194768,-58.8235294117647,2,2,2,50,0,0 +194968,-58.8235294117647,2,2,1,50,0,0 +196568,-58.8235294117647,2,2,2,50,0,0 +196768,-58.8235294117647,2,2,1,50,0,0 +197168,-58.8235294117647,2,2,2,50,0,0 +197368,-58.8235294117647,2,2,1,50,0,0 +197568,-58.8235294117647,2,2,2,50,0,0 +197968,-58.8235294117647,2,2,1,50,0,0 +198368,-58.8235294117647,2,2,1,50,0,0 +199568,-58.8235294117647,2,2,2,50,0,0 +199768,-58.8235294117647,2,2,1,50,0,0 +201968,-58.8235294117647,2,2,2,50,0,0 +202168,-58.8235294117647,2,2,1,50,0,0 +204368,-58.8235294117647,2,2,2,50,0,0 +204568,-58.8235294117647,2,2,1,50,0,0 +206768,-58.8235294117647,2,2,1,60,0,0 +207168,-58.8235294117647,2,2,2,60,0,0 +207968,-58.8235294117647,2,2,1,70,0,1 +216968,-58.8235294117647,2,2,2,70,0,1 +217168,-58.8235294117647,2,2,1,70,0,1 +217368,-58.8235294117647,2,2,2,70,0,1 +217568,-58.8235294117647,2,2,1,70,0,1 +225068,-58.8235294117647,2,2,1,5,0,1 +225168,-58.8235294117647,2,2,1,70,0,1 +225468,-58.8235294117647,2,2,1,5,0,1 +225568,-58.8235294117647,2,2,1,70,0,1 +225868,-58.8235294117647,2,2,1,5,0,1 +225968,-58.8235294117647,2,2,1,70,0,1 +227168,-58.8235294117647,2,2,1,30,0,0 +234368,-58.8235294117647,2,2,1,40,0,0 +236768,-58.8235294117647,2,2,1,70,0,1 +255968,-58.8235294117647,2,2,1,70,0,1 +261168,-58.8235294117647,2,2,1,70,0,1 +263068,-58.8235294117647,2,2,1,70,0,0 +263168,-58.8235294117647,2,2,1,60,0,1 +263243,-58.8235294117647,2,2,1,60,0,0 +264368,-58.8235294117647,2,2,1,60,0,1 +264443,-58.8235294117647,2,2,1,60,0,0 +265568,-444.444444444444,2,2,1,50,0,1 +265643,-444.444444444444,2,2,1,50,0,0 +266768,-444.444444444444,2,2,1,40,0,0 +267968,-444.444444444444,2,2,1,30,0,0 +269168,-444.444444444444,2,2,1,20,0,0 +270368,-444.444444444444,2,2,1,10,0,0 +271168,-444.444444444444,2,2,1,9,0,0 +271568,-444.444444444444,2,2,1,8,0,0 +271968,-444.444444444444,2,2,1,7,0,0 +272368,-444.444444444444,2,2,1,6,0,0 +272768,-444.444444444444,2,2,1,5,0,0 +275168,-444.444444444444,2,2,1,5,0,0 + + +[Colours] +Combo1 : 255,128,128 +Combo2 : 72,72,255 +Combo3 : 192,192,192 +Combo4 : 255,136,79 + +[HitObjects] +486,179,265568,6,0,P|461:174|454:174,1,26.999997997284,6|0,1:2|0:0,0:0:0:0: From a3b849375101c5ff4461fb78b92adb9e290af03a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 28 May 2024 13:07:13 +0200 Subject: [PATCH 084/139] Remove rounding of slider velocity multiplier on juice streams Compare: https://github.com/ppy/osu/pull/26616 This came up elsewhere, namely in https://github.com/ppy/osu/pull/28277#issuecomment-2133505958. As it turns out, at least one beatmap among those whose scores had unexpected changes in total score, namely https://osu.ppy.sh/beatmapsets/971028#fruits/2062131, was using slider velocity multipliers that were not a multiple of 0.01 (the specific value used was 0.225x). This meant that due to the rounding applied to `SliderVelocityMultiplierBindable` via `Precision`, the raw value was being incorrectly rounded, resulting in incorrect conversion. The "direct" change that revealed this is most likely https://github.com/ppy/osu-framework/pull/6249, by the virtue of shuffling the `BindableNumber` rounding code around and accidentally changing midpoint rounding semantics in the process. But it was not at fault here, as rounding was just as wrong before that change as after in this specific context. --- osu.Game.Rulesets.Catch/Objects/JuiceStream.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs b/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs index 671291ef0e..dade129038 100644 --- a/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs +++ b/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs @@ -29,7 +29,6 @@ namespace osu.Game.Rulesets.Catch.Objects public BindableNumber SliderVelocityMultiplierBindable { get; } = new BindableDouble(1) { - Precision = 0.01, MinValue = 0.1, MaxValue = 10 }; From c2e7cdfdccf1aaa83c5e9ab78cdf778547580951 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 28 May 2024 21:29:29 +0900 Subject: [PATCH 085/139] Update framework --- osu.Android.props | 2 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index 1f241c6db5..3a20dd2fdb 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -10,7 +10,7 @@ true - + diff --git a/osu.iOS.props b/osu.iOS.props index eba9abd3b8..2f64fcefa5 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -23,6 +23,6 @@ iossimulator-x64 - + From bf0040447cfaefb6f5a56a399391ba18242f2549 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 28 May 2024 15:24:37 +0200 Subject: [PATCH 086/139] Fix legacy mania note body animation not resetting sometimes Hopefully closes https://github.com/ppy/osu/issues/28284. As far as I can tell this is a somewhat difficult one to reproduce because it relies on a specific set of circumstances (at least the reproduction case that I found does). The reset to frame 0 would previously be called explicitly when `isHitting` changed: https://github.com/ppy/osu/blob/182ca145c78432f4b832c8ea407e107dfeaaa8ad/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyBodyPiece.cs#L144 However, it can be the case that `bodyAnimation` is not loaded at the point of this call. This is significant because `SkinnableTextureAnimation` contains this logic: https://github.com/ppy/osu/blob/182ca145c78432f4b832c8ea407e107dfeaaa8ad/osu.Game/Skinning/LegacySkinExtensions.cs#L192-L211 which cannot be moved any earlier (because any earlier the `Clock` may no longer be correct), and also causes the animation to be seeked forward while it is stopped. I can't figure out a decent way to layer this otherwise (by scheduling or whatever), so this commit is just applying the nuclear option of just seeking back to frame 0 on every update frame in which the body piece is not being hit. --- osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyBodyPiece.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyBodyPiece.cs b/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyBodyPiece.cs index a8200e0144..00054f6be2 100644 --- a/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyBodyPiece.cs +++ b/osu.Game.Rulesets.Mania/Skinning/Legacy/LegacyBodyPiece.cs @@ -140,10 +140,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy private void onIsHittingChanged(ValueChangedEvent isHitting) { if (bodySprite is TextureAnimation bodyAnimation) - { - bodyAnimation.GotoFrame(0); bodyAnimation.IsPlaying = isHitting.NewValue; - } if (lightContainer == null) return; @@ -219,6 +216,9 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy { base.Update(); + if (!isHitting.Value) + (bodySprite as TextureAnimation)?.GotoFrame(0); + if (holdNote.Body.HasHoldBreak) missFadeTime.Value = holdNote.Body.Result.TimeAbsolute; From 36453f621513ee8e7ad5971f3bc7c1a39404d700 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Tue, 28 May 2024 15:56:59 +0200 Subject: [PATCH 087/139] Change scale hotkey to Ctrl+T --- osu.Game/Input/Bindings/GlobalActionContainer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Input/Bindings/GlobalActionContainer.cs b/osu.Game/Input/Bindings/GlobalActionContainer.cs index 394cb98089..fd8e6fd6d0 100644 --- a/osu.Game/Input/Bindings/GlobalActionContainer.cs +++ b/osu.Game/Input/Bindings/GlobalActionContainer.cs @@ -142,7 +142,7 @@ namespace osu.Game.Input.Bindings new KeyBinding(new[] { InputKey.Control, InputKey.Shift, InputKey.MouseWheelRight }, GlobalAction.EditorCyclePreviousBeatSnapDivisor), new KeyBinding(new[] { InputKey.Control, InputKey.Shift, InputKey.MouseWheelLeft }, GlobalAction.EditorCycleNextBeatSnapDivisor), new KeyBinding(new[] { InputKey.Control, InputKey.R }, GlobalAction.EditorToggleRotateControl), - new KeyBinding(new[] { InputKey.S }, GlobalAction.EditorToggleScaleControl), + new KeyBinding(new[] { InputKey.Control, InputKey.T }, GlobalAction.EditorToggleScaleControl), }; private static IEnumerable inGameKeyBindings => new[] From a89ba33b475a4f73167f0c81e53eec8f082c16ec Mon Sep 17 00:00:00 2001 From: OliBomby Date: Tue, 28 May 2024 16:14:16 +0200 Subject: [PATCH 088/139] rename CanScaleSelectionOrigin/PlayfieldOrigin to make clear its not the origin being scaled --- .../Edit/OsuSelectionRotationHandler.cs | 4 ++-- osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs | 4 ++-- osu.Game.Rulesets.Osu/Edit/PreciseRotationPopover.cs | 2 +- osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs | 2 +- osu.Game.Rulesets.Osu/Edit/TransformToolboxGroup.cs | 10 +++++----- .../Visual/Editing/TestSceneComposeSelectBox.cs | 2 +- .../SkinEditor/SkinSelectionRotationHandler.cs | 2 +- .../Screens/Edit/Compose/Components/SelectionBox.cs | 2 +- .../Compose/Components/SelectionRotationHandler.cs | 4 ++-- .../Edit/Compose/Components/SelectionScaleHandler.cs | 4 ++-- 10 files changed, 18 insertions(+), 18 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionRotationHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionRotationHandler.cs index d48bc6a90b..b581e3fdea 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionRotationHandler.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionRotationHandler.cs @@ -41,8 +41,8 @@ namespace osu.Game.Rulesets.Osu.Edit private void updateState() { var quad = GeometryUtils.GetSurroundingQuad(selectedMovableObjects); - CanRotateSelectionOrigin.Value = quad.Width > 0 || quad.Height > 0; - CanRotatePlayfieldOrigin.Value = selectedMovableObjects.Any(); + CanRotateFromSelectionOrigin.Value = quad.Width > 0 || quad.Height > 0; + CanRotateFromPlayfieldOrigin.Value = selectedMovableObjects.Any(); } private OsuHitObject[]? objectsInRotation; diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs index e45494977f..a9cbc1b8f1 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs @@ -53,8 +53,8 @@ namespace osu.Game.Rulesets.Osu.Edit CanScaleX.Value = quad.Width > 0; CanScaleY.Value = quad.Height > 0; CanScaleDiagonally.Value = CanScaleX.Value && CanScaleY.Value; - CanScaleSelectionOrigin.Value = CanScaleX.Value || CanScaleY.Value; - CanScalePlayfieldOrigin.Value = selectedMovableObjects.Any(); + CanScaleFromSelectionOrigin.Value = CanScaleX.Value || CanScaleY.Value; + CanScaleFromPlayfieldOrigin.Value = selectedMovableObjects.Any(); } private Dictionary? objectsInScale; diff --git a/osu.Game.Rulesets.Osu/Edit/PreciseRotationPopover.cs b/osu.Game.Rulesets.Osu/Edit/PreciseRotationPopover.cs index 812d622ae5..ea6b28b215 100644 --- a/osu.Game.Rulesets.Osu/Edit/PreciseRotationPopover.cs +++ b/osu.Game.Rulesets.Osu/Edit/PreciseRotationPopover.cs @@ -86,7 +86,7 @@ namespace osu.Game.Rulesets.Osu.Edit angleInput.Current.BindValueChanged(angle => rotationInfo.Value = rotationInfo.Value with { Degrees = angle.NewValue }); rotationOrigin.Items.First().Select(); - rotationHandler.CanRotateSelectionOrigin.BindValueChanged(e => + rotationHandler.CanRotateFromSelectionOrigin.BindValueChanged(e => { selectionCentreButton.Selected.Disabled = !e.NewValue; }, true); diff --git a/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs b/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs index b76d778b3d..124a79390a 100644 --- a/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs +++ b/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs @@ -118,7 +118,7 @@ namespace osu.Game.Rulesets.Osu.Edit xCheckBox.Current.BindValueChanged(x => setAxis(x.NewValue, yCheckBox.Current.Value)); yCheckBox.Current.BindValueChanged(y => setAxis(xCheckBox.Current.Value, y.NewValue)); - scaleHandler.CanScaleSelectionOrigin.BindValueChanged(e => + scaleHandler.CanScaleFromSelectionOrigin.BindValueChanged(e => { selectionCentreButton.Selected.Disabled = !e.NewValue; }, true); diff --git a/osu.Game.Rulesets.Osu/Edit/TransformToolboxGroup.cs b/osu.Game.Rulesets.Osu/Edit/TransformToolboxGroup.cs index e1f53846dc..67baf7d165 100644 --- a/osu.Game.Rulesets.Osu/Edit/TransformToolboxGroup.cs +++ b/osu.Game.Rulesets.Osu/Edit/TransformToolboxGroup.cs @@ -64,15 +64,15 @@ namespace osu.Game.Rulesets.Osu.Edit base.LoadComplete(); // aggregate two values into canRotate - canRotatePlayfieldOrigin = RotationHandler.CanRotatePlayfieldOrigin.GetBoundCopy(); + canRotatePlayfieldOrigin = RotationHandler.CanRotateFromPlayfieldOrigin.GetBoundCopy(); canRotatePlayfieldOrigin.BindValueChanged(_ => updateCanRotateAggregate()); - canRotateSelectionOrigin = RotationHandler.CanRotateSelectionOrigin.GetBoundCopy(); + canRotateSelectionOrigin = RotationHandler.CanRotateFromSelectionOrigin.GetBoundCopy(); canRotateSelectionOrigin.BindValueChanged(_ => updateCanRotateAggregate()); void updateCanRotateAggregate() { - canRotate.Value = RotationHandler.CanRotatePlayfieldOrigin.Value || RotationHandler.CanRotateSelectionOrigin.Value; + canRotate.Value = RotationHandler.CanRotateFromPlayfieldOrigin.Value || RotationHandler.CanRotateFromSelectionOrigin.Value; } // aggregate three values into canScale @@ -82,12 +82,12 @@ namespace osu.Game.Rulesets.Osu.Edit canScaleY = ScaleHandler.CanScaleY.GetBoundCopy(); canScaleY.BindValueChanged(_ => updateCanScaleAggregate()); - canScalePlayfieldOrigin = ScaleHandler.CanScalePlayfieldOrigin.GetBoundCopy(); + canScalePlayfieldOrigin = ScaleHandler.CanScaleFromPlayfieldOrigin.GetBoundCopy(); canScalePlayfieldOrigin.BindValueChanged(_ => updateCanScaleAggregate()); void updateCanScaleAggregate() { - canScale.Value = ScaleHandler.CanScaleX.Value || ScaleHandler.CanScaleY.Value || ScaleHandler.CanScalePlayfieldOrigin.Value; + canScale.Value = ScaleHandler.CanScaleX.Value || ScaleHandler.CanScaleY.Value || ScaleHandler.CanScaleFromPlayfieldOrigin.Value; } // bindings to `Enabled` on the buttons are decoupled on purpose diff --git a/osu.Game.Tests/Visual/Editing/TestSceneComposeSelectBox.cs b/osu.Game.Tests/Visual/Editing/TestSceneComposeSelectBox.cs index 6bd6c4a8c4..d12f7ebde9 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneComposeSelectBox.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneComposeSelectBox.cs @@ -69,7 +69,7 @@ namespace osu.Game.Tests.Visual.Editing { this.getTargetContainer = getTargetContainer; - CanRotateSelectionOrigin.Value = true; + CanRotateFromSelectionOrigin.Value = true; } [CanBeNull] diff --git a/osu.Game/Overlays/SkinEditor/SkinSelectionRotationHandler.cs b/osu.Game/Overlays/SkinEditor/SkinSelectionRotationHandler.cs index 7ecf116b68..3a3eb9457b 100644 --- a/osu.Game/Overlays/SkinEditor/SkinSelectionRotationHandler.cs +++ b/osu.Game/Overlays/SkinEditor/SkinSelectionRotationHandler.cs @@ -41,7 +41,7 @@ namespace osu.Game.Overlays.SkinEditor private void updateState() { - CanRotateSelectionOrigin.Value = selectedItems.Count > 0; + CanRotateFromSelectionOrigin.Value = selectedItems.Count > 0; } private Drawable[]? objectsInRotation; diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs index 31a5c30fff..9f709f8c64 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs @@ -127,7 +127,7 @@ namespace osu.Game.Screens.Edit.Compose.Components private void load() { if (rotationHandler != null) - canRotate.BindTo(rotationHandler.CanRotateSelectionOrigin); + canRotate.BindTo(rotationHandler.CanRotateFromSelectionOrigin); if (scaleHandler != null) { diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionRotationHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionRotationHandler.cs index 459e4b0c41..8c35dc07b7 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionRotationHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionRotationHandler.cs @@ -15,12 +15,12 @@ namespace osu.Game.Screens.Edit.Compose.Components /// /// Whether rotation anchored by the selection origin can currently be performed. /// - public Bindable CanRotateSelectionOrigin { get; private set; } = new BindableBool(); + public Bindable CanRotateFromSelectionOrigin { get; private set; } = new BindableBool(); /// /// Whether rotation anchored by the center of the playfield can currently be performed. /// - public Bindable CanRotatePlayfieldOrigin { get; private set; } = new BindableBool(); + public Bindable CanRotateFromPlayfieldOrigin { get; private set; } = new BindableBool(); /// /// Performs a single, instant, atomic rotation operation. diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionScaleHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionScaleHandler.cs index 495cce7ad6..b72c3406f1 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionScaleHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionScaleHandler.cs @@ -35,12 +35,12 @@ namespace osu.Game.Screens.Edit.Compose.Components /// /// Whether scaling anchored by the selection origin can currently be performed. /// - public Bindable CanScaleSelectionOrigin { get; private set; } = new BindableBool(); + public Bindable CanScaleFromSelectionOrigin { get; private set; } = new BindableBool(); /// /// Whether scaling anchored by the center of the playfield can currently be performed. /// - public Bindable CanScalePlayfieldOrigin { get; private set; } = new BindableBool(); + public Bindable CanScaleFromPlayfieldOrigin { get; private set; } = new BindableBool(); public Quad? OriginalSurroundingQuad { get; protected set; } From 8eb23f8a604818b6ce94dc0810a551c2a5455a4d Mon Sep 17 00:00:00 2001 From: OliBomby Date: Tue, 28 May 2024 16:19:57 +0200 Subject: [PATCH 089/139] remove redundant CanScaleFromSelectionOrigin --- .../Edit/OsuSelectionScaleHandler.cs | 1 - .../Edit/PreciseScalePopover.cs | 16 ++++++++++++---- .../Compose/Components/SelectionScaleHandler.cs | 5 ----- 3 files changed, 12 insertions(+), 10 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs index a9cbc1b8f1..f120c8bd75 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs @@ -53,7 +53,6 @@ namespace osu.Game.Rulesets.Osu.Edit CanScaleX.Value = quad.Width > 0; CanScaleY.Value = quad.Height > 0; CanScaleDiagonally.Value = CanScaleX.Value && CanScaleY.Value; - CanScaleFromSelectionOrigin.Value = CanScaleX.Value || CanScaleY.Value; CanScaleFromPlayfieldOrigin.Value = selectedMovableObjects.Any(); } diff --git a/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs b/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs index 124a79390a..dca262cf5a 100644 --- a/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs +++ b/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs @@ -32,6 +32,9 @@ namespace osu.Game.Rulesets.Osu.Edit private OsuCheckbox xCheckBox = null!; private OsuCheckbox yCheckBox = null!; + private Bindable canScaleX = null!; + private Bindable canScaleY = null!; + public PreciseScalePopover(SelectionScaleHandler scaleHandler) { this.scaleHandler = scaleHandler; @@ -118,10 +121,15 @@ namespace osu.Game.Rulesets.Osu.Edit xCheckBox.Current.BindValueChanged(x => setAxis(x.NewValue, yCheckBox.Current.Value)); yCheckBox.Current.BindValueChanged(y => setAxis(xCheckBox.Current.Value, y.NewValue)); - scaleHandler.CanScaleFromSelectionOrigin.BindValueChanged(e => - { - selectionCentreButton.Selected.Disabled = !e.NewValue; - }, true); + // aggregate two values into canScaleFromSelectionCentre + canScaleX = scaleHandler.CanScaleX.GetBoundCopy(); + canScaleX.BindValueChanged(_ => updateCanScaleFromSelectionCentre()); + + canScaleY = scaleHandler.CanScaleY.GetBoundCopy(); + canScaleY.BindValueChanged(_ => updateCanScaleFromSelectionCentre(), true); + + void updateCanScaleFromSelectionCentre() => + selectionCentreButton.Selected.Disabled = !(scaleHandler.CanScaleY.Value || scaleHandler.CanScaleFromPlayfieldOrigin.Value); scaleInfo.BindValueChanged(scale => { diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionScaleHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionScaleHandler.cs index b72c3406f1..2c8b413560 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionScaleHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionScaleHandler.cs @@ -32,11 +32,6 @@ namespace osu.Game.Screens.Edit.Compose.Components /// public Bindable CanScaleDiagonally { get; private set; } = new BindableBool(); - /// - /// Whether scaling anchored by the selection origin can currently be performed. - /// - public Bindable CanScaleFromSelectionOrigin { get; private set; } = new BindableBool(); - /// /// Whether scaling anchored by the center of the playfield can currently be performed. /// From 7cdc755c1614ba666d63d3eba5d8062a9313e1a4 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Tue, 28 May 2024 16:57:24 +0200 Subject: [PATCH 090/139] Bind axis checkbox disabled state to CanScaleX/Y --- .../Edit/PreciseScalePopover.cs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs b/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs index dca262cf5a..ac6e2849c9 100644 --- a/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs +++ b/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs @@ -35,6 +35,8 @@ namespace osu.Game.Rulesets.Osu.Edit private Bindable canScaleX = null!; private Bindable canScaleY = null!; + private bool scaleInProgress; + public PreciseScalePopover(SelectionScaleHandler scaleHandler) { this.scaleHandler = scaleHandler; @@ -124,15 +126,26 @@ namespace osu.Game.Rulesets.Osu.Edit // aggregate two values into canScaleFromSelectionCentre canScaleX = scaleHandler.CanScaleX.GetBoundCopy(); canScaleX.BindValueChanged(_ => updateCanScaleFromSelectionCentre()); + canScaleX.BindValueChanged(e => updateAxisCheckBoxEnabled(e.NewValue, xCheckBox.Current), true); canScaleY = scaleHandler.CanScaleY.GetBoundCopy(); canScaleY.BindValueChanged(_ => updateCanScaleFromSelectionCentre(), true); + canScaleY.BindValueChanged(e => updateAxisCheckBoxEnabled(e.NewValue, yCheckBox.Current), true); void updateCanScaleFromSelectionCentre() => selectionCentreButton.Selected.Disabled = !(scaleHandler.CanScaleY.Value || scaleHandler.CanScaleFromPlayfieldOrigin.Value); + void updateAxisCheckBoxEnabled(bool enabled, Bindable current) + { + current.Disabled = false; // enable the bindable to allow setting the value + current.Value = enabled; + current.Disabled = !enabled; + } + scaleInfo.BindValueChanged(scale => { + if (!scaleInProgress) return; + var newScale = new Vector2(scale.NewValue.XAxis ? scale.NewValue.Scale : 1, scale.NewValue.YAxis ? scale.NewValue.Scale : 1); scaleHandler.Update(newScale, getOriginPosition(scale.NewValue)); }); @@ -172,6 +185,7 @@ namespace osu.Game.Rulesets.Osu.Edit { base.PopIn(); scaleHandler.Begin(); + scaleInProgress = true; updateMaxScale(); } @@ -180,7 +194,10 @@ namespace osu.Game.Rulesets.Osu.Edit base.PopOut(); if (IsLoaded) + { scaleHandler.Commit(); + scaleInProgress = false; + } } } From d143a697d2efbd7d57fc7d7ff7d3d4e3cfc13a7d Mon Sep 17 00:00:00 2001 From: OliBomby Date: Tue, 28 May 2024 17:12:16 +0200 Subject: [PATCH 091/139] refactor CanScaleFromPlayfieldOrigin and GetClampedScale to derived class --- osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs | 2 +- .../Edit/OsuSelectionScaleHandler.cs | 13 ++++++++++++- osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs | 5 ++--- osu.Game.Rulesets.Osu/Edit/TransformToolboxGroup.cs | 2 +- .../Compose/Components/SelectionScaleHandler.cs | 13 ------------- 5 files changed, 16 insertions(+), 19 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs index 6f3ed9730e..cc1d1fe89f 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs @@ -104,7 +104,7 @@ namespace osu.Game.Rulesets.Osu.Edit new TransformToolboxGroup { RotationHandler = BlueprintContainer.SelectionHandler.RotationHandler, - ScaleHandler = BlueprintContainer.SelectionHandler.ScaleHandler, + ScaleHandler = (OsuSelectionScaleHandler)BlueprintContainer.SelectionHandler.ScaleHandler, }, FreehandlSliderToolboxGroup } diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs index f120c8bd75..32057bd7fe 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs @@ -24,6 +24,11 @@ namespace osu.Game.Rulesets.Osu.Edit { public partial class OsuSelectionScaleHandler : SelectionScaleHandler { + /// + /// Whether scaling anchored by the center of the playfield can currently be performed. + /// + public Bindable CanScaleFromPlayfieldOrigin { get; private set; } = new BindableBool(); + [Resolved] private IEditorChangeHandler? changeHandler { get; set; } @@ -156,7 +161,13 @@ namespace osu.Game.Rulesets.Osu.Edit return (xInBounds, yInBounds); } - public override Vector2 GetClampedScale(Vector2 scale, Vector2? origin = null) + /// + /// Clamp scale for multi-object-scaling where selection does not exceed playfield bounds or flip. + /// + /// The origin from which the scale operation is performed + /// The scale to be clamped + /// The clamped scale vector + public Vector2 GetClampedScale(Vector2 scale, Vector2? origin = null) { //todo: this is not always correct for selections involving sliders. This approximation assumes each point is scaled independently, but sliderends move with the sliderhead. if (objectsInScale == null) diff --git a/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs b/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs index ac6e2849c9..f14e166d3b 100644 --- a/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs +++ b/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs @@ -12,14 +12,13 @@ using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterfaceV2; using osu.Game.Rulesets.Osu.UI; using osu.Game.Screens.Edit.Components.RadioButtons; -using osu.Game.Screens.Edit.Compose.Components; using osuTK; namespace osu.Game.Rulesets.Osu.Edit { public partial class PreciseScalePopover : OsuPopover { - private readonly SelectionScaleHandler scaleHandler; + private readonly OsuSelectionScaleHandler scaleHandler; private readonly Bindable scaleInfo = new Bindable(new PreciseScaleInfo(1, ScaleOrigin.PlayfieldCentre, true, true)); @@ -37,7 +36,7 @@ namespace osu.Game.Rulesets.Osu.Edit private bool scaleInProgress; - public PreciseScalePopover(SelectionScaleHandler scaleHandler) + public PreciseScalePopover(OsuSelectionScaleHandler scaleHandler) { this.scaleHandler = scaleHandler; diff --git a/osu.Game.Rulesets.Osu/Edit/TransformToolboxGroup.cs b/osu.Game.Rulesets.Osu/Edit/TransformToolboxGroup.cs index 67baf7d165..e70be8d93c 100644 --- a/osu.Game.Rulesets.Osu/Edit/TransformToolboxGroup.cs +++ b/osu.Game.Rulesets.Osu/Edit/TransformToolboxGroup.cs @@ -32,7 +32,7 @@ namespace osu.Game.Rulesets.Osu.Edit private Bindable canScalePlayfieldOrigin = null!; public SelectionRotationHandler RotationHandler { get; init; } = null!; - public SelectionScaleHandler ScaleHandler { get; init; } = null!; + public OsuSelectionScaleHandler ScaleHandler { get; init; } = null!; public TransformToolboxGroup() : base("transform") diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionScaleHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionScaleHandler.cs index 2c8b413560..a96f627e56 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionScaleHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionScaleHandler.cs @@ -32,21 +32,8 @@ namespace osu.Game.Screens.Edit.Compose.Components /// public Bindable CanScaleDiagonally { get; private set; } = new BindableBool(); - /// - /// Whether scaling anchored by the center of the playfield can currently be performed. - /// - public Bindable CanScaleFromPlayfieldOrigin { get; private set; } = new BindableBool(); - public Quad? OriginalSurroundingQuad { get; protected set; } - /// - /// Clamp scale where selection does not exceed playfield bounds or flip. - /// - /// The origin from which the scale operation is performed - /// The scale to be clamped - /// The clamped scale vector - public virtual Vector2 GetClampedScale(Vector2 scale, Vector2? origin = null) => scale; - /// /// Performs a single, instant, atomic scale operation. /// From 9548585b15fe4154e7e7da80d21da9779486e898 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Tue, 28 May 2024 17:24:31 +0200 Subject: [PATCH 092/139] fix axis checkboxes being disabled in playfield origin scale --- .../Edit/PreciseScalePopover.cs | 35 +++++++++++++------ 1 file changed, 25 insertions(+), 10 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs b/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs index f14e166d3b..d45c4020b9 100644 --- a/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs +++ b/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs @@ -125,21 +125,14 @@ namespace osu.Game.Rulesets.Osu.Edit // aggregate two values into canScaleFromSelectionCentre canScaleX = scaleHandler.CanScaleX.GetBoundCopy(); canScaleX.BindValueChanged(_ => updateCanScaleFromSelectionCentre()); - canScaleX.BindValueChanged(e => updateAxisCheckBoxEnabled(e.NewValue, xCheckBox.Current), true); + canScaleX.BindValueChanged(e => updateAxisCheckBoxesEnabled()); canScaleY = scaleHandler.CanScaleY.GetBoundCopy(); canScaleY.BindValueChanged(_ => updateCanScaleFromSelectionCentre(), true); - canScaleY.BindValueChanged(e => updateAxisCheckBoxEnabled(e.NewValue, yCheckBox.Current), true); + canScaleY.BindValueChanged(e => updateAxisCheckBoxesEnabled(), true); void updateCanScaleFromSelectionCentre() => - selectionCentreButton.Selected.Disabled = !(scaleHandler.CanScaleY.Value || scaleHandler.CanScaleFromPlayfieldOrigin.Value); - - void updateAxisCheckBoxEnabled(bool enabled, Bindable current) - { - current.Disabled = false; // enable the bindable to allow setting the value - current.Value = enabled; - current.Disabled = !enabled; - } + selectionCentreButton.Selected.Disabled = !(scaleHandler.CanScaleX.Value || scaleHandler.CanScaleY.Value); scaleInfo.BindValueChanged(scale => { @@ -150,6 +143,27 @@ namespace osu.Game.Rulesets.Osu.Edit }); } + private void updateAxisCheckBoxesEnabled() + { + if (scaleInfo.Value.Origin == ScaleOrigin.PlayfieldCentre) + { + setBindableEnabled(true, xCheckBox.Current); + setBindableEnabled(true, yCheckBox.Current); + } + else + { + setBindableEnabled(canScaleX.Value, xCheckBox.Current); + setBindableEnabled(canScaleY.Value, yCheckBox.Current); + } + } + + private void setBindableEnabled(bool enabled, Bindable current) + { + current.Disabled = false; // enable the bindable to allow setting the value + current.Value = enabled; + current.Disabled = !enabled; + } + private void updateMaxScale() { if (!scaleHandler.OriginalSurroundingQuad.HasValue) @@ -170,6 +184,7 @@ namespace osu.Game.Rulesets.Osu.Edit { scaleInfo.Value = scaleInfo.Value with { Origin = origin }; updateMaxScale(); + updateAxisCheckBoxesEnabled(); } private Vector2? getOriginPosition(PreciseScaleInfo scale) => scale.Origin == ScaleOrigin.PlayfieldCentre ? OsuPlayfield.BASE_SIZE / 2 : null; From 9a18ba2699883a0278adba889b2141b3e21f044b Mon Sep 17 00:00:00 2001 From: OliBomby Date: Tue, 28 May 2024 18:27:01 +0200 Subject: [PATCH 093/139] disable playfield centre origin when scaling slider and simplify logic --- .../Edit/OsuSelectionScaleHandler.cs | 6 +++ .../Edit/PreciseScalePopover.cs | 42 +++++++------------ 2 files changed, 21 insertions(+), 27 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs index 32057bd7fe..9d1c3bc78f 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs @@ -29,6 +29,11 @@ namespace osu.Game.Rulesets.Osu.Edit /// public Bindable CanScaleFromPlayfieldOrigin { get; private set; } = new BindableBool(); + /// + /// Whether a single slider is currently selected, which results in a different scaling behaviour. + /// + public Bindable IsScalingSlider { get; private set; } = new BindableBool(); + [Resolved] private IEditorChangeHandler? changeHandler { get; set; } @@ -59,6 +64,7 @@ namespace osu.Game.Rulesets.Osu.Edit CanScaleY.Value = quad.Height > 0; CanScaleDiagonally.Value = CanScaleX.Value && CanScaleY.Value; CanScaleFromPlayfieldOrigin.Value = selectedMovableObjects.Any(); + IsScalingSlider.Value = selectedMovableObjects.Count() == 1 && selectedMovableObjects.First() is Slider; } private Dictionary? objectsInScale; diff --git a/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs b/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs index d45c4020b9..b792baf428 100644 --- a/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs +++ b/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs @@ -26,16 +26,12 @@ namespace osu.Game.Rulesets.Osu.Edit private BindableNumber scaleInputBindable = null!; private EditorRadioButtonCollection scaleOrigin = null!; + private RadioButton playfieldCentreButton = null!; private RadioButton selectionCentreButton = null!; private OsuCheckbox xCheckBox = null!; private OsuCheckbox yCheckBox = null!; - private Bindable canScaleX = null!; - private Bindable canScaleY = null!; - - private bool scaleInProgress; - public PreciseScalePopover(OsuSelectionScaleHandler scaleHandler) { this.scaleHandler = scaleHandler; @@ -70,7 +66,7 @@ namespace osu.Game.Rulesets.Osu.Edit RelativeSizeAxes = Axes.X, Items = new[] { - new RadioButton("Playfield centre", + playfieldCentreButton = new RadioButton("Playfield centre", () => setOrigin(ScaleOrigin.PlayfieldCentre), () => new SpriteIcon { Icon = FontAwesome.Regular.Square }), selectionCentreButton = new RadioButton("Selection centre", @@ -101,6 +97,10 @@ namespace osu.Game.Rulesets.Osu.Edit }, } }; + playfieldCentreButton.Selected.DisabledChanged += isDisabled => + { + playfieldCentreButton.TooltipText = isDisabled ? "Select something other than a single slider to perform playfield-based scaling." : string.Empty; + }; selectionCentreButton.Selected.DisabledChanged += isDisabled => { selectionCentreButton.TooltipText = isDisabled ? "Select more than one object to perform selection-based scaling." : string.Empty; @@ -117,27 +117,20 @@ namespace osu.Game.Rulesets.Osu.Edit scaleInput.SelectAll(); }); scaleInput.Current.BindValueChanged(scale => scaleInfo.Value = scaleInfo.Value with { Scale = scale.NewValue }); - scaleOrigin.Items.First().Select(); xCheckBox.Current.BindValueChanged(x => setAxis(x.NewValue, yCheckBox.Current.Value)); yCheckBox.Current.BindValueChanged(y => setAxis(xCheckBox.Current.Value, y.NewValue)); - // aggregate two values into canScaleFromSelectionCentre - canScaleX = scaleHandler.CanScaleX.GetBoundCopy(); - canScaleX.BindValueChanged(_ => updateCanScaleFromSelectionCentre()); - canScaleX.BindValueChanged(e => updateAxisCheckBoxesEnabled()); + selectionCentreButton.Selected.Disabled = !(scaleHandler.CanScaleX.Value || scaleHandler.CanScaleY.Value); + playfieldCentreButton.Selected.Disabled = scaleHandler.IsScalingSlider.Value && !selectionCentreButton.Selected.Disabled; - canScaleY = scaleHandler.CanScaleY.GetBoundCopy(); - canScaleY.BindValueChanged(_ => updateCanScaleFromSelectionCentre(), true); - canScaleY.BindValueChanged(e => updateAxisCheckBoxesEnabled(), true); - - void updateCanScaleFromSelectionCentre() => - selectionCentreButton.Selected.Disabled = !(scaleHandler.CanScaleX.Value || scaleHandler.CanScaleY.Value); + if (playfieldCentreButton.Selected.Disabled) + scaleOrigin.Items.Last().Select(); + else + scaleOrigin.Items.First().Select(); scaleInfo.BindValueChanged(scale => { - if (!scaleInProgress) return; - var newScale = new Vector2(scale.NewValue.XAxis ? scale.NewValue.Scale : 1, scale.NewValue.YAxis ? scale.NewValue.Scale : 1); scaleHandler.Update(newScale, getOriginPosition(scale.NewValue)); }); @@ -152,8 +145,8 @@ namespace osu.Game.Rulesets.Osu.Edit } else { - setBindableEnabled(canScaleX.Value, xCheckBox.Current); - setBindableEnabled(canScaleY.Value, yCheckBox.Current); + setBindableEnabled(scaleHandler.CanScaleX.Value, xCheckBox.Current); + setBindableEnabled(scaleHandler.CanScaleY.Value, yCheckBox.Current); } } @@ -199,7 +192,6 @@ namespace osu.Game.Rulesets.Osu.Edit { base.PopIn(); scaleHandler.Begin(); - scaleInProgress = true; updateMaxScale(); } @@ -207,11 +199,7 @@ namespace osu.Game.Rulesets.Osu.Edit { base.PopOut(); - if (IsLoaded) - { - scaleHandler.Commit(); - scaleInProgress = false; - } + if (IsLoaded) scaleHandler.Commit(); } } From b74f66e3351457ef7233c277c88b18db7e174f2a Mon Sep 17 00:00:00 2001 From: Aurelian Date: Tue, 28 May 2024 19:38:33 +0200 Subject: [PATCH 094/139] SliderBall's rotation updates based on CurvePositionAt --- .../Objects/Drawables/DrawableSliderBall.cs | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderBall.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderBall.cs index 46f0231981..24c0d0fcf0 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderBall.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderBall.cs @@ -10,9 +10,7 @@ using osu.Framework.Graphics.Containers; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Osu.Skinning.Default; -using osu.Game.Screens.Play; using osu.Game.Skinning; -using osuTK; namespace osu.Game.Rulesets.Osu.Objects.Drawables { @@ -63,22 +61,20 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables base.ApplyTransformsAt(time, false); } - private Vector2? lastPosition; - public void UpdateProgress(double completionProgress) { - Position = drawableSlider.HitObject.CurvePositionAt(completionProgress); + Slider slider = drawableSlider.HitObject; + Position = slider.CurvePositionAt(completionProgress); - var diff = lastPosition.HasValue ? lastPosition.Value - Position : Position - drawableSlider.HitObject.CurvePositionAt(completionProgress + 0.01f); - - bool rewinding = (Clock as IGameplayClock)?.IsRewinding == true; + //0.1 / slider.Path.Distance is the additional progress needed to ensure the diff length is 0.1 + var diff = slider.CurvePositionAt(completionProgress) - slider.CurvePositionAt(Math.Min(1, completionProgress + 0.1 / slider.Path.Distance)); // Ensure the value is substantially high enough to allow for Atan2 to get a valid angle. + // Needed for when near completion, or in case of a very short slider. if (diff.LengthFast < 0.01f) return; - ball.Rotation = -90 + (float)(-Math.Atan2(diff.X, diff.Y) * 180 / Math.PI) + (rewinding ? 180 : 0); - lastPosition = Position; + ball.Rotation = -90 + (float)(-Math.Atan2(diff.X, diff.Y) * 180 / Math.PI); } } } From 542809a748072f8438b9289027bafd018a33c5f3 Mon Sep 17 00:00:00 2001 From: Aurelian Date: Wed, 29 May 2024 09:39:46 +0200 Subject: [PATCH 095/139] Reduced subpoints limit to be a more practical value --- osu.Game/Rulesets/Objects/SliderPath.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/osu.Game/Rulesets/Objects/SliderPath.cs b/osu.Game/Rulesets/Objects/SliderPath.cs index aa2570c336..730a2013b0 100644 --- a/osu.Game/Rulesets/Objects/SliderPath.cs +++ b/osu.Game/Rulesets/Objects/SliderPath.cs @@ -339,9 +339,10 @@ namespace osu.Game.Rulesets.Objects //Coppied from PathApproximator.CircularArcToPiecewiseLinear int subPoints = (2f * circularArcProperties.Radius <= 0.1f) ? 2 : Math.Max(2, (int)Math.Ceiling(circularArcProperties.ThetaRange / (2.0 * Math.Acos(1f - (0.1f / circularArcProperties.Radius))))); - //if the amount of subpoints is int.MaxValue, causes an out of memory issue, so we default to bezier - //this only occurs for very large radii, so the result should be essentially a straight line anyways - if (subPoints == int.MaxValue) + //theoretically can be int.MaxValue, but lets set this to a lower value anyways + //1000 requires an arc length of over 20 thousand to surpass this limit, which should be safe. + //See here for calculations https://www.desmos.com/calculator/210bwswkbb + if (subPoints >= 1000) break; List subPath = PathApproximator.CircularArcToPiecewiseLinear(subControlPoints); From 4c881b56331626feb5272e0b33442ec388765acf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 29 May 2024 09:40:29 +0200 Subject: [PATCH 096/139] Use better name if we're renaming --- osu.Game.Rulesets.Osu/Edit/OsuSelectionRotationHandler.cs | 4 ++-- osu.Game.Rulesets.Osu/Edit/PreciseRotationPopover.cs | 2 +- osu.Game.Rulesets.Osu/Edit/TransformToolboxGroup.cs | 6 +++--- osu.Game.Tests/Visual/Editing/TestSceneComposeSelectBox.cs | 2 +- .../Overlays/SkinEditor/SkinSelectionRotationHandler.cs | 2 +- osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs | 2 +- .../Edit/Compose/Components/SelectionRotationHandler.cs | 4 ++-- 7 files changed, 11 insertions(+), 11 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionRotationHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionRotationHandler.cs index b581e3fdea..7624b2f27e 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionRotationHandler.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionRotationHandler.cs @@ -41,8 +41,8 @@ namespace osu.Game.Rulesets.Osu.Edit private void updateState() { var quad = GeometryUtils.GetSurroundingQuad(selectedMovableObjects); - CanRotateFromSelectionOrigin.Value = quad.Width > 0 || quad.Height > 0; - CanRotateFromPlayfieldOrigin.Value = selectedMovableObjects.Any(); + CanRotateAroundSelectionOrigin.Value = quad.Width > 0 || quad.Height > 0; + CanRotateAroundPlayfieldOrigin.Value = selectedMovableObjects.Any(); } private OsuHitObject[]? objectsInRotation; diff --git a/osu.Game.Rulesets.Osu/Edit/PreciseRotationPopover.cs b/osu.Game.Rulesets.Osu/Edit/PreciseRotationPopover.cs index ea6b28b215..3a0d3c4763 100644 --- a/osu.Game.Rulesets.Osu/Edit/PreciseRotationPopover.cs +++ b/osu.Game.Rulesets.Osu/Edit/PreciseRotationPopover.cs @@ -86,7 +86,7 @@ namespace osu.Game.Rulesets.Osu.Edit angleInput.Current.BindValueChanged(angle => rotationInfo.Value = rotationInfo.Value with { Degrees = angle.NewValue }); rotationOrigin.Items.First().Select(); - rotationHandler.CanRotateFromSelectionOrigin.BindValueChanged(e => + rotationHandler.CanRotateAroundSelectionOrigin.BindValueChanged(e => { selectionCentreButton.Selected.Disabled = !e.NewValue; }, true); diff --git a/osu.Game.Rulesets.Osu/Edit/TransformToolboxGroup.cs b/osu.Game.Rulesets.Osu/Edit/TransformToolboxGroup.cs index e70be8d93c..4da1593fb7 100644 --- a/osu.Game.Rulesets.Osu/Edit/TransformToolboxGroup.cs +++ b/osu.Game.Rulesets.Osu/Edit/TransformToolboxGroup.cs @@ -64,15 +64,15 @@ namespace osu.Game.Rulesets.Osu.Edit base.LoadComplete(); // aggregate two values into canRotate - canRotatePlayfieldOrigin = RotationHandler.CanRotateFromPlayfieldOrigin.GetBoundCopy(); + canRotatePlayfieldOrigin = RotationHandler.CanRotateAroundPlayfieldOrigin.GetBoundCopy(); canRotatePlayfieldOrigin.BindValueChanged(_ => updateCanRotateAggregate()); - canRotateSelectionOrigin = RotationHandler.CanRotateFromSelectionOrigin.GetBoundCopy(); + canRotateSelectionOrigin = RotationHandler.CanRotateAroundSelectionOrigin.GetBoundCopy(); canRotateSelectionOrigin.BindValueChanged(_ => updateCanRotateAggregate()); void updateCanRotateAggregate() { - canRotate.Value = RotationHandler.CanRotateFromPlayfieldOrigin.Value || RotationHandler.CanRotateFromSelectionOrigin.Value; + canRotate.Value = RotationHandler.CanRotateAroundPlayfieldOrigin.Value || RotationHandler.CanRotateAroundSelectionOrigin.Value; } // aggregate three values into canScale diff --git a/osu.Game.Tests/Visual/Editing/TestSceneComposeSelectBox.cs b/osu.Game.Tests/Visual/Editing/TestSceneComposeSelectBox.cs index d12f7ebde9..79a808bbd2 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneComposeSelectBox.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneComposeSelectBox.cs @@ -69,7 +69,7 @@ namespace osu.Game.Tests.Visual.Editing { this.getTargetContainer = getTargetContainer; - CanRotateFromSelectionOrigin.Value = true; + CanRotateAroundSelectionOrigin.Value = true; } [CanBeNull] diff --git a/osu.Game/Overlays/SkinEditor/SkinSelectionRotationHandler.cs b/osu.Game/Overlays/SkinEditor/SkinSelectionRotationHandler.cs index 3a3eb9457b..6a118a73a8 100644 --- a/osu.Game/Overlays/SkinEditor/SkinSelectionRotationHandler.cs +++ b/osu.Game/Overlays/SkinEditor/SkinSelectionRotationHandler.cs @@ -41,7 +41,7 @@ namespace osu.Game.Overlays.SkinEditor private void updateState() { - CanRotateFromSelectionOrigin.Value = selectedItems.Count > 0; + CanRotateAroundSelectionOrigin.Value = selectedItems.Count > 0; } private Drawable[]? objectsInRotation; diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs index 9f709f8c64..fec3224fad 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs @@ -127,7 +127,7 @@ namespace osu.Game.Screens.Edit.Compose.Components private void load() { if (rotationHandler != null) - canRotate.BindTo(rotationHandler.CanRotateFromSelectionOrigin); + canRotate.BindTo(rotationHandler.CanRotateAroundSelectionOrigin); if (scaleHandler != null) { diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionRotationHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionRotationHandler.cs index 8c35dc07b7..787716a38c 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionRotationHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionRotationHandler.cs @@ -15,12 +15,12 @@ namespace osu.Game.Screens.Edit.Compose.Components /// /// Whether rotation anchored by the selection origin can currently be performed. /// - public Bindable CanRotateFromSelectionOrigin { get; private set; } = new BindableBool(); + public Bindable CanRotateAroundSelectionOrigin { get; private set; } = new BindableBool(); /// /// Whether rotation anchored by the center of the playfield can currently be performed. /// - public Bindable CanRotateFromPlayfieldOrigin { get; private set; } = new BindableBool(); + public Bindable CanRotateAroundPlayfieldOrigin { get; private set; } = new BindableBool(); /// /// Performs a single, instant, atomic rotation operation. From 4a8273b6ed8defecef61bcf6e77a937372a1de4f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 29 May 2024 09:43:09 +0200 Subject: [PATCH 097/139] Rename another method --- osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs | 4 ++-- osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs index 9d1c3bc78f..de00aa6ad3 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs @@ -104,7 +104,7 @@ namespace osu.Game.Rulesets.Osu.Edit } else { - scale = GetClampedScale(scale, actualOrigin); + scale = ClampScaleToPlayfieldBounds(scale, actualOrigin); foreach (var (ho, originalState) in objectsInScale) { @@ -173,7 +173,7 @@ namespace osu.Game.Rulesets.Osu.Edit /// The origin from which the scale operation is performed /// The scale to be clamped /// The clamped scale vector - public Vector2 GetClampedScale(Vector2 scale, Vector2? origin = null) + public Vector2 ClampScaleToPlayfieldBounds(Vector2 scale, Vector2? origin = null) { //todo: this is not always correct for selections involving sliders. This approximation assumes each point is scaled independently, but sliderends move with the sliderhead. if (objectsInScale == null) diff --git a/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs b/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs index b792baf428..b7202b9310 100644 --- a/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs +++ b/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs @@ -163,7 +163,7 @@ namespace osu.Game.Rulesets.Osu.Edit return; const float max_scale = 10; - var scale = scaleHandler.GetClampedScale(new Vector2(max_scale), getOriginPosition(scaleInfo.Value)); + var scale = scaleHandler.ClampScaleToPlayfieldBounds(new Vector2(max_scale), getOriginPosition(scaleInfo.Value)); if (!scaleInfo.Value.XAxis) scale.X = max_scale; From bd5060965f4a9e0fcbed9fc6044126e66e5d36d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 29 May 2024 09:49:16 +0200 Subject: [PATCH 098/139] Simplify toolbox button enable logic --- .../Edit/TransformToolboxGroup.cs | 38 +++++-------------- 1 file changed, 9 insertions(+), 29 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/TransformToolboxGroup.cs b/osu.Game.Rulesets.Osu/Edit/TransformToolboxGroup.cs index 4da1593fb7..278d38b2d9 100644 --- a/osu.Game.Rulesets.Osu/Edit/TransformToolboxGroup.cs +++ b/osu.Game.Rulesets.Osu/Edit/TransformToolboxGroup.cs @@ -18,8 +18,8 @@ namespace osu.Game.Rulesets.Osu.Edit { public partial class TransformToolboxGroup : EditorToolboxGroup, IKeyBindingHandler { - private readonly Bindable canRotate = new BindableBool(); - private readonly Bindable canScale = new BindableBool(); + private readonly AggregateBindable canRotate = new AggregateBindable((x, y) => x || y); + private readonly AggregateBindable canScale = new AggregateBindable((x, y) => x || y); private EditorToolButton rotateButton = null!; private EditorToolButton scaleButton = null!; @@ -63,37 +63,17 @@ namespace osu.Game.Rulesets.Osu.Edit { base.LoadComplete(); - // aggregate two values into canRotate - canRotatePlayfieldOrigin = RotationHandler.CanRotateAroundPlayfieldOrigin.GetBoundCopy(); - canRotatePlayfieldOrigin.BindValueChanged(_ => updateCanRotateAggregate()); + canRotate.AddSource(RotationHandler.CanRotateAroundPlayfieldOrigin); + canRotate.AddSource(RotationHandler.CanRotateAroundSelectionOrigin); - canRotateSelectionOrigin = RotationHandler.CanRotateAroundSelectionOrigin.GetBoundCopy(); - canRotateSelectionOrigin.BindValueChanged(_ => updateCanRotateAggregate()); - - void updateCanRotateAggregate() - { - canRotate.Value = RotationHandler.CanRotateAroundPlayfieldOrigin.Value || RotationHandler.CanRotateAroundSelectionOrigin.Value; - } - - // aggregate three values into canScale - canScaleX = ScaleHandler.CanScaleX.GetBoundCopy(); - canScaleX.BindValueChanged(_ => updateCanScaleAggregate()); - - canScaleY = ScaleHandler.CanScaleY.GetBoundCopy(); - canScaleY.BindValueChanged(_ => updateCanScaleAggregate()); - - canScalePlayfieldOrigin = ScaleHandler.CanScaleFromPlayfieldOrigin.GetBoundCopy(); - canScalePlayfieldOrigin.BindValueChanged(_ => updateCanScaleAggregate()); - - void updateCanScaleAggregate() - { - canScale.Value = ScaleHandler.CanScaleX.Value || ScaleHandler.CanScaleY.Value || ScaleHandler.CanScaleFromPlayfieldOrigin.Value; - } + canScale.AddSource(ScaleHandler.CanScaleX); + canScale.AddSource(ScaleHandler.CanScaleY); + canScale.AddSource(ScaleHandler.CanScaleFromPlayfieldOrigin); // bindings to `Enabled` on the buttons are decoupled on purpose // due to the weird `OsuButton` behaviour of resetting `Enabled` to `false` when `Action` is set. - canRotate.BindValueChanged(_ => rotateButton.Enabled.Value = canRotate.Value, true); - canScale.BindValueChanged(_ => scaleButton.Enabled.Value = canScale.Value, true); + canRotate.Result.BindValueChanged(rotate => rotateButton.Enabled.Value = rotate.NewValue, true); + canScale.Result.BindValueChanged(scale => scaleButton.Enabled.Value = scale.NewValue, true); } public bool OnPressed(KeyBindingPressEvent e) From 96a8bdf92064f04c00120a3274b451cf9ebd1ea9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 29 May 2024 09:59:19 +0200 Subject: [PATCH 099/139] Use more generic tooltip copy --- osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs b/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs index b7202b9310..ac1b40e235 100644 --- a/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs +++ b/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs @@ -99,11 +99,11 @@ namespace osu.Game.Rulesets.Osu.Edit }; playfieldCentreButton.Selected.DisabledChanged += isDisabled => { - playfieldCentreButton.TooltipText = isDisabled ? "Select something other than a single slider to perform playfield-based scaling." : string.Empty; + playfieldCentreButton.TooltipText = isDisabled ? "The current selection cannot be scaled relative to playfield centre." : string.Empty; }; selectionCentreButton.Selected.DisabledChanged += isDisabled => { - selectionCentreButton.TooltipText = isDisabled ? "Select more than one object to perform selection-based scaling." : string.Empty; + selectionCentreButton.TooltipText = isDisabled ? "The current selection cannot be scaled relative to its centre." : string.Empty; }; } From ba4073735649eaf4a581ce3f0ae1f566fab38ddc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 29 May 2024 10:01:04 +0200 Subject: [PATCH 100/139] Simplify logic --- osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs b/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs index ac1b40e235..d3e1b491b0 100644 --- a/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs +++ b/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs @@ -124,10 +124,7 @@ namespace osu.Game.Rulesets.Osu.Edit selectionCentreButton.Selected.Disabled = !(scaleHandler.CanScaleX.Value || scaleHandler.CanScaleY.Value); playfieldCentreButton.Selected.Disabled = scaleHandler.IsScalingSlider.Value && !selectionCentreButton.Selected.Disabled; - if (playfieldCentreButton.Selected.Disabled) - scaleOrigin.Items.Last().Select(); - else - scaleOrigin.Items.First().Select(); + scaleOrigin.Items.First(b => !b.Selected.Disabled).Select(); scaleInfo.BindValueChanged(scale => { From 9bd4b0d61303fb0dde3892d51b595174a61a2b4d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 29 May 2024 10:04:49 +0200 Subject: [PATCH 101/139] Rename method --- .../Edit/PreciseScalePopover.cs | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs b/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs index d3e1b491b0..a299eebbce 100644 --- a/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs +++ b/osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs @@ -137,21 +137,23 @@ namespace osu.Game.Rulesets.Osu.Edit { if (scaleInfo.Value.Origin == ScaleOrigin.PlayfieldCentre) { - setBindableEnabled(true, xCheckBox.Current); - setBindableEnabled(true, yCheckBox.Current); + toggleAxisAvailable(xCheckBox.Current, true); + toggleAxisAvailable(yCheckBox.Current, true); } else { - setBindableEnabled(scaleHandler.CanScaleX.Value, xCheckBox.Current); - setBindableEnabled(scaleHandler.CanScaleY.Value, yCheckBox.Current); + toggleAxisAvailable(xCheckBox.Current, scaleHandler.CanScaleX.Value); + toggleAxisAvailable(yCheckBox.Current, scaleHandler.CanScaleY.Value); } } - private void setBindableEnabled(bool enabled, Bindable current) + private void toggleAxisAvailable(Bindable axisBindable, bool available) { - current.Disabled = false; // enable the bindable to allow setting the value - current.Value = enabled; - current.Disabled = !enabled; + // enable the bindable to allow setting the value + axisBindable.Disabled = false; + // restore the presumed default value given the axis's new availability state + axisBindable.Value = available; + axisBindable.Disabled = !available; } private void updateMaxScale() From 9477e3b67de6f33a6866d9761b14a2cda20785b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 29 May 2024 10:14:47 +0200 Subject: [PATCH 102/139] Change editor scale hotkey to Ctrl-E Forgot that Ctrl-T was taken by the game-global toolbar already, so it wasn't working. --- osu.Game/Input/Bindings/GlobalActionContainer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Input/Bindings/GlobalActionContainer.cs b/osu.Game/Input/Bindings/GlobalActionContainer.cs index fd8e6fd6d0..2af564d8ba 100644 --- a/osu.Game/Input/Bindings/GlobalActionContainer.cs +++ b/osu.Game/Input/Bindings/GlobalActionContainer.cs @@ -142,7 +142,7 @@ namespace osu.Game.Input.Bindings new KeyBinding(new[] { InputKey.Control, InputKey.Shift, InputKey.MouseWheelRight }, GlobalAction.EditorCyclePreviousBeatSnapDivisor), new KeyBinding(new[] { InputKey.Control, InputKey.Shift, InputKey.MouseWheelLeft }, GlobalAction.EditorCycleNextBeatSnapDivisor), new KeyBinding(new[] { InputKey.Control, InputKey.R }, GlobalAction.EditorToggleRotateControl), - new KeyBinding(new[] { InputKey.Control, InputKey.T }, GlobalAction.EditorToggleScaleControl), + new KeyBinding(new[] { InputKey.Control, InputKey.E }, GlobalAction.EditorToggleScaleControl), }; private static IEnumerable inGameKeyBindings => new[] From 84513343d6789cf38bd2d8e355a233e277382f19 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 29 May 2024 10:18:22 +0200 Subject: [PATCH 103/139] Remove unused fields --- osu.Game.Rulesets.Osu/Edit/TransformToolboxGroup.cs | 7 ------- 1 file changed, 7 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/TransformToolboxGroup.cs b/osu.Game.Rulesets.Osu/Edit/TransformToolboxGroup.cs index 278d38b2d9..28d0f8320f 100644 --- a/osu.Game.Rulesets.Osu/Edit/TransformToolboxGroup.cs +++ b/osu.Game.Rulesets.Osu/Edit/TransformToolboxGroup.cs @@ -24,13 +24,6 @@ namespace osu.Game.Rulesets.Osu.Edit private EditorToolButton rotateButton = null!; private EditorToolButton scaleButton = null!; - private Bindable canRotatePlayfieldOrigin = null!; - private Bindable canRotateSelectionOrigin = null!; - - private Bindable canScaleX = null!; - private Bindable canScaleY = null!; - private Bindable canScalePlayfieldOrigin = null!; - public SelectionRotationHandler RotationHandler { get; init; } = null!; public OsuSelectionScaleHandler ScaleHandler { get; init; } = null!; From 22a2adb5e6ca899487d0a09f3edbaade37c6338b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 29 May 2024 10:57:30 +0200 Subject: [PATCH 104/139] Revert unrelated changes --- osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTick.cs | 2 +- osu.Game.Rulesets.Osu/Objects/Slider.cs | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTick.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTick.cs index 0c7ba180f2..73c061afbd 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTick.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTick.cs @@ -20,7 +20,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables public const float DEFAULT_TICK_SIZE = 16; - public DrawableSlider DrawableSlider => (DrawableSlider)ParentHitObject; + protected DrawableSlider DrawableSlider => (DrawableSlider)ParentHitObject; private SkinnableDrawable scaleContainer; diff --git a/osu.Game.Rulesets.Osu/Objects/Slider.cs b/osu.Game.Rulesets.Osu/Objects/Slider.cs index 248f40208a..cc3ffd376e 100644 --- a/osu.Game.Rulesets.Osu/Objects/Slider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Slider.cs @@ -248,8 +248,6 @@ namespace osu.Game.Rulesets.Osu.Objects if (TailCircle != null) TailCircle.Position = EndPosition; - - // Positions of other nested hitobjects are not updated } protected void UpdateNestedSamples() From a6c776dac891203fa591426dc4c086eefcdf499c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 29 May 2024 11:11:43 +0200 Subject: [PATCH 105/139] Use hopefully safer implementation of anchoring judgements to objects --- .../TestSceneDrawableJudgement.cs | 2 +- .../Objects/Drawables/DrawableOsuJudgement.cs | 14 +++---------- .../Rulesets/Judgements/DrawableJudgement.cs | 20 ++++++++++++------- 3 files changed, 17 insertions(+), 19 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneDrawableJudgement.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneDrawableJudgement.cs index 5f5596cbb3..a239f671af 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneDrawableJudgement.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneDrawableJudgement.cs @@ -108,7 +108,7 @@ namespace osu.Game.Rulesets.Osu.Tests private partial class TestDrawableOsuJudgement : DrawableOsuJudgement { public new SkinnableSprite Lighting => base.Lighting; - public new SkinnableDrawable JudgementBody => base.JudgementBody; + public new SkinnableDrawable? JudgementBody => base.JudgementBody; } } } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs index ffbf45291f..98fb99609a 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.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. -#nullable disable - using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Game.Configuration; @@ -14,10 +12,10 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables { public partial class DrawableOsuJudgement : DrawableJudgement { - internal SkinnableLighting Lighting { get; private set; } + internal SkinnableLighting Lighting { get; private set; } = null!; [Resolved] - private OsuConfigManager config { get; set; } + private OsuConfigManager config { get; set; } = null!; [BackgroundDependencyLoader] private void load() @@ -38,19 +36,13 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables Lighting.ResetAnimation(); Lighting.SetColourFrom(JudgedObject, Result); - - if (JudgedObject is DrawableOsuHitObject osuObject) - { - Position = osuObject.ToSpaceOfOtherDrawable(osuObject.OriginPosition, Parent!); - Scale = new Vector2(osuObject.HitObject.Scale); - } } protected override void Update() { base.Update(); - if (JudgedObject is DrawableOsuHitObject osuObject && Parent != null && osuObject.HitObject != null) + if (JudgedObject is DrawableOsuHitObject osuObject && JudgedObject.IsInUse) { Position = osuObject.ToSpaceOfOtherDrawable(osuObject.OriginPosition, Parent!); Scale = new Vector2(osuObject.HitObject.Scale); diff --git a/osu.Game/Rulesets/Judgements/DrawableJudgement.cs b/osu.Game/Rulesets/Judgements/DrawableJudgement.cs index b4686c52f3..37a9766b71 100644 --- a/osu.Game/Rulesets/Judgements/DrawableJudgement.cs +++ b/osu.Game/Rulesets/Judgements/DrawableJudgement.cs @@ -1,11 +1,8 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.Diagnostics; -using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -24,13 +21,13 @@ namespace osu.Game.Rulesets.Judgements { private const float judgement_size = 128; - public JudgementResult Result { get; private set; } + public JudgementResult? Result { get; private set; } - public DrawableHitObject JudgedObject { get; private set; } + public DrawableHitObject? JudgedObject { get; private set; } public override bool RemoveCompletedTransforms => false; - protected SkinnableDrawable JudgementBody { get; private set; } + protected SkinnableDrawable? JudgementBody { get; private set; } private readonly Container aboveHitObjectsContent; @@ -97,12 +94,19 @@ namespace osu.Game.Rulesets.Judgements /// /// The applicable judgement. /// The drawable object. - public void Apply([NotNull] JudgementResult result, [CanBeNull] DrawableHitObject judgedObject) + public void Apply(JudgementResult result, DrawableHitObject? judgedObject) { Result = result; JudgedObject = judgedObject; } + protected override void FreeAfterUse() + { + base.FreeAfterUse(); + + JudgedObject = null; + } + protected override void PrepareForUse() { base.PrepareForUse(); @@ -121,6 +125,8 @@ namespace osu.Game.Rulesets.Judgements ApplyTransformsAt(double.MinValue, true); ClearTransforms(true); + Debug.Assert(Result != null && JudgementBody != null); + LifetimeStart = Result.TimeAbsolute; using (BeginAbsoluteSequence(Result.TimeAbsolute)) From cc136556175b4c2e854aca51d3aebe35f0e5069f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 29 May 2024 13:31:15 +0200 Subject: [PATCH 106/139] Derive API response version from game version (Or local date, in the case of non-deployed builds). Came up when I was looking at https://github.com/ppy/osu-web/pull/11240 and found that we were still hardcoding this. Thankfully, this *should not* cause issues, since there don't seem to be any (documented or undocumented) API response version checks for versions newer than 20220705 in osu-web master. For clarity and possible debugging needs, the API response version is also logged. --- osu.Game/Online/API/APIAccess.cs | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/osu.Game/Online/API/APIAccess.cs b/osu.Game/Online/API/APIAccess.cs index d3707fe74d..6f84e98d2c 100644 --- a/osu.Game/Online/API/APIAccess.cs +++ b/osu.Game/Online/API/APIAccess.cs @@ -42,7 +42,7 @@ namespace osu.Game.Online.API public string WebsiteRootUrl { get; } - public int APIVersion => 20220705; // We may want to pull this from the game version eventually. + public int APIVersion { get; } public Exception LastLoginError { get; private set; } @@ -84,12 +84,23 @@ namespace osu.Game.Online.API this.config = config; this.versionHash = versionHash; + if (game.IsDeployedBuild) + APIVersion = game.AssemblyVersion.Major * 10000 + game.AssemblyVersion.Minor; + else + { + var now = DateTimeOffset.Now; + APIVersion = now.Year * 10000 + now.Month * 100 + now.Day; + } + APIEndpointUrl = endpointConfiguration.APIEndpointUrl; WebsiteRootUrl = endpointConfiguration.WebsiteRootUrl; NotificationsClient = setUpNotificationsClient(); authentication = new OAuth(endpointConfiguration.APIClientID, endpointConfiguration.APIClientSecret, APIEndpointUrl); + log = Logger.GetLogger(LoggingTarget.Network); + log.Add($@"API endpoint root: {APIEndpointUrl}"); + log.Add($@"API request version: {APIVersion}"); ProvidedUsername = config.Get(OsuSetting.Username); From ab01fa6d4572af4239527c0090ae438f549f55c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 29 May 2024 13:34:12 +0200 Subject: [PATCH 107/139] Add xmldoc to `APIAccess.APIVersion` --- osu.Game/Online/API/APIAccess.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game/Online/API/APIAccess.cs b/osu.Game/Online/API/APIAccess.cs index 6f84e98d2c..923f841bd8 100644 --- a/osu.Game/Online/API/APIAccess.cs +++ b/osu.Game/Online/API/APIAccess.cs @@ -42,6 +42,10 @@ namespace osu.Game.Online.API public string WebsiteRootUrl { get; } + /// + /// The API response version. + /// See: https://osu.ppy.sh/docs/index.html#api-versions + /// public int APIVersion { get; } public Exception LastLoginError { get; private set; } From 8916f08f8620d38b308f211006e6c75535138342 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 30 May 2024 09:03:02 +0200 Subject: [PATCH 108/139] Only take initial judgement position from object instead of following Looks less bad with mods like depth active. Co-authored-by: Dean Herbert --- .../Objects/Drawables/DrawableOsuJudgement.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs index 98fb99609a..6e252a53e2 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs @@ -17,6 +17,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables [Resolved] private OsuConfigManager config { get; set; } = null!; + private bool positionTransferred; + [BackgroundDependencyLoader] private void load() { @@ -36,16 +38,20 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables Lighting.ResetAnimation(); Lighting.SetColourFrom(JudgedObject, Result); + + positionTransferred = false; } protected override void Update() { base.Update(); - if (JudgedObject is DrawableOsuHitObject osuObject && JudgedObject.IsInUse) + if (!positionTransferred && JudgedObject is DrawableOsuHitObject osuObject && JudgedObject.IsInUse) { Position = osuObject.ToSpaceOfOtherDrawable(osuObject.OriginPosition, Parent!); Scale = new Vector2(osuObject.HitObject.Scale); + + positionTransferred = true; } } From 2f2bc8e52eaa6bf2213357f8ca624e902cfcf2f4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 30 May 2024 17:37:55 +0900 Subject: [PATCH 109/139] Avoid `ChatAckRequest` failures flooding console in `OsuGameTestScene`s --- osu.Game.Tests/Chat/TestSceneChannelManager.cs | 2 +- osu.Game/Online/API/DummyAPIAccess.cs | 8 ++++++++ osu.Game/Online/API/Requests/Responses/ChatAckResponse.cs | 4 ++-- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Chat/TestSceneChannelManager.cs b/osu.Game.Tests/Chat/TestSceneChannelManager.cs index 95fd2669e5..ef4d4f683a 100644 --- a/osu.Game.Tests/Chat/TestSceneChannelManager.cs +++ b/osu.Game.Tests/Chat/TestSceneChannelManager.cs @@ -59,7 +59,7 @@ namespace osu.Game.Tests.Chat return true; case ChatAckRequest ack: - ack.TriggerSuccess(new ChatAckResponse { Silences = silencedUserIds.Select(u => new ChatSilence { UserId = u }).ToList() }); + ack.TriggerSuccess(new ChatAckResponse { Silences = silencedUserIds.Select(u => new ChatSilence { UserId = u }).ToArray() }); silencedUserIds.Clear(); return true; diff --git a/osu.Game/Online/API/DummyAPIAccess.cs b/osu.Game/Online/API/DummyAPIAccess.cs index 4962838bd9..8d1e986577 100644 --- a/osu.Game/Online/API/DummyAPIAccess.cs +++ b/osu.Game/Online/API/DummyAPIAccess.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using osu.Framework.Bindables; @@ -84,6 +85,13 @@ namespace osu.Game.Online.API { if (HandleRequest?.Invoke(request) != true) { + // Noisy so let's silently allow these to succeed. + if (request is ChatAckRequest ack) + { + ack.TriggerSuccess(new ChatAckResponse()); + return; + } + request.Fail(new InvalidOperationException($@"{nameof(DummyAPIAccess)} cannot process this request.")); } }); diff --git a/osu.Game/Online/API/Requests/Responses/ChatAckResponse.cs b/osu.Game/Online/API/Requests/Responses/ChatAckResponse.cs index 6ed22a19b2..f68735d390 100644 --- a/osu.Game/Online/API/Requests/Responses/ChatAckResponse.cs +++ b/osu.Game/Online/API/Requests/Responses/ChatAckResponse.cs @@ -1,7 +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.Collections.Generic; +using System; using Newtonsoft.Json; namespace osu.Game.Online.API.Requests.Responses @@ -10,6 +10,6 @@ namespace osu.Game.Online.API.Requests.Responses public class ChatAckResponse { [JsonProperty("silences")] - public List Silences { get; set; } = null!; + public ChatSilence[] Silences { get; set; } = Array.Empty(); } } From 36d7775032c08f0983ebd2ea28960bcfb0db4968 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 30 May 2024 17:38:05 +0900 Subject: [PATCH 110/139] Fix typo in `IAPIProvider` xmldoc --- osu.Game/Online/API/IAPIProvider.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Online/API/IAPIProvider.cs b/osu.Game/Online/API/IAPIProvider.cs index 66f124f7c3..7b95b68ec3 100644 --- a/osu.Game/Online/API/IAPIProvider.cs +++ b/osu.Game/Online/API/IAPIProvider.cs @@ -61,7 +61,7 @@ namespace osu.Game.Online.API string APIEndpointUrl { get; } /// - /// The root URL of of the website, excluding the trailing slash. + /// The root URL of the website, excluding the trailing slash. /// string WebsiteRootUrl { get; } From 50bd0897f653f64fa394abc490b691ac84170547 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 30 May 2024 10:38:22 +0200 Subject: [PATCH 111/139] Fix main menu button backgrounds not covering their entire width sometimes I thought I had fixed this already once but it still looks broken. Basically when hovering over main menu buttons every now and then it will look like their backgrounds are not covering their entire width when they expand. The removed X position set looks wrong to me when inspecting the draw visualiser with the element because the element looks to be off centre horizontally, and removing it fixes that. --- osu.Game/Screens/Menu/MainMenuButton.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Screens/Menu/MainMenuButton.cs b/osu.Game/Screens/Menu/MainMenuButton.cs index 29a661066c..4df5e6d309 100644 --- a/osu.Game/Screens/Menu/MainMenuButton.cs +++ b/osu.Game/Screens/Menu/MainMenuButton.cs @@ -115,7 +115,6 @@ namespace osu.Game.Screens.Menu backgroundContent = CreateBackground(colour).With(bg => { bg.RelativeSizeAxes = Axes.Y; - bg.X = -ButtonSystem.WEDGE_WIDTH; bg.Anchor = Anchor.Centre; bg.Origin = Anchor.Centre; }), From f3bc944ac86c7f5bd37c04cc35c0c2fb71cebfba Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 30 May 2024 17:45:32 +0900 Subject: [PATCH 112/139] Remove using statement --- osu.Game/Online/API/DummyAPIAccess.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Online/API/DummyAPIAccess.cs b/osu.Game/Online/API/DummyAPIAccess.cs index 8d1e986577..960941fc05 100644 --- a/osu.Game/Online/API/DummyAPIAccess.cs +++ b/osu.Game/Online/API/DummyAPIAccess.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using osu.Framework.Bindables; From f6a59bee9addebb75dc34b4d1253da84967b35bb Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 30 May 2024 19:17:18 +0900 Subject: [PATCH 113/139] Use delayed resume overlay for autopilot --- osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs | 2 -- osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs | 9 ++++++++- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs b/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs index efcc728d55..b45b4fea13 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs @@ -67,8 +67,6 @@ namespace osu.Game.Rulesets.Osu.Mods // Generate the replay frames the cursor should follow replayFrames = new OsuAutoGenerator(drawableRuleset.Beatmap, drawableRuleset.Mods).Generate().Frames.Cast().ToList(); - - drawableRuleset.UseResumeOverlay = false; } } } diff --git a/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs b/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs index c3efd48053..f0390ad716 100644 --- a/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs @@ -13,6 +13,7 @@ using osu.Game.Replays; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Osu.Configuration; +using osu.Game.Rulesets.Osu.Mods; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Replays; using osu.Game.Rulesets.UI; @@ -45,7 +46,13 @@ namespace osu.Game.Rulesets.Osu.UI public override PlayfieldAdjustmentContainer CreatePlayfieldAdjustmentContainer() => new OsuPlayfieldAdjustmentContainer { AlignWithStoryboard = true }; - protected override ResumeOverlay CreateResumeOverlay() => new OsuResumeOverlay(); + protected override ResumeOverlay CreateResumeOverlay() + { + if (Mods.Any(m => m is OsuModAutopilot)) + return new DelayedResumeOverlay { Scale = new Vector2(0.65f) }; + + return new OsuResumeOverlay(); + } protected override ReplayInputHandler CreateReplayInputHandler(Replay replay) => new OsuFramedReplayInputHandler(replay); From 87a331fdde783dbfc3b5f395339d2d09cba1105e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 30 May 2024 16:34:52 +0900 Subject: [PATCH 114/139] Improve text on external link warning dialog --- osu.Game/Online/Chat/ExternalLinkOpener.cs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/osu.Game/Online/Chat/ExternalLinkOpener.cs b/osu.Game/Online/Chat/ExternalLinkOpener.cs index 56d24e35bb..08adf965da 100644 --- a/osu.Game/Online/Chat/ExternalLinkOpener.cs +++ b/osu.Game/Online/Chat/ExternalLinkOpener.cs @@ -10,6 +10,7 @@ using osu.Framework.Platform; using osu.Game.Configuration; using osu.Game.Overlays; using osu.Game.Overlays.Dialog; +using osu.Game.Resources.Localisation.Web; namespace osu.Game.Online.Chat { @@ -44,8 +45,8 @@ namespace osu.Game.Online.Chat { public ExternalLinkDialog(string url, Action openExternalLinkAction, Action copyExternalLinkAction) { - HeaderText = "Just checking..."; - BodyText = $"You are about to leave osu! and open the following link in a web browser:\n\n{url}"; + HeaderText = "You are leaving osu!"; + BodyText = $"Are you sure you want to open the following link in a web browser:\n\n{url}"; Icon = FontAwesome.Solid.ExclamationTriangle; @@ -53,17 +54,17 @@ namespace osu.Game.Online.Chat { new PopupDialogOkButton { - Text = @"Yes. Go for it.", + Text = @"Open in browser", Action = openExternalLinkAction }, new PopupDialogCancelButton { - Text = @"Copy URL to the clipboard instead.", + Text = @"Copy URL to the clipboard", Action = copyExternalLinkAction }, new PopupDialogCancelButton { - Text = @"No! Abort mission!" + Text = CommonStrings.ButtonsCancel, }, }; } From ed64bfff8d9c13c54b27b955a706e28d3cefb134 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 30 May 2024 16:39:53 +0900 Subject: [PATCH 115/139] Bypass external link dialog for links on the trusted osu! domain --- osu.Game/OsuGame.cs | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index af01a1b1ac..29c040c597 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -485,10 +485,19 @@ namespace osu.Game } }); - public void OpenUrlExternally(string url, bool bypassExternalUrlWarning = false) => waitForReady(() => externalLinkOpener, _ => + public void OpenUrlExternally(string url, bool forceBypassExternalUrlWarning = false) => waitForReady(() => externalLinkOpener, _ => { + bool isTrustedDomain; + if (url.StartsWith('/')) - url = $"{API.APIEndpointUrl}{url}"; + { + url = $"{API.WebsiteRootUrl}{url}"; + isTrustedDomain = true; + } + else + { + isTrustedDomain = url.StartsWith(API.APIEndpointUrl, StringComparison.Ordinal); + } if (!url.CheckIsValidUrl()) { @@ -500,7 +509,7 @@ namespace osu.Game return; } - externalLinkOpener.OpenUrlExternally(url, bypassExternalUrlWarning); + externalLinkOpener.OpenUrlExternally(url, forceBypassExternalUrlWarning || isTrustedDomain); }); /// From 53b7c29488f6dd28ae30877e0df148d4c6be6d33 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 30 May 2024 17:35:42 +0900 Subject: [PATCH 116/139] Add test coverage of menu banner link opening --- .../Visual/Menus/TestSceneMainMenu.cs | 49 ++++++++++++++++++- osu.Game/Screens/Menu/OnlineMenuBanner.cs | 5 ++ 2 files changed, 52 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Menus/TestSceneMainMenu.cs b/osu.Game.Tests/Visual/Menus/TestSceneMainMenu.cs index e2a841d79a..240421b360 100644 --- a/osu.Game.Tests/Visual/Menus/TestSceneMainMenu.cs +++ b/osu.Game.Tests/Visual/Menus/TestSceneMainMenu.cs @@ -6,6 +6,7 @@ using NUnit.Framework; using osu.Framework.Graphics.Containers; using osu.Framework.Testing; using osu.Game.Online.API.Requests.Responses; +using osu.Game.Overlays; using osu.Game.Screens.Menu; using osuTK.Input; @@ -15,8 +16,14 @@ namespace osu.Game.Tests.Visual.Menus { private OnlineMenuBanner onlineMenuBanner => Game.ChildrenOfType().Single(); + public override void SetUpSteps() + { + base.SetUpSteps(); + AddStep("don't fetch online content", () => onlineMenuBanner.FetchOnlineContent = false); + } + [Test] - public void TestOnlineMenuBanner() + public void TestOnlineMenuBannerTrusted() { AddStep("set online content", () => onlineMenuBanner.Current.Value = new APIMenuContent { @@ -25,13 +32,51 @@ namespace osu.Game.Tests.Visual.Menus new APIMenuImage { Image = @"https://assets.ppy.sh/main-menu/project-loved-2@2x.png", - Url = @"https://osu.ppy.sh/home/news/2023-12-21-project-loved-december-2023", + Url = $@"{API.WebsiteRootUrl}/home/news/2023-12-21-project-loved-december-2023", } } }); AddAssert("system title not visible", () => onlineMenuBanner.State.Value, () => Is.EqualTo(Visibility.Hidden)); AddStep("enter menu", () => InputManager.Key(Key.Enter)); AddUntilStep("system title visible", () => onlineMenuBanner.State.Value, () => Is.EqualTo(Visibility.Visible)); + AddUntilStep("image loaded", () => onlineMenuBanner.ChildrenOfType().FirstOrDefault()?.IsLoaded, () => Is.True); + + AddStep("click banner", () => + { + InputManager.MoveMouseTo(onlineMenuBanner); + InputManager.Click(MouseButton.Left); + }); + + // Might not catch every occurrence due to async nature, but works in manual testing and saves annoying test setup. + AddAssert("no dialog", () => Game.ChildrenOfType().SingleOrDefault()?.CurrentDialog == null); + } + + [Test] + public void TestOnlineMenuBannerUntrustedDomain() + { + AddStep("set online content", () => onlineMenuBanner.Current.Value = new APIMenuContent + { + Images = new[] + { + new APIMenuImage + { + Image = @"https://assets.ppy.sh/main-menu/project-loved-2@2x.png", + Url = @"https://google.com", + } + } + }); + AddAssert("system title not visible", () => onlineMenuBanner.State.Value, () => Is.EqualTo(Visibility.Hidden)); + AddStep("enter menu", () => InputManager.Key(Key.Enter)); + AddUntilStep("system title visible", () => onlineMenuBanner.State.Value, () => Is.EqualTo(Visibility.Visible)); + AddUntilStep("image loaded", () => onlineMenuBanner.ChildrenOfType().FirstOrDefault()?.IsLoaded, () => Is.True); + + AddStep("click banner", () => + { + InputManager.MoveMouseTo(onlineMenuBanner); + InputManager.Click(MouseButton.Left); + }); + + AddUntilStep("wait for dialog", () => Game.ChildrenOfType().SingleOrDefault()?.CurrentDialog != null); } } } diff --git a/osu.Game/Screens/Menu/OnlineMenuBanner.cs b/osu.Game/Screens/Menu/OnlineMenuBanner.cs index b9d269c82a..edd34d0bfb 100644 --- a/osu.Game/Screens/Menu/OnlineMenuBanner.cs +++ b/osu.Game/Screens/Menu/OnlineMenuBanner.cs @@ -73,6 +73,9 @@ namespace osu.Game.Screens.Menu Task.Run(() => request.Perform()) .ContinueWith(r => { + if (!FetchOnlineContent) + return; + if (r.IsCompletedSuccessfully) Schedule(() => Current.Value = request.ResponseObject); @@ -170,6 +173,8 @@ namespace osu.Game.Screens.Menu private Sprite flash = null!; + public override bool IsPresent => base.IsPresent || Scheduler.HasPendingTasks; + private ScheduledDelegate? openUrlAction; public MenuImage(APIMenuImage image) From 474ff5b99d089cd5db38d26541598735cb65b9cb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 31 May 2024 10:46:30 +0900 Subject: [PATCH 117/139] Use question mark for more grammatical correctness Co-authored-by: Joseph Madamba --- osu.Game/Online/Chat/ExternalLinkOpener.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Online/Chat/ExternalLinkOpener.cs b/osu.Game/Online/Chat/ExternalLinkOpener.cs index 08adf965da..513ccd43af 100644 --- a/osu.Game/Online/Chat/ExternalLinkOpener.cs +++ b/osu.Game/Online/Chat/ExternalLinkOpener.cs @@ -46,7 +46,7 @@ namespace osu.Game.Online.Chat public ExternalLinkDialog(string url, Action openExternalLinkAction, Action copyExternalLinkAction) { HeaderText = "You are leaving osu!"; - BodyText = $"Are you sure you want to open the following link in a web browser:\n\n{url}"; + BodyText = $"Are you sure you want to open the following link in a web browser?\n\n{url}"; Icon = FontAwesome.Solid.ExclamationTriangle; From e52f524ea2ce15b6ef891900a3c7cee236f6d7cb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 31 May 2024 11:44:53 +0900 Subject: [PATCH 118/139] Use common header text --- osu.Game/Online/Chat/ExternalLinkOpener.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game/Online/Chat/ExternalLinkOpener.cs b/osu.Game/Online/Chat/ExternalLinkOpener.cs index 513ccd43af..2f046d8fd4 100644 --- a/osu.Game/Online/Chat/ExternalLinkOpener.cs +++ b/osu.Game/Online/Chat/ExternalLinkOpener.cs @@ -8,9 +8,10 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; using osu.Framework.Platform; using osu.Game.Configuration; +using osu.Game.Localisation; using osu.Game.Overlays; using osu.Game.Overlays.Dialog; -using osu.Game.Resources.Localisation.Web; +using CommonStrings = osu.Game.Resources.Localisation.Web.CommonStrings; namespace osu.Game.Online.Chat { @@ -45,7 +46,7 @@ namespace osu.Game.Online.Chat { public ExternalLinkDialog(string url, Action openExternalLinkAction, Action copyExternalLinkAction) { - HeaderText = "You are leaving osu!"; + HeaderText = DeleteConfirmationDialogStrings.HeaderText; BodyText = $"Are you sure you want to open the following link in a web browser?\n\n{url}"; Icon = FontAwesome.Solid.ExclamationTriangle; From 5dfeaa3c4afa75dbfebffbd1c7a61af0dd93b6d4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 31 May 2024 11:46:32 +0900 Subject: [PATCH 119/139] Move dialog strings to more common class name --- ...{DeleteConfirmationDialogStrings.cs => DialogStrings.cs} | 6 +++--- osu.Game/Online/Chat/ExternalLinkOpener.cs | 2 +- osu.Game/Overlays/Dialog/DangerousActionDialog.cs | 6 +++--- 3 files changed, 7 insertions(+), 7 deletions(-) rename osu.Game/Localisation/{DeleteConfirmationDialogStrings.cs => DialogStrings.cs} (80%) diff --git a/osu.Game/Localisation/DeleteConfirmationDialogStrings.cs b/osu.Game/Localisation/DialogStrings.cs similarity index 80% rename from osu.Game/Localisation/DeleteConfirmationDialogStrings.cs rename to osu.Game/Localisation/DialogStrings.cs index 25997eadd3..cea543eb9f 100644 --- a/osu.Game/Localisation/DeleteConfirmationDialogStrings.cs +++ b/osu.Game/Localisation/DialogStrings.cs @@ -5,14 +5,14 @@ using osu.Framework.Localisation; namespace osu.Game.Localisation { - public static class DeleteConfirmationDialogStrings + public static class DialogStrings { - private const string prefix = @"osu.Game.Resources.Localisation.DeleteConfirmationDialog"; + private const string prefix = @"osu.Game.Resources.Localisation.DialogStrings"; /// /// "Caution" /// - public static LocalisableString HeaderText => new TranslatableString(getKey(@"header_text"), @"Caution"); + public static LocalisableString Caution => new TranslatableString(getKey(@"header_text"), @"Caution"); /// /// "Yes. Go for it." diff --git a/osu.Game/Online/Chat/ExternalLinkOpener.cs b/osu.Game/Online/Chat/ExternalLinkOpener.cs index 2f046d8fd4..82ad4215c2 100644 --- a/osu.Game/Online/Chat/ExternalLinkOpener.cs +++ b/osu.Game/Online/Chat/ExternalLinkOpener.cs @@ -46,7 +46,7 @@ namespace osu.Game.Online.Chat { public ExternalLinkDialog(string url, Action openExternalLinkAction, Action copyExternalLinkAction) { - HeaderText = DeleteConfirmationDialogStrings.HeaderText; + HeaderText = DialogStrings.Caution; BodyText = $"Are you sure you want to open the following link in a web browser?\n\n{url}"; Icon = FontAwesome.Solid.ExclamationTriangle; diff --git a/osu.Game/Overlays/Dialog/DangerousActionDialog.cs b/osu.Game/Overlays/Dialog/DangerousActionDialog.cs index 42a3ff827c..60f51e7e06 100644 --- a/osu.Game/Overlays/Dialog/DangerousActionDialog.cs +++ b/osu.Game/Overlays/Dialog/DangerousActionDialog.cs @@ -30,7 +30,7 @@ namespace osu.Game.Overlays.Dialog protected DangerousActionDialog() { - HeaderText = DeleteConfirmationDialogStrings.HeaderText; + HeaderText = DialogStrings.DialogCautionHeader; Icon = FontAwesome.Regular.TrashAlt; @@ -38,12 +38,12 @@ namespace osu.Game.Overlays.Dialog { new PopupDialogDangerousButton { - Text = DeleteConfirmationDialogStrings.Confirm, + Text = DialogStrings.DialogConfirm, Action = () => DangerousAction?.Invoke() }, new PopupDialogCancelButton { - Text = DeleteConfirmationDialogStrings.Cancel, + Text = DialogStrings.Cancel, Action = () => CancelAction?.Invoke() } }; From cb72630ce1894a3ed73f607112095c4845413349 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 31 May 2024 08:09:06 +0200 Subject: [PATCH 120/139] Fix compile failures --- osu.Game/Overlays/Dialog/DangerousActionDialog.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/Dialog/DangerousActionDialog.cs b/osu.Game/Overlays/Dialog/DangerousActionDialog.cs index 60f51e7e06..31160d1832 100644 --- a/osu.Game/Overlays/Dialog/DangerousActionDialog.cs +++ b/osu.Game/Overlays/Dialog/DangerousActionDialog.cs @@ -30,7 +30,7 @@ namespace osu.Game.Overlays.Dialog protected DangerousActionDialog() { - HeaderText = DialogStrings.DialogCautionHeader; + HeaderText = DialogStrings.Caution; Icon = FontAwesome.Regular.TrashAlt; @@ -38,7 +38,7 @@ namespace osu.Game.Overlays.Dialog { new PopupDialogDangerousButton { - Text = DialogStrings.DialogConfirm, + Text = DialogStrings.Confirm, Action = () => DangerousAction?.Invoke() }, new PopupDialogCancelButton From 1a26a5dbdaf77256564930e69e5f20d3e9a2cd17 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 31 May 2024 08:09:25 +0200 Subject: [PATCH 121/139] Fix mismatching localisation key prefix The `Strings` suffix is not supposed to be in here, judging by other localisation classes. --- osu.Game/Localisation/DialogStrings.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Localisation/DialogStrings.cs b/osu.Game/Localisation/DialogStrings.cs index cea543eb9f..043a3f5b4c 100644 --- a/osu.Game/Localisation/DialogStrings.cs +++ b/osu.Game/Localisation/DialogStrings.cs @@ -7,7 +7,7 @@ namespace osu.Game.Localisation { public static class DialogStrings { - private const string prefix = @"osu.Game.Resources.Localisation.DialogStrings"; + private const string prefix = @"osu.Game.Resources.Localisation.Dialog"; /// /// "Caution" From 9111da81d22c2a9e35533df964cb9e5c0e691807 Mon Sep 17 00:00:00 2001 From: Aurelian Date: Fri, 31 May 2024 01:30:26 +0200 Subject: [PATCH 122/139] Updated comments --- osu.Game/Rulesets/Objects/SliderPath.cs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/osu.Game/Rulesets/Objects/SliderPath.cs b/osu.Game/Rulesets/Objects/SliderPath.cs index 730a2013b0..5550815370 100644 --- a/osu.Game/Rulesets/Objects/SliderPath.cs +++ b/osu.Game/Rulesets/Objects/SliderPath.cs @@ -332,16 +332,15 @@ namespace osu.Game.Rulesets.Objects CircularArcProperties circularArcProperties = new CircularArcProperties(subControlPoints); - //if false, we'll end up breaking anyways when calculating subPath + // `PathApproximator` will already internally revert to B-spline if the arc isn't valid. if (!circularArcProperties.IsValid) break; - //Coppied from PathApproximator.CircularArcToPiecewiseLinear + // taken from https://github.com/ppy/osu-framework/blob/1201e641699a1d50d2f6f9295192dad6263d5820/osu.Framework/Utils/PathApproximator.cs#L181-L186 int subPoints = (2f * circularArcProperties.Radius <= 0.1f) ? 2 : Math.Max(2, (int)Math.Ceiling(circularArcProperties.ThetaRange / (2.0 * Math.Acos(1f - (0.1f / circularArcProperties.Radius))))); - //theoretically can be int.MaxValue, but lets set this to a lower value anyways - //1000 requires an arc length of over 20 thousand to surpass this limit, which should be safe. - //See here for calculations https://www.desmos.com/calculator/210bwswkbb + // 1000 subpoints requires an arc length of at least ~120 thousand to occur + // See here for calculations https://www.desmos.com/calculator/umj6jvmcz7 if (subPoints >= 1000) break; From c6a7082034da26c6399633ad18665a85ccc58f30 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 31 May 2024 15:38:26 +0900 Subject: [PATCH 123/139] Fix incorrect prefix check --- osu.Game/OsuGame.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 29c040c597..0833f52b1e 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -496,7 +496,7 @@ namespace osu.Game } else { - isTrustedDomain = url.StartsWith(API.APIEndpointUrl, StringComparison.Ordinal); + isTrustedDomain = url.StartsWith(API.WebsiteRootUrl, StringComparison.Ordinal); } if (!url.CheckIsValidUrl()) From 69990c35cb638d57e735cedd6785fe2a06bd4341 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 31 May 2024 08:47:19 +0200 Subject: [PATCH 124/139] Add commentary on presence of `IsPresent` override --- osu.Game/Screens/Menu/OnlineMenuBanner.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Screens/Menu/OnlineMenuBanner.cs b/osu.Game/Screens/Menu/OnlineMenuBanner.cs index edd34d0bfb..49fc89c171 100644 --- a/osu.Game/Screens/Menu/OnlineMenuBanner.cs +++ b/osu.Game/Screens/Menu/OnlineMenuBanner.cs @@ -173,6 +173,9 @@ namespace osu.Game.Screens.Menu private Sprite flash = null!; + /// + /// Overridden as a safety for functioning correctly. + /// public override bool IsPresent => base.IsPresent || Scheduler.HasPendingTasks; private ScheduledDelegate? openUrlAction; From e3205fce4769afd2ac3cce60f6e1321f530ec029 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Sat, 1 Jun 2024 14:29:23 +0900 Subject: [PATCH 125/139] Fix unable to drag-scroll on collections right-click menu --- osu.Game/Graphics/UserInterface/DrawableStatefulMenuItem.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game/Graphics/UserInterface/DrawableStatefulMenuItem.cs b/osu.Game/Graphics/UserInterface/DrawableStatefulMenuItem.cs index 686c490930..b9e81e1bf2 100644 --- a/osu.Game/Graphics/UserInterface/DrawableStatefulMenuItem.cs +++ b/osu.Game/Graphics/UserInterface/DrawableStatefulMenuItem.cs @@ -26,9 +26,12 @@ namespace osu.Game.Graphics.UserInterface // Right mouse button is a special case where we allow actioning without dismissing the menu. // This is achieved by not calling `Clicked` (as done by the base implementation in OnClick). if (IsActionable && e.Button == MouseButton.Right) + { Item.Action.Value?.Invoke(); + return true; + } - return true; + return false; } private partial class ToggleTextContainer : TextContainer From 091104764e677d4efecbe8958ca71b7f9cac03b8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 2 Jun 2024 17:33:06 +0900 Subject: [PATCH 126/139] Fix `AssemblyRulesetStore` not marking rulesets as available --- osu.Game/Rulesets/AssemblyRulesetStore.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/AssemblyRulesetStore.cs b/osu.Game/Rulesets/AssemblyRulesetStore.cs index 03554ef2db..935ef241dc 100644 --- a/osu.Game/Rulesets/AssemblyRulesetStore.cs +++ b/osu.Game/Rulesets/AssemblyRulesetStore.cs @@ -43,7 +43,12 @@ namespace osu.Game.Rulesets // add all legacy rulesets first to ensure they have exclusive choice of primary key. foreach (var r in instances.Where(r => r is ILegacyRuleset)) - availableRulesets.Add(new RulesetInfo(r.RulesetInfo.ShortName, r.RulesetInfo.Name, r.RulesetInfo.InstantiationInfo, r.RulesetInfo.OnlineID)); + { + availableRulesets.Add(new RulesetInfo(r.RulesetInfo.ShortName, r.RulesetInfo.Name, r.RulesetInfo.InstantiationInfo, r.RulesetInfo.OnlineID) + { + Available = true + }); + } } } } From 96514132c1ecc5e3efabf9427741148b7e283fd1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 3 Jun 2024 12:24:50 +0900 Subject: [PATCH 127/139] Fix occasional test failures on new menu content tests Scheduled data transfer could still overwrite test data. --- osu.Game/Screens/Menu/OnlineMenuBanner.cs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Menu/OnlineMenuBanner.cs b/osu.Game/Screens/Menu/OnlineMenuBanner.cs index 49fc89c171..aa73ce2136 100644 --- a/osu.Game/Screens/Menu/OnlineMenuBanner.cs +++ b/osu.Game/Screens/Menu/OnlineMenuBanner.cs @@ -77,7 +77,15 @@ namespace osu.Game.Screens.Menu return; if (r.IsCompletedSuccessfully) - Schedule(() => Current.Value = request.ResponseObject); + { + Schedule(() => + { + if (!FetchOnlineContent) + return; + + Current.Value = request.ResponseObject; + }); + } // if the request failed, "observe" the exception. // it isn't very important why this failed, as it's only for display. From f13ca28d5eae95792bb6cdac1e75f05f56d26c2a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 4 Jun 2024 10:25:08 +0200 Subject: [PATCH 128/139] Fix performance overhead from ternary state bindable callbacks when selection is changing Closes https://github.com/ppy/osu/issues/28369. The reporter of the issue was incorrect; it's not the beat snap grid that is causing the problem, it's something far stupider than that. When the current selection changes, `EditorSelectionHandler.UpdateTernaryStates()` is supposed to update the state of ternary bindables to reflect the reality of the current selection. This in turn will fire bindable change callbacks for said ternary toggles, which heavily use `EditorBeatmap.PerformOnSelection()`. The thing about that method is that it will attempt to check whether any changes were actually made to avoid producing empty undo states, *but* to do this, it must *serialise out the entire beatmap to a stream* and then *binary equality check that* to determine whether any changes were actually made: https://github.com/ppy/osu/blob/7b14c77e43e4ee96775a9fcb6843324170fa70bb/osu.Game/Screens/Edit/EditorChangeHandler.cs#L65-L69 As goes without saying, this is very expensive and unnecessary, which leads to stuff like keeping a selection box active while a taiko beatmap is playing under it dog slow. So to attempt to mitigate that, add precondition checks to every single ternary callback of this sort to avoid this serialisation overhead. And yes, those precondition checks use linq, and that is *still* faster than not having them. --- .../Edit/TaikoSelectionHandler.cs | 6 ++++++ .../Compose/Components/EditorSelectionHandler.cs | 12 ++++++++++++ 2 files changed, 18 insertions(+) diff --git a/osu.Game.Rulesets.Taiko/Edit/TaikoSelectionHandler.cs b/osu.Game.Rulesets.Taiko/Edit/TaikoSelectionHandler.cs index 7ab8a54b02..ae6dced9aa 100644 --- a/osu.Game.Rulesets.Taiko/Edit/TaikoSelectionHandler.cs +++ b/osu.Game.Rulesets.Taiko/Edit/TaikoSelectionHandler.cs @@ -53,6 +53,9 @@ namespace osu.Game.Rulesets.Taiko.Edit public void SetStrongState(bool state) { + if (SelectedItems.OfType().All(h => h.IsStrong == state)) + return; + EditorBeatmap.PerformOnSelection(h => { if (!(h is Hit taikoHit)) return; @@ -67,6 +70,9 @@ namespace osu.Game.Rulesets.Taiko.Edit public void SetRimState(bool state) { + if (SelectedItems.OfType().All(h => h.Type == (state ? HitType.Rim : HitType.Centre))) + return; + EditorBeatmap.PerformOnSelection(h => { if (h is Hit taikoHit) diff --git a/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs index a73278a61e..7420362e19 100644 --- a/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/EditorSelectionHandler.cs @@ -198,6 +198,9 @@ namespace osu.Game.Screens.Edit.Compose.Components /// The name of the sample bank. public void AddSampleBank(string bankName) { + if (SelectedItems.All(h => h.Samples.All(s => s.Bank == bankName))) + return; + EditorBeatmap.PerformOnSelection(h => { if (h.Samples.All(s => s.Bank == bankName)) @@ -214,6 +217,9 @@ namespace osu.Game.Screens.Edit.Compose.Components /// The name of the hit sample. public void AddHitSample(string sampleName) { + if (SelectedItems.All(h => h.Samples.Any(s => s.Name == sampleName))) + return; + EditorBeatmap.PerformOnSelection(h => { // Make sure there isn't already an existing sample @@ -231,6 +237,9 @@ namespace osu.Game.Screens.Edit.Compose.Components /// The name of the hit sample. public void RemoveHitSample(string sampleName) { + if (SelectedItems.All(h => h.Samples.All(s => s.Name != sampleName))) + return; + EditorBeatmap.PerformOnSelection(h => { h.SamplesBindable.RemoveAll(s => s.Name == sampleName); @@ -245,6 +254,9 @@ namespace osu.Game.Screens.Edit.Compose.Components /// Throws if any selected object doesn't implement public void SetNewCombo(bool state) { + if (SelectedItems.OfType().All(h => h.NewCombo == state)) + return; + EditorBeatmap.PerformOnSelection(h => { var comboInfo = h as IHasComboInformation; From ecfcf7a2c047a589858a532e0c764b98d9fd3550 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 4 Jun 2024 10:36:30 +0200 Subject: [PATCH 129/139] Add xmldoc mention about performance overhead of `PerformOnSelection()` --- osu.Game/Screens/Edit/EditorBeatmap.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/osu.Game/Screens/Edit/EditorBeatmap.cs b/osu.Game/Screens/Edit/EditorBeatmap.cs index 7a3ea474fb..6363ed2854 100644 --- a/osu.Game/Screens/Edit/EditorBeatmap.cs +++ b/osu.Game/Screens/Edit/EditorBeatmap.cs @@ -198,6 +198,11 @@ namespace osu.Game.Screens.Edit /// Perform the provided action on every selected hitobject. /// Changes will be grouped as one history action. /// + /// + /// Note that this incurs a full state save, and as such requires the entire beatmap to be encoded, etc. + /// Very frequent use of this method (e.g. once a frame) is most discouraged. + /// If there is need to do so, use local precondition checks to eliminate changes that are known to be no-ops. + /// /// The action to perform. public void PerformOnSelection(Action action) { From 442b61da849dbca8983475803c3d4d5d1f9702a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 4 Jun 2024 15:13:50 +0200 Subject: [PATCH 130/139] Disable primary constructor related inspections I'm not actually sure whether the editorconfig incantation does anything but it's the one official sources give: https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/style-rules/ide0290 --- .editorconfig | 3 +++ osu.sln.DotSettings | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/.editorconfig b/.editorconfig index c249e5e9b3..7aecde95ee 100644 --- a/.editorconfig +++ b/.editorconfig @@ -196,6 +196,9 @@ csharp_style_prefer_switch_expression = false:none csharp_style_namespace_declarations = block_scoped:warning +#Style - C# 12 features +csharp_style_prefer_primary_constructors = false + [*.{yaml,yml}] insert_final_newline = true indent_style = space diff --git a/osu.sln.DotSettings b/osu.sln.DotSettings index 08eb264aab..04633a9348 100644 --- a/osu.sln.DotSettings +++ b/osu.sln.DotSettings @@ -82,7 +82,7 @@ WARNING WARNING HINT - HINT + DO_NOT_SHOW WARNING HINT DO_NOT_SHOW From 7dd18a84f6f6a29df941c5aa2e2d7da6d7a29020 Mon Sep 17 00:00:00 2001 From: Xesquim Date: Wed, 5 Jun 2024 12:25:33 -0300 Subject: [PATCH 131/139] Fixing the GetTotalDuration in PlaylistExtesions --- osu.Game/Online/Rooms/PlaylistExtensions.cs | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/osu.Game/Online/Rooms/PlaylistExtensions.cs b/osu.Game/Online/Rooms/PlaylistExtensions.cs index cd52a3c6e6..3171716992 100644 --- a/osu.Game/Online/Rooms/PlaylistExtensions.cs +++ b/osu.Game/Online/Rooms/PlaylistExtensions.cs @@ -1,11 +1,13 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using System.Collections.Generic; using System.Linq; using Humanizer; using Humanizer.Localisation; using osu.Framework.Bindables; +using osu.Game.Rulesets.Mods; namespace osu.Game.Online.Rooms { @@ -39,6 +41,17 @@ namespace osu.Game.Online.Rooms } public static string GetTotalDuration(this BindableList playlist) => - playlist.Select(p => p.Beatmap.Length).Sum().Milliseconds().Humanize(minUnit: TimeUnit.Second, maxUnit: TimeUnit.Hour, precision: 2); + playlist.Select(p => + { + var ruleset = p.Beatmap.Ruleset.CreateInstance(); + double rate = 1; + if (p.RequiredMods.Count() > 0) + { + List mods = p.RequiredMods.Select(mod => mod.ToMod(ruleset)).ToList(); + foreach (var mod in mods.OfType()) + rate = mod.ApplyToRate(0, rate); + } + return p.Beatmap.Length / rate; + }).Sum().Milliseconds().Humanize(minUnit: TimeUnit.Second, maxUnit: TimeUnit.Hour, precision: 2); } } From 860afb812367c21a88f667ad1d9431f75dab26ab Mon Sep 17 00:00:00 2001 From: Xesquim Date: Thu, 6 Jun 2024 10:06:07 -0300 Subject: [PATCH 132/139] Creating method in ModUtils to calculate the rate for the song --- .../Beatmaps/Drawables/DifficultyIconTooltip.cs | 9 ++------- osu.Game/Online/Rooms/PlaylistExtensions.cs | 12 +++++------- .../Overlays/Mods/BeatmapAttributesDisplay.cs | 4 +--- osu.Game/Screens/Select/BeatmapInfoWedge.cs | 4 +--- osu.Game/Screens/Select/Details/AdvancedStats.cs | 5 ++--- osu.Game/Utils/ModUtils.cs | 16 ++++++++++++++++ 6 files changed, 27 insertions(+), 23 deletions(-) diff --git a/osu.Game/Beatmaps/Drawables/DifficultyIconTooltip.cs b/osu.Game/Beatmaps/Drawables/DifficultyIconTooltip.cs index 1f3dcfee8c..a5cac69afd 100644 --- a/osu.Game/Beatmaps/Drawables/DifficultyIconTooltip.cs +++ b/osu.Game/Beatmaps/Drawables/DifficultyIconTooltip.cs @@ -13,6 +13,7 @@ using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; +using osu.Game.Utils; using osuTK; namespace osu.Game.Beatmaps.Drawables @@ -123,13 +124,7 @@ namespace osu.Game.Beatmaps.Drawables difficultyFillFlowContainer.Show(); miscFillFlowContainer.Show(); - double rate = 1; - - if (displayedContent.Mods != null) - { - foreach (var mod in displayedContent.Mods.OfType()) - rate = mod.ApplyToRate(0, rate); - } + double rate = ModUtils.CalculateRateWithMods(displayedContent.Mods); double bpmAdjusted = displayedContent.BeatmapInfo.BPM * rate; diff --git a/osu.Game/Online/Rooms/PlaylistExtensions.cs b/osu.Game/Online/Rooms/PlaylistExtensions.cs index 3171716992..4bcaaa8131 100644 --- a/osu.Game/Online/Rooms/PlaylistExtensions.cs +++ b/osu.Game/Online/Rooms/PlaylistExtensions.cs @@ -8,6 +8,7 @@ using Humanizer; using Humanizer.Localisation; using osu.Framework.Bindables; using osu.Game.Rulesets.Mods; +using osu.Game.Utils; namespace osu.Game.Online.Rooms { @@ -40,17 +41,14 @@ namespace osu.Game.Online.Rooms : GetUpcomingItems(playlist).First(); } + /// + /// Returns the total duration from the in playlist order from the supplied , + /// public static string GetTotalDuration(this BindableList playlist) => playlist.Select(p => { var ruleset = p.Beatmap.Ruleset.CreateInstance(); - double rate = 1; - if (p.RequiredMods.Count() > 0) - { - List mods = p.RequiredMods.Select(mod => mod.ToMod(ruleset)).ToList(); - foreach (var mod in mods.OfType()) - rate = mod.ApplyToRate(0, rate); - } + double rate = ModUtils.CalculateRateWithMods(p.RequiredMods.Select(mod => mod.ToMod(ruleset)).ToList()); return p.Beatmap.Length / rate; }).Sum().Milliseconds().Humanize(minUnit: TimeUnit.Second, maxUnit: TimeUnit.Hour, precision: 2); } diff --git a/osu.Game/Overlays/Mods/BeatmapAttributesDisplay.cs b/osu.Game/Overlays/Mods/BeatmapAttributesDisplay.cs index 5b10a2844e..b625de27f8 100644 --- a/osu.Game/Overlays/Mods/BeatmapAttributesDisplay.cs +++ b/osu.Game/Overlays/Mods/BeatmapAttributesDisplay.cs @@ -165,9 +165,7 @@ namespace osu.Game.Overlays.Mods starRatingDisplay.FinishTransforms(true); }); - double rate = 1; - foreach (var mod in Mods.Value.OfType()) - rate = mod.ApplyToRate(0, rate); + double rate = ModUtils.CalculateRateWithMods(Mods.Value.ToList()); bpmDisplay.Current.Value = FormatUtils.RoundBPM(BeatmapInfo.Value.BPM, rate); diff --git a/osu.Game/Screens/Select/BeatmapInfoWedge.cs b/osu.Game/Screens/Select/BeatmapInfoWedge.cs index 3cab4b67b6..0e2d1db6b7 100644 --- a/osu.Game/Screens/Select/BeatmapInfoWedge.cs +++ b/osu.Game/Screens/Select/BeatmapInfoWedge.cs @@ -402,9 +402,7 @@ namespace osu.Game.Screens.Select return; // this doesn't consider mods which apply variable rates, yet. - double rate = 1; - foreach (var mod in mods.Value.OfType()) - rate = mod.ApplyToRate(0, rate); + double rate = ModUtils.CalculateRateWithMods(mods.Value.ToList()); int bpmMax = FormatUtils.RoundBPM(beatmap.ControlPointInfo.BPMMaximum, rate); int bpmMin = FormatUtils.RoundBPM(beatmap.ControlPointInfo.BPMMinimum, rate); diff --git a/osu.Game/Screens/Select/Details/AdvancedStats.cs b/osu.Game/Screens/Select/Details/AdvancedStats.cs index cb820f4da9..1da890100e 100644 --- a/osu.Game/Screens/Select/Details/AdvancedStats.cs +++ b/osu.Game/Screens/Select/Details/AdvancedStats.cs @@ -27,6 +27,7 @@ using osu.Game.Configuration; using osu.Game.Resources.Localisation.Web; using osu.Game.Rulesets; using osu.Game.Overlays.Mods; +using osu.Game.Utils; namespace osu.Game.Screens.Select.Details { @@ -179,9 +180,7 @@ namespace osu.Game.Screens.Select.Details if (Ruleset.Value != null) { - double rate = 1; - foreach (var mod in mods.Value.OfType()) - rate = mod.ApplyToRate(0, rate); + double rate = ModUtils.CalculateRateWithMods(mods.Value); adjustedDifficulty = Ruleset.Value.CreateInstance().GetRateAdjustedDisplayDifficulty(originalDifficulty, rate); diff --git a/osu.Game/Utils/ModUtils.cs b/osu.Game/Utils/ModUtils.cs index 2c9eef41e3..3378e94ec0 100644 --- a/osu.Game/Utils/ModUtils.cs +++ b/osu.Game/Utils/ModUtils.cs @@ -276,5 +276,21 @@ namespace osu.Game.Utils return scoreMultiplier.ToLocalisableString("0.00x"); } + + /// + /// Calculate the rate for the song with the selected mods. + /// + /// The list of selected mods. + /// The rate with mods. + public static double CalculateRateWithMods(IEnumerable mods) + { + double rate = 1; + if (mods != null) + { + foreach (var mod in mods.OfType()) + rate = mod.ApplyToRate(0, rate); + } + return rate; + } } } From dd3f4bcdab623c3c7563d7cc191dc060cc518d37 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 6 Jun 2024 23:59:15 +0800 Subject: [PATCH 133/139] Fix code quality and null handling --- osu.Game/Beatmaps/Drawables/DifficultyIconTooltip.cs | 4 +++- osu.Game/Online/Rooms/PlaylistExtensions.cs | 4 +--- osu.Game/Overlays/Mods/BeatmapAttributesDisplay.cs | 2 +- osu.Game/Screens/Select/BeatmapInfoWedge.cs | 2 +- osu.Game/Utils/ModUtils.cs | 9 ++++----- 5 files changed, 10 insertions(+), 11 deletions(-) diff --git a/osu.Game/Beatmaps/Drawables/DifficultyIconTooltip.cs b/osu.Game/Beatmaps/Drawables/DifficultyIconTooltip.cs index a5cac69afd..36ddb6030e 100644 --- a/osu.Game/Beatmaps/Drawables/DifficultyIconTooltip.cs +++ b/osu.Game/Beatmaps/Drawables/DifficultyIconTooltip.cs @@ -124,7 +124,9 @@ namespace osu.Game.Beatmaps.Drawables difficultyFillFlowContainer.Show(); miscFillFlowContainer.Show(); - double rate = ModUtils.CalculateRateWithMods(displayedContent.Mods); + double rate = 1; + if (displayedContent.Mods != null) + rate = ModUtils.CalculateRateWithMods(displayedContent.Mods); double bpmAdjusted = displayedContent.BeatmapInfo.BPM * rate; diff --git a/osu.Game/Online/Rooms/PlaylistExtensions.cs b/osu.Game/Online/Rooms/PlaylistExtensions.cs index 4bcaaa8131..003fd23d40 100644 --- a/osu.Game/Online/Rooms/PlaylistExtensions.cs +++ b/osu.Game/Online/Rooms/PlaylistExtensions.cs @@ -1,13 +1,11 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; using System.Collections.Generic; using System.Linq; using Humanizer; using Humanizer.Localisation; using osu.Framework.Bindables; -using osu.Game.Rulesets.Mods; using osu.Game.Utils; namespace osu.Game.Online.Rooms @@ -48,7 +46,7 @@ namespace osu.Game.Online.Rooms playlist.Select(p => { var ruleset = p.Beatmap.Ruleset.CreateInstance(); - double rate = ModUtils.CalculateRateWithMods(p.RequiredMods.Select(mod => mod.ToMod(ruleset)).ToList()); + double rate = ModUtils.CalculateRateWithMods(p.RequiredMods.Select(mod => mod.ToMod(ruleset))); return p.Beatmap.Length / rate; }).Sum().Milliseconds().Humanize(minUnit: TimeUnit.Second, maxUnit: TimeUnit.Hour, precision: 2); } diff --git a/osu.Game/Overlays/Mods/BeatmapAttributesDisplay.cs b/osu.Game/Overlays/Mods/BeatmapAttributesDisplay.cs index b625de27f8..1f4e007f47 100644 --- a/osu.Game/Overlays/Mods/BeatmapAttributesDisplay.cs +++ b/osu.Game/Overlays/Mods/BeatmapAttributesDisplay.cs @@ -165,7 +165,7 @@ namespace osu.Game.Overlays.Mods starRatingDisplay.FinishTransforms(true); }); - double rate = ModUtils.CalculateRateWithMods(Mods.Value.ToList()); + double rate = ModUtils.CalculateRateWithMods(Mods.Value); bpmDisplay.Current.Value = FormatUtils.RoundBPM(BeatmapInfo.Value.BPM, rate); diff --git a/osu.Game/Screens/Select/BeatmapInfoWedge.cs b/osu.Game/Screens/Select/BeatmapInfoWedge.cs index 0e2d1db6b7..02682c1851 100644 --- a/osu.Game/Screens/Select/BeatmapInfoWedge.cs +++ b/osu.Game/Screens/Select/BeatmapInfoWedge.cs @@ -402,7 +402,7 @@ namespace osu.Game.Screens.Select return; // this doesn't consider mods which apply variable rates, yet. - double rate = ModUtils.CalculateRateWithMods(mods.Value.ToList()); + double rate = ModUtils.CalculateRateWithMods(mods.Value); int bpmMax = FormatUtils.RoundBPM(beatmap.ControlPointInfo.BPMMaximum, rate); int bpmMin = FormatUtils.RoundBPM(beatmap.ControlPointInfo.BPMMinimum, rate); diff --git a/osu.Game/Utils/ModUtils.cs b/osu.Game/Utils/ModUtils.cs index 3378e94ec0..f901f15388 100644 --- a/osu.Game/Utils/ModUtils.cs +++ b/osu.Game/Utils/ModUtils.cs @@ -285,11 +285,10 @@ namespace osu.Game.Utils public static double CalculateRateWithMods(IEnumerable mods) { double rate = 1; - if (mods != null) - { - foreach (var mod in mods.OfType()) - rate = mod.ApplyToRate(0, rate); - } + + foreach (var mod in mods.OfType()) + rate = mod.ApplyToRate(0, rate); + return rate; } } From 6e3bea938e1ecfe255bc4dcfe558870007e8cb6e Mon Sep 17 00:00:00 2001 From: Xesquim Date: Thu, 6 Jun 2024 13:26:52 -0300 Subject: [PATCH 134/139] Instancing a Ruleset only when it's necessary to --- osu.Game/Online/Rooms/PlaylistExtensions.cs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/osu.Game/Online/Rooms/PlaylistExtensions.cs b/osu.Game/Online/Rooms/PlaylistExtensions.cs index 003fd23d40..5a5950333b 100644 --- a/osu.Game/Online/Rooms/PlaylistExtensions.cs +++ b/osu.Game/Online/Rooms/PlaylistExtensions.cs @@ -6,6 +6,7 @@ using System.Linq; using Humanizer; using Humanizer.Localisation; using osu.Framework.Bindables; +using osu.Game.Rulesets.Mods; using osu.Game.Utils; namespace osu.Game.Online.Rooms @@ -45,8 +46,13 @@ namespace osu.Game.Online.Rooms public static string GetTotalDuration(this BindableList playlist) => playlist.Select(p => { - var ruleset = p.Beatmap.Ruleset.CreateInstance(); - double rate = ModUtils.CalculateRateWithMods(p.RequiredMods.Select(mod => mod.ToMod(ruleset))); + IEnumerable modList = []; + if (p.RequiredMods.Length > 0) + { + var ruleset = p.Beatmap.Ruleset.CreateInstance(); + modList = p.RequiredMods.Select(mod => mod.ToMod(ruleset)); + } + double rate = ModUtils.CalculateRateWithMods(modList); return p.Beatmap.Length / rate; }).Sum().Milliseconds().Humanize(minUnit: TimeUnit.Second, maxUnit: TimeUnit.Hour, precision: 2); } From 7cbe93efc3dcef9389922f034b12a92c6777aca7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 7 Jun 2024 10:37:27 +0800 Subject: [PATCH 135/139] Refactor latest changes to avoid unnecessary call when mods not present --- osu.Game/Online/Rooms/PlaylistExtensions.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Online/Rooms/PlaylistExtensions.cs b/osu.Game/Online/Rooms/PlaylistExtensions.cs index 5a5950333b..e9a0519f3d 100644 --- a/osu.Game/Online/Rooms/PlaylistExtensions.cs +++ b/osu.Game/Online/Rooms/PlaylistExtensions.cs @@ -6,7 +6,6 @@ using System.Linq; using Humanizer; using Humanizer.Localisation; using osu.Framework.Bindables; -using osu.Game.Rulesets.Mods; using osu.Game.Utils; namespace osu.Game.Online.Rooms @@ -46,13 +45,14 @@ namespace osu.Game.Online.Rooms public static string GetTotalDuration(this BindableList playlist) => playlist.Select(p => { - IEnumerable modList = []; + double rate = 1; + if (p.RequiredMods.Length > 0) { var ruleset = p.Beatmap.Ruleset.CreateInstance(); - modList = p.RequiredMods.Select(mod => mod.ToMod(ruleset)); + rate = ModUtils.CalculateRateWithMods(p.RequiredMods.Select(mod => mod.ToMod(ruleset))); } - double rate = ModUtils.CalculateRateWithMods(modList); + return p.Beatmap.Length / rate; }).Sum().Milliseconds().Humanize(minUnit: TimeUnit.Second, maxUnit: TimeUnit.Hour, precision: 2); } From 199d31c0f4f68b1049cd0692d172273f57689bda Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 7 Jun 2024 09:54:00 +0200 Subject: [PATCH 136/139] Fix test not compiling A little ugly but maybe it'll do... --- .../Gameplay/TestSceneSkinnableRankDisplay.cs | 24 +++++++++++-------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableRankDisplay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableRankDisplay.cs index dc8b3d994b..d442e69c61 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableRankDisplay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableRankDisplay.cs @@ -3,9 +3,11 @@ using NUnit.Framework; using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Scoring; +using osu.Game.Scoring; using osu.Game.Screens.Play.HUD; using osu.Game.Skinning; @@ -16,6 +18,8 @@ namespace osu.Game.Tests.Visual.Gameplay [Cached] private ScoreProcessor scoreProcessor = new ScoreProcessor(new OsuRuleset()); + private Bindable rank => (Bindable)scoreProcessor.Rank; + protected override Drawable CreateDefaultImplementation() => new DefaultRankDisplay(); protected override Drawable CreateLegacyImplementation() => new LegacyRankDisplay(); @@ -23,15 +27,15 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestChangingRank() { - AddStep("Set rank to SS Hidden", () => scoreProcessor.Rank.Value = Scoring.ScoreRank.XH); - AddStep("Set rank to SS", () => scoreProcessor.Rank.Value = Scoring.ScoreRank.X); - AddStep("Set rank to S Hidden", () => scoreProcessor.Rank.Value = Scoring.ScoreRank.SH); - AddStep("Set rank to S", () => scoreProcessor.Rank.Value = Scoring.ScoreRank.S); - AddStep("Set rank to A", () => scoreProcessor.Rank.Value = Scoring.ScoreRank.A); - AddStep("Set rank to B", () => scoreProcessor.Rank.Value = Scoring.ScoreRank.B); - AddStep("Set rank to C", () => scoreProcessor.Rank.Value = Scoring.ScoreRank.C); - AddStep("Set rank to D", () => scoreProcessor.Rank.Value = Scoring.ScoreRank.D); - AddStep("Set rank to F", () => scoreProcessor.Rank.Value = Scoring.ScoreRank.F); + AddStep("Set rank to SS Hidden", () => rank.Value = ScoreRank.XH); + AddStep("Set rank to SS", () => rank.Value = ScoreRank.X); + AddStep("Set rank to S Hidden", () => rank.Value = ScoreRank.SH); + AddStep("Set rank to S", () => rank.Value = ScoreRank.S); + AddStep("Set rank to A", () => rank.Value = ScoreRank.A); + AddStep("Set rank to B", () => rank.Value = ScoreRank.B); + AddStep("Set rank to C", () => rank.Value = ScoreRank.C); + AddStep("Set rank to D", () => rank.Value = ScoreRank.D); + AddStep("Set rank to F", () => rank.Value = ScoreRank.F); } } -} \ No newline at end of file +} From 72890bb9acf9ab6b3733dd0d3ede0ad6961a45a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 7 Jun 2024 09:54:27 +0200 Subject: [PATCH 137/139] Add stable-like animation legacy rank display Just substituting the sprite felt pretty terrible. --- osu.Game/Skinning/LegacyRankDisplay.cs | 33 +++++++++++++++++++++++--- 1 file changed, 30 insertions(+), 3 deletions(-) diff --git a/osu.Game/Skinning/LegacyRankDisplay.cs b/osu.Game/Skinning/LegacyRankDisplay.cs index 38ece4e5e4..71d487eade 100644 --- a/osu.Game/Skinning/LegacyRankDisplay.cs +++ b/osu.Game/Skinning/LegacyRankDisplay.cs @@ -6,6 +6,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; using osu.Game.Rulesets.Scoring; +using osuTK; namespace osu.Game.Skinning { @@ -25,12 +26,38 @@ namespace osu.Game.Skinning { AutoSizeAxes = Axes.Both; - AddInternal(rank = new Sprite()); + AddInternal(rank = new Sprite + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }); } protected override void LoadComplete() { - scoreProcessor.Rank.BindValueChanged(v => rank.Texture = source.GetTexture($"ranking-{v.NewValue}-small"), true); + scoreProcessor.Rank.BindValueChanged(v => + { + var texture = source.GetTexture($"ranking-{v.NewValue}-small"); + + rank.Texture = texture; + + if (texture != null) + { + var transientRank = new Sprite + { + Texture = texture, + Blending = BlendingParameters.Additive, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + BypassAutoSizeAxes = Axes.Both, + }; + AddInternal(transientRank); + transientRank.FadeOutFromOne(1200, Easing.Out) + .ScaleTo(new Vector2(1.625f), 1200, Easing.Out) + .Expire(); + } + }, true); + FinishTransforms(true); } } -} \ No newline at end of file +} From 366ef64a2c12e1f7f8a1ff7c975fe0fe5ed7ac90 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 7 Jun 2024 16:54:12 +0800 Subject: [PATCH 138/139] Apply NRT to `UpdateableRank` --- osu.Game/Online/Leaderboards/UpdateableRank.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/osu.Game/Online/Leaderboards/UpdateableRank.cs b/osu.Game/Online/Leaderboards/UpdateableRank.cs index 46cfe8ec65..717adee79d 100644 --- a/osu.Game/Online/Leaderboards/UpdateableRank.cs +++ b/osu.Game/Online/Leaderboards/UpdateableRank.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. -#nullable disable - using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Scoring; @@ -22,7 +20,7 @@ namespace osu.Game.Online.Leaderboards Rank = rank; } - protected override Drawable CreateDrawable(ScoreRank? rank) + protected override Drawable? CreateDrawable(ScoreRank? rank) { if (rank.HasValue) { From 9c6e707f00454f9370a8ee3b0424903c9c9b917c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 7 Jun 2024 17:04:16 +0800 Subject: [PATCH 139/139] Adjust transitions --- .../Online/Leaderboards/UpdateableRank.cs | 28 +++++++++++++++++++ osu.Game/Skinning/LegacyRankDisplay.cs | 4 +-- 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/osu.Game/Online/Leaderboards/UpdateableRank.cs b/osu.Game/Online/Leaderboards/UpdateableRank.cs index 717adee79d..b64fab6861 100644 --- a/osu.Game/Online/Leaderboards/UpdateableRank.cs +++ b/osu.Game/Online/Leaderboards/UpdateableRank.cs @@ -1,14 +1,19 @@ // 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.Framework.Graphics.Transforms; using osu.Game.Scoring; namespace osu.Game.Online.Leaderboards { public partial class UpdateableRank : ModelBackedDrawable { + protected override double TransformDuration => 600; + protected override bool TransformImmediately => true; + public ScoreRank? Rank { get => Model; @@ -20,6 +25,16 @@ namespace osu.Game.Online.Leaderboards Rank = rank; } + protected override DelayedLoadWrapper CreateDelayedLoadWrapper(Func createContentFunc, double timeBeforeLoad) + { + return base.CreateDelayedLoadWrapper(createContentFunc, timeBeforeLoad) + .With(w => + { + w.Anchor = Anchor.Centre; + w.Origin = Anchor.Centre; + }); + } + protected override Drawable? CreateDrawable(ScoreRank? rank) { if (rank.HasValue) @@ -33,5 +48,18 @@ namespace osu.Game.Online.Leaderboards return null; } + + protected override TransformSequence ApplyShowTransforms(Drawable drawable) + { + drawable.ScaleTo(1); + return base.ApplyShowTransforms(drawable); + } + + protected override TransformSequence ApplyHideTransforms(Drawable drawable) + { + drawable.ScaleTo(1.8f, TransformDuration, Easing.Out); + + return base.ApplyHideTransforms(drawable); + } } } diff --git a/osu.Game/Skinning/LegacyRankDisplay.cs b/osu.Game/Skinning/LegacyRankDisplay.cs index 71d487eade..70b5ed0278 100644 --- a/osu.Game/Skinning/LegacyRankDisplay.cs +++ b/osu.Game/Skinning/LegacyRankDisplay.cs @@ -52,8 +52,8 @@ namespace osu.Game.Skinning BypassAutoSizeAxes = Axes.Both, }; AddInternal(transientRank); - transientRank.FadeOutFromOne(1200, Easing.Out) - .ScaleTo(new Vector2(1.625f), 1200, Easing.Out) + transientRank.FadeOutFromOne(500, Easing.Out) + .ScaleTo(new Vector2(1.625f), 500, Easing.Out) .Expire(); } }, true);