From cabbc486e9d1940f4d95429c6691758ff7ee6b85 Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Fri, 1 Apr 2022 11:36:20 +0800 Subject: [PATCH 001/395] Rotate sliders in random mod --- .../Utils/OsuHitObjectGenerationUtils.cs | 35 +++++++++ .../OsuHitObjectGenerationUtils_Reposition.cs | 72 +++++++++++++++++-- 2 files changed, 103 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils.cs b/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils.cs index da73c2addb..19d3390f56 100644 --- a/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils.cs +++ b/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils.cs @@ -146,5 +146,40 @@ namespace osu.Game.Rulesets.Osu.Utils slider.Path = new SliderPath(controlPoints, slider.Path.ExpectedDistance.Value); } + + /// + /// Rotate a slider about its start position by the specified angle. + /// + /// The slider to be rotated. + /// The angle to rotate the slider by. + public static void RotateSlider(Slider slider, float rotation) + { + void rotateNestedObject(OsuHitObject nested) => nested.Position = rotateVector(nested.Position - slider.Position, rotation) + slider.Position; + + slider.NestedHitObjects.OfType().ForEach(rotateNestedObject); + slider.NestedHitObjects.OfType().ForEach(rotateNestedObject); + + var controlPoints = slider.Path.ControlPoints.Select(p => new PathControlPoint(p.Position, p.Type)).ToArray(); + foreach (var point in controlPoints) + point.Position = rotateVector(point.Position, rotation); + + slider.Path = new SliderPath(controlPoints, slider.Path.ExpectedDistance.Value); + } + + /// + /// Rotate a vector by the specified angle. + /// + /// The vector to be rotated. + /// The angle to rotate the vector by. + /// The rotated vector. + private static Vector2 rotateVector(Vector2 vector, float rotation) + { + float angle = (float)Math.Atan2(vector.Y, vector.X) + rotation; + float length = vector.Length; + return new Vector2( + length * (float)Math.Cos(angle), + length * (float)Math.Sin(angle) + ); + } } } diff --git a/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils_Reposition.cs b/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils_Reposition.cs index d1bc3b45df..ef1c258a8d 100644 --- a/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils_Reposition.cs +++ b/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils_Reposition.cs @@ -40,12 +40,21 @@ namespace osu.Game.Rulesets.Osu.Utils float absoluteAngle = (float)Math.Atan2(relativePosition.Y, relativePosition.X); float relativeAngle = absoluteAngle - previousAngle; - positionInfos.Add(new ObjectPositionInfo(hitObject) + ObjectPositionInfo positionInfo; + positionInfos.Add(positionInfo = new ObjectPositionInfo(hitObject) { RelativeAngle = relativeAngle, DistanceFromPrevious = relativePosition.Length }); + if (hitObject is Slider) + { + var endPositionVector = hitObject.EndPosition - hitObject.Position; + float absoluteRotation = (float)Math.Atan2(endPositionVector.Y, endPositionVector.X); + positionInfo.Rotation = absoluteRotation - absoluteAngle; + absoluteAngle = absoluteRotation; + } + previousPosition = hitObject.EndPosition; previousAngle = absoluteAngle; } @@ -124,9 +133,16 @@ namespace osu.Game.Rulesets.Osu.Utils if (previous != null) { - Vector2 earliestPosition = beforePrevious?.HitObject.EndPosition ?? playfield_centre; - Vector2 relativePosition = previous.HitObject.Position - earliestPosition; - previousAbsoluteAngle = (float)Math.Atan2(relativePosition.Y, relativePosition.X); + if (previous.HitObject is Slider s) + { + previousAbsoluteAngle = getSliderRotation(s); + } + else + { + Vector2 earliestPosition = beforePrevious?.HitObject.EndPosition ?? playfield_centre; + Vector2 relativePosition = previous.HitObject.Position - earliestPosition; + previousAbsoluteAngle = (float)Math.Atan2(relativePosition.Y, relativePosition.X); + } } float absoluteAngle = previousAbsoluteAngle + current.PositionInfo.RelativeAngle; @@ -141,6 +157,16 @@ namespace osu.Game.Rulesets.Osu.Utils posRelativeToPrev = RotateAwayFromEdge(lastEndPosition, posRelativeToPrev); current.PositionModified = lastEndPosition + posRelativeToPrev; + + if (!(current.HitObject is Slider slider)) + return; + + Vector2 centreOfMassOriginal = calculateCentreOfMass(slider); + Vector2 centreOfMassModified = rotateVector(centreOfMassOriginal, current.PositionInfo.Rotation - current.RotationOriginal); + centreOfMassModified = RotateAwayFromEdge(current.PositionModified, centreOfMassModified); + + float relativeRotation = (float)Math.Atan2(centreOfMassModified.Y, centreOfMassModified.X) - (float)Math.Atan2(centreOfMassOriginal.Y, centreOfMassOriginal.X); + RotateSlider(slider, relativeRotation); } /// @@ -287,6 +313,27 @@ namespace osu.Game.Rulesets.Osu.Utils ); } + private static Vector2 calculateCentreOfMass(Slider slider) + { + int count = 0; + Vector2 sum = Vector2.Zero; + double pathDistance = slider.Distance; + + for (double i = 0; i < pathDistance; i++) + { + sum += slider.Path.PositionAt(i / pathDistance); + count++; + } + + return sum / count; + } + + private static float getSliderRotation(Slider slider) + { + var endPositionVector = slider.EndPosition - slider.Position; + return (float)Math.Atan2(endPositionVector.Y, endPositionVector.X); + } + public class ObjectPositionInfo { /// @@ -309,6 +356,13 @@ namespace osu.Game.Rulesets.Osu.Utils /// public float DistanceFromPrevious { get; set; } + /// + /// The rotation of the hit object, relative to its jump angle. + /// For sliders, this is defined as the angle from the slider's start position to its end position, relative to its jump angle. + /// For hit circles and spinners, this property is ignored. + /// + public float Rotation { get; set; } + /// /// The hit object associated with this . /// @@ -325,6 +379,7 @@ namespace osu.Game.Rulesets.Osu.Utils public Vector2 PositionOriginal { get; } public Vector2 PositionModified { get; set; } public Vector2 EndPositionModified { get; set; } + public float RotationOriginal { get; } public ObjectPositionInfo PositionInfo { get; } public OsuHitObject HitObject => PositionInfo.HitObject; @@ -334,6 +389,15 @@ namespace osu.Game.Rulesets.Osu.Utils PositionInfo = positionInfo; PositionModified = PositionOriginal = HitObject.Position; EndPositionModified = HitObject.EndPosition; + + if (HitObject is Slider slider) + { + RotationOriginal = getSliderRotation(slider); + } + else + { + RotationOriginal = 0; + } } } } From 998df5a4fef7627a897c23afa4f8b57c01e2a6f7 Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Fri, 1 Apr 2022 11:37:10 +0800 Subject: [PATCH 002/395] Fix large slider clamping --- .../Utils/OsuHitObjectGenerationUtils_Reposition.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils_Reposition.cs b/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils_Reposition.cs index ef1c258a8d..9f308df985 100644 --- a/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils_Reposition.cs +++ b/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils_Reposition.cs @@ -198,13 +198,13 @@ namespace osu.Game.Rulesets.Osu.Utils var previousPosition = workingObject.PositionModified; // Clamp slider position to the placement area - // If the slider is larger than the playfield, force it to stay at the original position + // If the slider is larger than the playfield, at least make sure that the head circle is inside the playfield float newX = possibleMovementBounds.Width < 0 - ? workingObject.PositionOriginal.X + ? Math.Clamp(possibleMovementBounds.Left, 0, OsuPlayfield.BASE_SIZE.X) : Math.Clamp(previousPosition.X, possibleMovementBounds.Left, possibleMovementBounds.Right); float newY = possibleMovementBounds.Height < 0 - ? workingObject.PositionOriginal.Y + ? Math.Clamp(possibleMovementBounds.Top, 0, OsuPlayfield.BASE_SIZE.Y) : Math.Clamp(previousPosition.Y, possibleMovementBounds.Top, possibleMovementBounds.Bottom); slider.Position = workingObject.PositionModified = new Vector2(newX, newY); From af3835083ccd246810758b0156adbca2b3117587 Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Fri, 1 Apr 2022 11:41:45 +0800 Subject: [PATCH 003/395] Fix slider relative rotation calculation --- .../OsuHitObjectGenerationUtils_Reposition.cs | 19 +++++-------------- 1 file changed, 5 insertions(+), 14 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils_Reposition.cs b/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils_Reposition.cs index 9f308df985..fe5841daac 100644 --- a/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils_Reposition.cs +++ b/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils_Reposition.cs @@ -47,10 +47,9 @@ namespace osu.Game.Rulesets.Osu.Utils DistanceFromPrevious = relativePosition.Length }); - if (hitObject is Slider) + if (hitObject is Slider slider) { - var endPositionVector = hitObject.EndPosition - hitObject.Position; - float absoluteRotation = (float)Math.Atan2(endPositionVector.Y, endPositionVector.X); + float absoluteRotation = getSliderRotation(slider); positionInfo.Rotation = absoluteRotation - absoluteAngle; absoluteAngle = absoluteRotation; } @@ -161,8 +160,10 @@ namespace osu.Game.Rulesets.Osu.Utils if (!(current.HitObject is Slider slider)) return; + absoluteAngle = (float)Math.Atan2(posRelativeToPrev.Y, posRelativeToPrev.X); + Vector2 centreOfMassOriginal = calculateCentreOfMass(slider); - Vector2 centreOfMassModified = rotateVector(centreOfMassOriginal, current.PositionInfo.Rotation - current.RotationOriginal); + Vector2 centreOfMassModified = rotateVector(centreOfMassOriginal, current.PositionInfo.Rotation + absoluteAngle - getSliderRotation(slider)); centreOfMassModified = RotateAwayFromEdge(current.PositionModified, centreOfMassModified); float relativeRotation = (float)Math.Atan2(centreOfMassModified.Y, centreOfMassModified.X) - (float)Math.Atan2(centreOfMassOriginal.Y, centreOfMassOriginal.X); @@ -379,7 +380,6 @@ namespace osu.Game.Rulesets.Osu.Utils public Vector2 PositionOriginal { get; } public Vector2 PositionModified { get; set; } public Vector2 EndPositionModified { get; set; } - public float RotationOriginal { get; } public ObjectPositionInfo PositionInfo { get; } public OsuHitObject HitObject => PositionInfo.HitObject; @@ -389,15 +389,6 @@ namespace osu.Game.Rulesets.Osu.Utils PositionInfo = positionInfo; PositionModified = PositionOriginal = HitObject.Position; EndPositionModified = HitObject.EndPosition; - - if (HitObject is Slider slider) - { - RotationOriginal = getSliderRotation(slider); - } - else - { - RotationOriginal = 0; - } } } } From c0a78924aa2e5fe9ea9f6fe671e7fa770e472cac Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Fri, 1 Apr 2022 11:47:21 +0800 Subject: [PATCH 004/395] Fix generation for zero-length sliders --- .../Utils/OsuHitObjectGenerationUtils_Reposition.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils_Reposition.cs b/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils_Reposition.cs index fe5841daac..5f3719146f 100644 --- a/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils_Reposition.cs +++ b/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils_Reposition.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.Linq; using osu.Framework.Graphics.Primitives; +using osu.Framework.Utils; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.UI; using osuTK; @@ -167,7 +168,8 @@ namespace osu.Game.Rulesets.Osu.Utils centreOfMassModified = RotateAwayFromEdge(current.PositionModified, centreOfMassModified); float relativeRotation = (float)Math.Atan2(centreOfMassModified.Y, centreOfMassModified.X) - (float)Math.Atan2(centreOfMassOriginal.Y, centreOfMassOriginal.X); - RotateSlider(slider, relativeRotation); + if (!Precision.AlmostEquals(relativeRotation, 0)) + RotateSlider(slider, relativeRotation); } /// @@ -316,6 +318,8 @@ namespace osu.Game.Rulesets.Osu.Utils private static Vector2 calculateCentreOfMass(Slider slider) { + if (slider.Distance < 1) return Vector2.Zero; + int count = 0; Vector2 sum = Vector2.Zero; double pathDistance = slider.Distance; From 0015f627b04a23f407ff514255650003fb610c0b Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Fri, 1 Apr 2022 11:49:27 +0800 Subject: [PATCH 005/395] Add xmldoc --- .../Utils/OsuHitObjectGenerationUtils_Reposition.cs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils_Reposition.cs b/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils_Reposition.cs index 5f3719146f..ccc2529768 100644 --- a/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils_Reposition.cs +++ b/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils_Reposition.cs @@ -316,6 +316,11 @@ namespace osu.Game.Rulesets.Osu.Utils ); } + /// + /// Estimate the centre of mass of a slider relative to its start position. + /// + /// The slider to process. + /// The centre of mass of the slider. private static Vector2 calculateCentreOfMass(Slider slider) { if (slider.Distance < 1) return Vector2.Zero; @@ -333,6 +338,11 @@ namespace osu.Game.Rulesets.Osu.Utils return sum / count; } + /// + /// Get the absolute rotation of a slider, defined as the angle from its start position to its end position. + /// + /// The slider to process. + /// The angle in radians. private static float getSliderRotation(Slider slider) { var endPositionVector = slider.EndPosition - slider.Position; From 031a977009d466796eb90aa9386d24ce16410f6c Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Fri, 1 Apr 2022 11:50:30 +0800 Subject: [PATCH 006/395] Calculate slider rotation using end point of path instead of EndPosition --- .../Utils/OsuHitObjectGenerationUtils_Reposition.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils_Reposition.cs b/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils_Reposition.cs index ccc2529768..45285e5e0c 100644 --- a/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils_Reposition.cs +++ b/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils_Reposition.cs @@ -339,13 +339,13 @@ namespace osu.Game.Rulesets.Osu.Utils } /// - /// Get the absolute rotation of a slider, defined as the angle from its start position to its end position. + /// Get the absolute rotation of a slider, defined as the angle from its start position to the end of its path. /// /// The slider to process. /// The angle in radians. private static float getSliderRotation(Slider slider) { - var endPositionVector = slider.EndPosition - slider.Position; + var endPositionVector = slider.Path.PositionAt(1); return (float)Math.Atan2(endPositionVector.Y, endPositionVector.X); } @@ -373,7 +373,7 @@ namespace osu.Game.Rulesets.Osu.Utils /// /// The rotation of the hit object, relative to its jump angle. - /// For sliders, this is defined as the angle from the slider's start position to its end position, relative to its jump angle. + /// For sliders, this is defined as the angle from the slider's start position to the end of its path, relative to its jump angle. /// For hit circles and spinners, this property is ignored. /// public float Rotation { get; set; } From ee6567788425bd24b198493bdd7f565fd6f0b4a5 Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Fri, 1 Apr 2022 11:57:45 +0800 Subject: [PATCH 007/395] Use height of playfield instead of width when randomizing the first object This is the change discussed in #17194. The effect of this change is barely noticeable, but it makes more sense to generate the object within playfield from the start. --- osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs index fea9246035..ccc56bd64f 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs @@ -48,7 +48,7 @@ namespace osu.Game.Rulesets.Osu.Mods if (positionInfo == positionInfos.First()) { - positionInfo.DistanceFromPrevious = (float)(rng.NextDouble() * OsuPlayfield.BASE_SIZE.X / 2); + positionInfo.DistanceFromPrevious = (float)(rng.NextDouble() * OsuPlayfield.BASE_SIZE.Y / 2); positionInfo.RelativeAngle = (float)(rng.NextDouble() * 2 * Math.PI - Math.PI); } else From 3bebc88306c9abb2543686ba22fbbcbd51d2b0da Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Fri, 1 Apr 2022 11:59:24 +0800 Subject: [PATCH 008/395] Consider spinners when calculating jump angles Spinners are considered in `GeneratePositionInfos`, so they should also be considered in `RepositionHitObjects` --- .../Utils/OsuHitObjectGenerationUtils_Reposition.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils_Reposition.cs b/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils_Reposition.cs index 45285e5e0c..664bfae35a 100644 --- a/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils_Reposition.cs +++ b/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils_Reposition.cs @@ -79,7 +79,7 @@ namespace osu.Game.Rulesets.Osu.Utils if (hitObject is Spinner) { - previous = null; + previous = current; continue; } From 72cb3d6ad63dc0967a74980d67ae3c097c02e41b Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Mon, 11 Apr 2022 14:15:08 +0800 Subject: [PATCH 009/395] USe `MathF` in all applicable places --- .../Utils/OsuHitObjectGenerationUtils.cs | 6 +++--- .../OsuHitObjectGenerationUtils_Reposition.cs | 14 +++++++------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils.cs b/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils.cs index 19d3390f56..6129e6bfc4 100644 --- a/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils.cs +++ b/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils.cs @@ -174,11 +174,11 @@ namespace osu.Game.Rulesets.Osu.Utils /// The rotated vector. private static Vector2 rotateVector(Vector2 vector, float rotation) { - float angle = (float)Math.Atan2(vector.Y, vector.X) + rotation; + float angle = MathF.Atan2(vector.Y, vector.X) + rotation; float length = vector.Length; return new Vector2( - length * (float)Math.Cos(angle), - length * (float)Math.Sin(angle) + length * MathF.Cos(angle), + length * MathF.Sin(angle) ); } } diff --git a/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils_Reposition.cs b/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils_Reposition.cs index 664bfae35a..2abbd61c59 100644 --- a/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils_Reposition.cs +++ b/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils_Reposition.cs @@ -38,7 +38,7 @@ namespace osu.Game.Rulesets.Osu.Utils foreach (OsuHitObject hitObject in hitObjects) { Vector2 relativePosition = hitObject.Position - previousPosition; - float absoluteAngle = (float)Math.Atan2(relativePosition.Y, relativePosition.X); + float absoluteAngle = MathF.Atan2(relativePosition.Y, relativePosition.X); float relativeAngle = absoluteAngle - previousAngle; ObjectPositionInfo positionInfo; @@ -141,15 +141,15 @@ namespace osu.Game.Rulesets.Osu.Utils { Vector2 earliestPosition = beforePrevious?.HitObject.EndPosition ?? playfield_centre; Vector2 relativePosition = previous.HitObject.Position - earliestPosition; - previousAbsoluteAngle = (float)Math.Atan2(relativePosition.Y, relativePosition.X); + previousAbsoluteAngle = MathF.Atan2(relativePosition.Y, relativePosition.X); } } float absoluteAngle = previousAbsoluteAngle + current.PositionInfo.RelativeAngle; var posRelativeToPrev = new Vector2( - current.PositionInfo.DistanceFromPrevious * (float)Math.Cos(absoluteAngle), - current.PositionInfo.DistanceFromPrevious * (float)Math.Sin(absoluteAngle) + current.PositionInfo.DistanceFromPrevious * MathF.Cos(absoluteAngle), + current.PositionInfo.DistanceFromPrevious * MathF.Sin(absoluteAngle) ); Vector2 lastEndPosition = previous?.EndPositionModified ?? playfield_centre; @@ -161,13 +161,13 @@ namespace osu.Game.Rulesets.Osu.Utils if (!(current.HitObject is Slider slider)) return; - absoluteAngle = (float)Math.Atan2(posRelativeToPrev.Y, posRelativeToPrev.X); + absoluteAngle = MathF.Atan2(posRelativeToPrev.Y, posRelativeToPrev.X); Vector2 centreOfMassOriginal = calculateCentreOfMass(slider); Vector2 centreOfMassModified = rotateVector(centreOfMassOriginal, current.PositionInfo.Rotation + absoluteAngle - getSliderRotation(slider)); centreOfMassModified = RotateAwayFromEdge(current.PositionModified, centreOfMassModified); - float relativeRotation = (float)Math.Atan2(centreOfMassModified.Y, centreOfMassModified.X) - (float)Math.Atan2(centreOfMassOriginal.Y, centreOfMassOriginal.X); + float relativeRotation = MathF.Atan2(centreOfMassModified.Y, centreOfMassModified.X) - MathF.Atan2(centreOfMassOriginal.Y, centreOfMassOriginal.X); if (!Precision.AlmostEquals(relativeRotation, 0)) RotateSlider(slider, relativeRotation); } @@ -346,7 +346,7 @@ namespace osu.Game.Rulesets.Osu.Utils private static float getSliderRotation(Slider slider) { var endPositionVector = slider.Path.PositionAt(1); - return (float)Math.Atan2(endPositionVector.Y, endPositionVector.X); + return MathF.Atan2(endPositionVector.Y, endPositionVector.X); } public class ObjectPositionInfo From 610d2dc1a310d918604f758705bab41b8d2af451 Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Sun, 17 Apr 2022 10:34:48 +0800 Subject: [PATCH 010/395] Use a bigger sample step to calculate slider center of mass --- .../Utils/OsuHitObjectGenerationUtils_Reposition.cs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils_Reposition.cs b/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils_Reposition.cs index 2abbd61c59..a77d1f8b0f 100644 --- a/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils_Reposition.cs +++ b/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils_Reposition.cs @@ -323,13 +323,19 @@ namespace osu.Game.Rulesets.Osu.Utils /// The centre of mass of the slider. private static Vector2 calculateCentreOfMass(Slider slider) { - if (slider.Distance < 1) return Vector2.Zero; + const double sample_step = 50; + + // just sample the start and end positions if the slider is too short + if (slider.Distance <= sample_step) + { + return Vector2.Divide(slider.Path.PositionAt(1), 2); + } int count = 0; Vector2 sum = Vector2.Zero; double pathDistance = slider.Distance; - for (double i = 0; i < pathDistance; i++) + for (double i = 0; i < pathDistance; i += sample_step) { sum += slider.Path.PositionAt(i / pathDistance); count++; From 1d79266d422fe8dbe06eec2d259c2aab348df106 Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Sun, 17 Apr 2022 10:40:43 +0800 Subject: [PATCH 011/395] Clarify in the xmldoc that angles are measured in radians --- osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils.cs b/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils.cs index 6129e6bfc4..7b0d061e9a 100644 --- a/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils.cs +++ b/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils.cs @@ -151,7 +151,7 @@ namespace osu.Game.Rulesets.Osu.Utils /// Rotate a slider about its start position by the specified angle. /// /// The slider to be rotated. - /// The angle to rotate the slider by. + /// The angle, measured in radians, to rotate the slider by. public static void RotateSlider(Slider slider, float rotation) { void rotateNestedObject(OsuHitObject nested) => nested.Position = rotateVector(nested.Position - slider.Position, rotation) + slider.Position; @@ -170,7 +170,7 @@ namespace osu.Game.Rulesets.Osu.Utils /// Rotate a vector by the specified angle. /// /// The vector to be rotated. - /// The angle to rotate the vector by. + /// The angle, measured in radians, to rotate the vector by. /// The rotated vector. private static Vector2 rotateVector(Vector2 vector, float rotation) { From e5e196097584484bc9062328b887cd806232b960 Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Mon, 18 Apr 2022 09:38:51 +0800 Subject: [PATCH 012/395] Add inline comments --- osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils.cs b/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils.cs index 7b0d061e9a..266f7d1251 100644 --- a/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils.cs +++ b/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils.cs @@ -116,6 +116,7 @@ namespace osu.Game.Rulesets.Osu.Utils if (!(osuObject is Slider slider)) return; + // No need to update the head and tail circles, since slider handles that when the new slider path is set slider.NestedHitObjects.OfType().ForEach(h => h.Position = new Vector2(OsuPlayfield.BASE_SIZE.X - h.Position.X, h.Position.Y)); slider.NestedHitObjects.OfType().ForEach(h => h.Position = new Vector2(OsuPlayfield.BASE_SIZE.X - h.Position.X, h.Position.Y)); @@ -137,6 +138,7 @@ namespace osu.Game.Rulesets.Osu.Utils if (!(osuObject is Slider slider)) return; + // No need to update the head and tail circles, since slider handles that when the new slider path is set slider.NestedHitObjects.OfType().ForEach(h => h.Position = new Vector2(h.Position.X, OsuPlayfield.BASE_SIZE.Y - h.Position.Y)); slider.NestedHitObjects.OfType().ForEach(h => h.Position = new Vector2(h.Position.X, OsuPlayfield.BASE_SIZE.Y - h.Position.Y)); @@ -156,6 +158,7 @@ namespace osu.Game.Rulesets.Osu.Utils { void rotateNestedObject(OsuHitObject nested) => nested.Position = rotateVector(nested.Position - slider.Position, rotation) + slider.Position; + // No need to update the head and tail circles, since slider handles that when the new slider path is set slider.NestedHitObjects.OfType().ForEach(rotateNestedObject); slider.NestedHitObjects.OfType().ForEach(rotateNestedObject); From 4e0155fa4bac0dd8a8316f4cf1e80b610a465e17 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Sun, 8 May 2022 18:32:01 +0900 Subject: [PATCH 013/395] Make `JuiceStreamPath` time based instead of distance based. And remove the "slope limit" feature. TODO: for a juice stream with a large slope, the slider velocity of the hit object should be changed. --- .../TestSceneJuiceStreamSelectionBlueprint.cs | 8 +- .../JuiceStreamPathTest.cs | 95 +++++------ .../Blueprints/Components/EditablePath.cs | 30 ++-- .../Components/PlacementEditablePath.cs | 10 +- .../Components/SelectionEditablePath.cs | 12 +- .../JuiceStreamPlacementBlueprint.cs | 6 +- .../JuiceStreamSelectionBlueprint.cs | 6 +- .../Objects/JuiceStreamPath.cs | 158 ++++++++---------- .../Objects/JuiceStreamPathVertex.cs | 10 +- 9 files changed, 157 insertions(+), 178 deletions(-) diff --git a/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneJuiceStreamSelectionBlueprint.cs b/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneJuiceStreamSelectionBlueprint.cs index fb77fb1efd..123316f461 100644 --- a/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneJuiceStreamSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneJuiceStreamSelectionBlueprint.cs @@ -234,10 +234,10 @@ namespace osu.Game.Rulesets.Catch.Tests.Editor { var path = new JuiceStreamPath(); for (int i = 1; i < times.Length; i++) - path.Add((times[i] - times[0]) * velocity, positions[i] - positions[0]); + path.Add(times[i] - times[0], positions[i] - positions[0]); var sliderPath = new SliderPath(); - path.ConvertToSliderPath(sliderPath, 0); + path.ConvertToSliderPath(sliderPath, 0, velocity); addBlueprintStep(times[0], positions[0], sliderPath, velocity); } @@ -245,11 +245,11 @@ namespace osu.Game.Rulesets.Catch.Tests.Editor private void addVertexCheckStep(int count, int index, double time, float x) => AddAssert($"vertex {index} of {count} at {time}, {x}", () => { - double expectedDistance = (time - hitObject.StartTime) * hitObject.Velocity; + double expectedTime = time - hitObject.StartTime; float expectedX = x - hitObject.OriginalX; var vertices = getVertices(); return vertices.Count == count && - Precision.AlmostEquals(vertices[index].Distance, expectedDistance, 1e-3) && + Precision.AlmostEquals(vertices[index].Time, expectedTime, 1e-3) && Precision.AlmostEquals(vertices[index].X, expectedX); }); diff --git a/osu.Game.Rulesets.Catch.Tests/JuiceStreamPathTest.cs b/osu.Game.Rulesets.Catch.Tests/JuiceStreamPathTest.cs index 8fa96fb8c9..5248d5a96a 100644 --- a/osu.Game.Rulesets.Catch.Tests/JuiceStreamPathTest.cs +++ b/osu.Game.Rulesets.Catch.Tests/JuiceStreamPathTest.cs @@ -5,7 +5,6 @@ using System; using System.Collections.Generic; using System.Linq; using NUnit.Framework; -using osu.Framework.Utils; using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Types; @@ -37,14 +36,14 @@ namespace osu.Game.Rulesets.Catch.Tests { case 0: { - double distance = rng.NextDouble() * scale * 2 - scale; + double time = rng.NextDouble() * scale * 2 - scale; if (integralValues) - distance = Math.Round(distance); + time = Math.Round(time); - float oldX = path.PositionAtDistance(distance); - int index = path.InsertVertex(distance); + float oldX = path.PositionAtTime(time); + int index = path.InsertVertex(time); Assert.That(path.Vertices.Count, Is.EqualTo(vertexCount + 1)); - Assert.That(path.Vertices[index].Distance, Is.EqualTo(distance)); + Assert.That(path.Vertices[index].Time, Is.EqualTo(time)); Assert.That(path.Vertices[index].X, Is.EqualTo(oldX)); break; } @@ -52,20 +51,20 @@ namespace osu.Game.Rulesets.Catch.Tests case 1: { int index = rng.Next(path.Vertices.Count); - double distance = path.Vertices[index].Distance; + double time = path.Vertices[index].Time; float newX = (float)(rng.NextDouble() * scale * 2 - scale); if (integralValues) newX = MathF.Round(newX); path.SetVertexPosition(index, newX); Assert.That(path.Vertices.Count, Is.EqualTo(vertexCount)); - Assert.That(path.Vertices[index].Distance, Is.EqualTo(distance)); + Assert.That(path.Vertices[index].Time, Is.EqualTo(time)); Assert.That(path.Vertices[index].X, Is.EqualTo(newX)); break; } } - assertInvariants(path.Vertices, checkSlope); + assertInvariants(path.Vertices); } } @@ -76,7 +75,7 @@ namespace osu.Game.Rulesets.Catch.Tests path.Add(10, 5); path.Add(20, -5); - int removeCount = path.RemoveVertices((v, i) => v.Distance == 10 && i == 1); + int removeCount = path.RemoveVertices((v, i) => v.Time == 10 && i == 1); Assert.That(removeCount, Is.EqualTo(1)); Assert.That(path.Vertices, Is.EqualTo(new[] { @@ -131,8 +130,9 @@ namespace osu.Game.Rulesets.Catch.Tests })); } - [Test] - public void TestRandomConvertFromSliderPath() + [TestCase(10)] + [TestCase(0.1)] + public void TestRandomConvertFromSliderPath(double velocity) { var rng = new Random(1); var path = new JuiceStreamPath(); @@ -162,28 +162,28 @@ namespace osu.Game.Rulesets.Catch.Tests else sliderPath.ExpectedDistance.Value = null; - path.ConvertFromSliderPath(sliderPath); - Assert.That(path.Vertices[0].Distance, Is.EqualTo(0)); - Assert.That(path.Distance, Is.EqualTo(sliderPath.Distance).Within(1e-3)); - assertInvariants(path.Vertices, true); + path.ConvertFromSliderPath(sliderPath, velocity); + Assert.That(path.Vertices[0].Time, Is.EqualTo(0)); + Assert.That(path.Duration * velocity, Is.EqualTo(sliderPath.Distance).Within(1e-3)); + assertInvariants(path.Vertices); - double[] sampleDistances = Enumerable.Range(0, 10) - .Select(_ => rng.NextDouble() * sliderPath.Distance) - .ToArray(); + double[] sampleTimes = Enumerable.Range(0, 10) + .Select(_ => rng.NextDouble() * sliderPath.Distance / velocity) + .ToArray(); - foreach (double distance in sampleDistances) + foreach (double time in sampleTimes) { - float expected = sliderPath.PositionAt(distance / sliderPath.Distance).X; - Assert.That(path.PositionAtDistance(distance), Is.EqualTo(expected).Within(1e-3)); + float expected = sliderPath.PositionAt(time * velocity / sliderPath.Distance).X; + Assert.That(path.PositionAtTime(time), Is.EqualTo(expected).Within(1e-3)); } - path.ResampleVertices(sampleDistances); - assertInvariants(path.Vertices, true); + path.ResampleVertices(sampleTimes); + assertInvariants(path.Vertices); - foreach (double distance in sampleDistances) + foreach (double time in sampleTimes) { - float expected = sliderPath.PositionAt(distance / sliderPath.Distance).X; - Assert.That(path.PositionAtDistance(distance), Is.EqualTo(expected).Within(1e-3)); + float expected = sliderPath.PositionAt(time * velocity / sliderPath.Distance).X; + Assert.That(path.PositionAtTime(time), Is.EqualTo(expected).Within(1e-3)); } } } @@ -201,17 +201,17 @@ namespace osu.Game.Rulesets.Catch.Tests do { - double distance = rng.NextDouble() * 1e3; + double time = rng.NextDouble() * 1e3; float x = (float)(rng.NextDouble() * 1e3); - path.Add(distance, x); + path.Add(time, x); } while (rng.Next(5) != 0); float sliderStartY = (float)(rng.NextDouble() * JuiceStreamPath.OSU_PLAYFIELD_HEIGHT); - path.ConvertToSliderPath(sliderPath, sliderStartY); - Assert.That(sliderPath.Distance, Is.EqualTo(path.Distance).Within(1e-3)); - Assert.That(sliderPath.ControlPoints[0].Position.X, Is.EqualTo(path.Vertices[0].X)); - assertInvariants(path.Vertices, true); + double requiredVelocity = path.ComputeRequiredVelocity(); + double velocity = Math.Clamp(requiredVelocity, 1, 100); + + path.ConvertToSliderPath(sliderPath, sliderStartY, velocity); foreach (var point in sliderPath.ControlPoints) { @@ -219,11 +219,18 @@ namespace osu.Game.Rulesets.Catch.Tests Assert.That(sliderStartY + point.Position.Y, Is.InRange(0, JuiceStreamPath.OSU_PLAYFIELD_HEIGHT)); } + Assert.That(sliderPath.ControlPoints[0].Position.X, Is.EqualTo(path.Vertices[0].X)); + + // The path is preserved only if required velocity is used. + if (velocity < requiredVelocity) continue; + + Assert.That(sliderPath.Distance / velocity, Is.EqualTo(path.Duration).Within(1e-3)); + for (int i = 0; i < 10; i++) { - double distance = rng.NextDouble() * path.Distance; - float expected = path.PositionAtDistance(distance); - Assert.That(sliderPath.PositionAt(distance / sliderPath.Distance).X, Is.EqualTo(expected).Within(1e-3)); + double time = rng.NextDouble() * path.Duration; + float expected = path.PositionAtTime(time); + Assert.That(sliderPath.PositionAt(time * velocity / sliderPath.Distance).X, Is.EqualTo(expected).Within(3e-3)); } } } @@ -244,7 +251,7 @@ namespace osu.Game.Rulesets.Catch.Tests path.Add(20, 0); checkNewId(); - path.RemoveVertices((v, _) => v.Distance == 20); + path.RemoveVertices((v, _) => v.Time == 20); checkNewId(); path.ResampleVertices(new double[] { 5, 10, 15 }); @@ -253,7 +260,7 @@ namespace osu.Game.Rulesets.Catch.Tests path.Clear(); checkNewId(); - path.ConvertFromSliderPath(new SliderPath()); + path.ConvertFromSliderPath(new SliderPath(), 1); checkNewId(); void checkNewId() @@ -263,25 +270,19 @@ namespace osu.Game.Rulesets.Catch.Tests } } - private void assertInvariants(IReadOnlyList vertices, bool checkSlope) + private void assertInvariants(IReadOnlyList vertices) { Assert.That(vertices, Is.Not.Empty); for (int i = 0; i < vertices.Count; i++) { - Assert.That(double.IsFinite(vertices[i].Distance)); + Assert.That(double.IsFinite(vertices[i].Time)); Assert.That(float.IsFinite(vertices[i].X)); } for (int i = 1; i < vertices.Count; i++) { - Assert.That(vertices[i].Distance, Is.GreaterThanOrEqualTo(vertices[i - 1].Distance)); - - if (!checkSlope) continue; - - float xDiff = Math.Abs(vertices[i].X - vertices[i - 1].X); - double distanceDiff = vertices[i].Distance - vertices[i - 1].Distance; - Assert.That(xDiff, Is.LessThanOrEqualTo(distanceDiff).Within(Precision.FLOAT_EPSILON)); + Assert.That(vertices[i].Time, Is.GreaterThanOrEqualTo(vertices[i - 1].Time)); } } } diff --git a/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/EditablePath.cs b/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/EditablePath.cs index 1a43a10c81..3004d3644c 100644 --- a/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/EditablePath.cs +++ b/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/EditablePath.cs @@ -26,7 +26,7 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints.Components public int VertexCount => path.Vertices.Count; - protected readonly Func PositionToDistance; + protected readonly Func PositionToTime; protected IReadOnlyList VertexStates => vertexStates; @@ -44,9 +44,9 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints.Components [CanBeNull] private IBeatSnapProvider beatSnapProvider { get; set; } - protected EditablePath(Func positionToDistance) + protected EditablePath(Func positionToTime) { - PositionToDistance = positionToDistance; + PositionToTime = positionToTime; Anchor = Anchor.BottomLeft; } @@ -59,13 +59,13 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints.Components while (InternalChildren.Count < path.Vertices.Count) AddInternal(new VertexPiece()); - double distanceToYFactor = -hitObjectContainer.LengthAtTime(hitObject.StartTime, hitObject.StartTime + 1 / hitObject.Velocity); + double timeToYFactor = -hitObjectContainer.LengthAtTime(hitObject.StartTime, hitObject.StartTime + 1); for (int i = 0; i < VertexCount; i++) { var piece = (VertexPiece)InternalChildren[i]; var vertex = path.Vertices[i]; - piece.Position = new Vector2(vertex.X, (float)(vertex.Distance * distanceToYFactor)); + piece.Position = new Vector2(vertex.X, (float)(vertex.Time * timeToYFactor)); piece.UpdateFrom(vertexStates[i]); } } @@ -73,7 +73,7 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints.Components public void InitializeFromHitObject(JuiceStream hitObject) { var sliderPath = hitObject.Path; - path.ConvertFromSliderPath(sliderPath); + path.ConvertFromSliderPath(sliderPath, hitObject.Velocity); // If the original slider path has non-linear type segments, resample the vertices at nested hit object times to reduce the number of vertices. if (sliderPath.ControlPoints.Any(p => p.Type != null && p.Type != PathType.Linear)) @@ -92,11 +92,11 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints.Components public void UpdateHitObjectFromPath(JuiceStream hitObject) { - path.ConvertToSliderPath(hitObject.Path, hitObject.LegacyConvertedY); + path.ConvertToSliderPath(hitObject.Path, hitObject.LegacyConvertedY, hitObject.Velocity); if (beatSnapProvider == null) return; - double endTime = hitObject.StartTime + path.Distance / hitObject.Velocity; + double endTime = hitObject.StartTime + path.Duration; double snappedEndTime = beatSnapProvider.SnapTime(endTime, hitObject.StartTime); hitObject.Path.ExpectedDistance.Value = (snappedEndTime - hitObject.StartTime) * hitObject.Velocity; } @@ -108,9 +108,9 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints.Components protected override bool ComputeIsMaskedAway(RectangleF maskingBounds) => false; - protected int AddVertex(double distance, float x) + protected int AddVertex(double time, float x) { - int index = path.InsertVertex(distance); + int index = path.InsertVertex(time); path.SetVertexPosition(index, x); vertexStates.Insert(index, new VertexState()); @@ -138,9 +138,9 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints.Components return true; } - protected void MoveSelectedVertices(double distanceDelta, float xDelta) + protected void MoveSelectedVertices(double timeDelta, float xDelta) { - // Because the vertex list may be reordered due to distance change, the state list must be reordered as well. + // Because the vertex list may be reordered due to time change, the state list must be reordered as well. previousVertexStates.Clear(); previousVertexStates.AddRange(vertexStates); @@ -152,11 +152,11 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints.Components for (int i = 1; i < vertexCount; i++) { var state = previousVertexStates[i]; - double distance = state.VertexBeforeChange.Distance; + double time = state.VertexBeforeChange.Time; if (state.IsSelected) - distance += distanceDelta; + time += timeDelta; - int newIndex = path.InsertVertex(Math.Max(0, distance)); + int newIndex = path.InsertVertex(Math.Max(0, time)); vertexStates.Insert(newIndex, state); } diff --git a/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/PlacementEditablePath.cs b/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/PlacementEditablePath.cs index 158872fbab..511aec5e5d 100644 --- a/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/PlacementEditablePath.cs +++ b/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/PlacementEditablePath.cs @@ -15,15 +15,15 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints.Components /// private JuiceStreamPathVertex lastVertex; - public PlacementEditablePath(Func positionToDistance) - : base(positionToDistance) + public PlacementEditablePath(Func positionToTime) + : base(positionToTime) { } public void AddNewVertex() { var endVertex = Vertices[^1]; - int index = AddVertex(endVertex.Distance, endVertex.X); + int index = AddVertex(endVertex.Time, endVertex.X); for (int i = 0; i < VertexCount; i++) { @@ -41,9 +41,9 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints.Components public void MoveLastVertex(Vector2 screenSpacePosition) { Vector2 position = ToRelativePosition(screenSpacePosition); - double distanceDelta = PositionToDistance(position.Y) - lastVertex.Distance; + double timeDelta = PositionToTime(position.Y) - lastVertex.Time; float xDelta = position.X - lastVertex.X; - MoveSelectedVertices(distanceDelta, xDelta); + MoveSelectedVertices(timeDelta, xDelta); } } } diff --git a/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/SelectionEditablePath.cs b/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/SelectionEditablePath.cs index 8c7314d0b6..b4c353313c 100644 --- a/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/SelectionEditablePath.cs +++ b/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/SelectionEditablePath.cs @@ -27,15 +27,15 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints.Components [CanBeNull] private IEditorChangeHandler changeHandler { get; set; } - public SelectionEditablePath(Func positionToDistance) - : base(positionToDistance) + public SelectionEditablePath(Func positionToTime) + : base(positionToTime) { } public void AddVertex(Vector2 relativePosition) { - double distance = Math.Max(0, PositionToDistance(relativePosition.Y)); - int index = AddVertex(distance, relativePosition.X); + double time = Math.Max(0, PositionToTime(relativePosition.Y)); + int index = AddVertex(time, relativePosition.X); selectOnly(index); } @@ -83,9 +83,9 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints.Components protected override void OnDrag(DragEvent e) { Vector2 mousePosition = ToRelativePosition(e.ScreenSpaceMousePosition); - double distanceDelta = PositionToDistance(mousePosition.Y) - PositionToDistance(dragStartPosition.Y); + double timeDelta = PositionToTime(mousePosition.Y) - PositionToTime(dragStartPosition.Y); float xDelta = mousePosition.X - dragStartPosition.X; - MoveSelectedVertices(distanceDelta, xDelta); + MoveSelectedVertices(timeDelta, xDelta); } protected override void OnDragEnd(DragEndEvent e) diff --git a/osu.Game.Rulesets.Catch/Edit/Blueprints/JuiceStreamPlacementBlueprint.cs b/osu.Game.Rulesets.Catch/Edit/Blueprints/JuiceStreamPlacementBlueprint.cs index cff5bc2417..4a5a1d8160 100644 --- a/osu.Game.Rulesets.Catch/Edit/Blueprints/JuiceStreamPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Catch/Edit/Blueprints/JuiceStreamPlacementBlueprint.cs @@ -30,7 +30,7 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints { scrollingPath = new ScrollingPath(), nestedOutlineContainer = new NestedOutlineContainer(), - editablePath = new PlacementEditablePath(positionToDistance) + editablePath = new PlacementEditablePath(positionToTime) }; } @@ -119,10 +119,10 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints lastEditablePathId = editablePath.PathId; } - private double positionToDistance(float relativeYPosition) + private double positionToTime(float relativeYPosition) { double time = HitObjectContainer.TimeAtPosition(relativeYPosition, HitObject.StartTime); - return (time - HitObject.StartTime) * HitObject.Velocity; + return time - HitObject.StartTime; } } } diff --git a/osu.Game.Rulesets.Catch/Edit/Blueprints/JuiceStreamSelectionBlueprint.cs b/osu.Game.Rulesets.Catch/Edit/Blueprints/JuiceStreamSelectionBlueprint.cs index 890d059d19..12054a1d16 100644 --- a/osu.Game.Rulesets.Catch/Edit/Blueprints/JuiceStreamSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Catch/Edit/Blueprints/JuiceStreamSelectionBlueprint.cs @@ -62,7 +62,7 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints { scrollingPath = new ScrollingPath(), nestedOutlineContainer = new NestedOutlineContainer(), - editablePath = new SelectionEditablePath(positionToDistance) + editablePath = new SelectionEditablePath(positionToTime) }; } @@ -145,10 +145,10 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints return new RectangleF(left, top, right - left, bottom - top).Inflate(objectRadius); } - private double positionToDistance(float relativeYPosition) + private double positionToTime(float relativeYPosition) { double time = HitObjectContainer.TimeAtPosition(relativeYPosition, HitObject.StartTime); - return (time - HitObject.StartTime) * HitObject.Velocity; + return time - HitObject.StartTime; } private void initializeJuiceStreamPath() diff --git a/osu.Game.Rulesets.Catch/Objects/JuiceStreamPath.cs b/osu.Game.Rulesets.Catch/Objects/JuiceStreamPath.cs index 7207833fe6..d8cea3945c 100644 --- a/osu.Game.Rulesets.Catch/Objects/JuiceStreamPath.cs +++ b/osu.Game.Rulesets.Catch/Objects/JuiceStreamPath.cs @@ -20,11 +20,6 @@ namespace osu.Game.Rulesets.Catch.Objects /// However, the representation is difficult to work with. /// This represents the path in a more convenient way, a polyline connecting list of s. /// - /// - /// The path can be regarded as a function from the closed interval [Vertices[0].Distance, Vertices[^1].Distance] to the x position, given by . - /// To ensure the path is convertible to a , the slope of the function must not be more than 1 everywhere, - /// and this slope condition is always maintained as an invariant. - /// /// public class JuiceStreamPath { @@ -46,9 +41,9 @@ namespace osu.Game.Rulesets.Catch.Objects public int InvalidationID { get; private set; } = 1; /// - /// The difference between first vertex's and last vertex's . + /// The difference between first vertex's and last vertex's . /// - public double Distance => vertices[^1].Distance - vertices[0].Distance; + public double Duration => vertices[^1].Time - vertices[0].Time; /// /// This list should always be non-empty. @@ -59,15 +54,15 @@ namespace osu.Game.Rulesets.Catch.Objects }; /// - /// Compute the x-position of the path at the given . + /// Compute the x-position of the path at the given . /// /// - /// When the given distance is outside of the path, the x position at the corresponding endpoint is returned, + /// When the given time is outside of the path, the x position at the corresponding endpoint is returned, /// - public float PositionAtDistance(double distance) + public float PositionAtTime(double time) { - int index = vertexIndexAtDistance(distance); - return positionAtDistance(distance, index); + int index = vertexIndexAtTime(time); + return positionAtTime(time, index); } /// @@ -81,19 +76,19 @@ namespace osu.Game.Rulesets.Catch.Objects } /// - /// Insert a vertex at given . - /// The is used as the position of the new vertex. + /// Insert a vertex at given . + /// The is used as the position of the new vertex. /// Thus, the set of points of the path is not changed (up to floating-point precision). /// /// The index of the new vertex. - public int InsertVertex(double distance) + public int InsertVertex(double time) { - if (!double.IsFinite(distance)) - throw new ArgumentOutOfRangeException(nameof(distance)); + if (!double.IsFinite(time)) + throw new ArgumentOutOfRangeException(nameof(time)); - int index = vertexIndexAtDistance(distance); - float x = positionAtDistance(distance, index); - vertices.Insert(index, new JuiceStreamPathVertex(distance, x)); + int index = vertexIndexAtTime(time); + float x = positionAtTime(time, index); + vertices.Insert(index, new JuiceStreamPathVertex(time, x)); invalidate(); return index; @@ -101,7 +96,6 @@ namespace osu.Game.Rulesets.Catch.Objects /// /// Move the vertex of given to the given position . - /// When the distances between vertices are too small for the new vertex positions, the adjacent vertices are moved towards . /// public void SetVertexPosition(int index, float newX) { @@ -111,32 +105,17 @@ namespace osu.Game.Rulesets.Catch.Objects if (!float.IsFinite(newX)) throw new ArgumentOutOfRangeException(nameof(newX)); - var newVertex = new JuiceStreamPathVertex(vertices[index].Distance, newX); - - for (int i = index - 1; i >= 0 && !canConnect(vertices[i], newVertex); i--) - { - float clampedX = clampToConnectablePosition(newVertex, vertices[i]); - vertices[i] = new JuiceStreamPathVertex(vertices[i].Distance, clampedX); - } - - for (int i = index + 1; i < vertices.Count; i++) - { - float clampedX = clampToConnectablePosition(newVertex, vertices[i]); - vertices[i] = new JuiceStreamPathVertex(vertices[i].Distance, clampedX); - } - - vertices[index] = newVertex; + vertices[index] = new JuiceStreamPathVertex(vertices[index].Time, newX); invalidate(); } /// - /// Add a new vertex at given and position. - /// Adjacent vertices are moved when necessary in the same way as . + /// Add a new vertex at given and position. /// - public void Add(double distance, float x) + public void Add(double time, float x) { - int index = InsertVertex(distance); + int index = InsertVertex(time); SetVertexPosition(index, x); } @@ -163,22 +142,22 @@ namespace osu.Game.Rulesets.Catch.Objects } /// - /// Recreate this path by using difference set of vertices at given distances. - /// In addition to the given , the first vertex and the last vertex are always added to the new path. - /// New vertices use the positions on the original path. Thus, s at are preserved. + /// Recreate this path by using difference set of vertices at given time points. + /// In addition to the given , the first vertex and the last vertex are always added to the new path. + /// New vertices use the positions on the original path. Thus, s at are preserved. /// - public void ResampleVertices(IEnumerable sampleDistances) + public void ResampleVertices(IEnumerable sampleTimes) { var sampledVertices = new List(); - foreach (double distance in sampleDistances) + foreach (double time in sampleTimes) { - if (!double.IsFinite(distance)) - throw new ArgumentOutOfRangeException(nameof(sampleDistances)); + if (!double.IsFinite(time)) + throw new ArgumentOutOfRangeException(nameof(sampleTimes)); - double clampedDistance = Math.Clamp(distance, vertices[0].Distance, vertices[^1].Distance); - float x = PositionAtDistance(clampedDistance); - sampledVertices.Add(new JuiceStreamPathVertex(clampedDistance, x)); + double clampedTime = Math.Clamp(time, vertices[0].Time, vertices[^1].Time); + float x = PositionAtTime(clampedTime); + sampledVertices.Add(new JuiceStreamPathVertex(clampedTime, x)); } sampledVertices.Sort(); @@ -196,37 +175,57 @@ namespace osu.Game.Rulesets.Catch.Objects /// /// Duplicated vertices are automatically removed. /// - public void ConvertFromSliderPath(SliderPath sliderPath) + public void ConvertFromSliderPath(SliderPath sliderPath, double velocity) { var sliderPathVertices = new List(); sliderPath.GetPathToProgress(sliderPathVertices, 0, 1); - double distance = 0; + double time = 0; vertices.Clear(); vertices.Add(new JuiceStreamPathVertex(0, sliderPathVertices.FirstOrDefault().X)); for (int i = 1; i < sliderPathVertices.Count; i++) { - distance += Vector2.Distance(sliderPathVertices[i - 1], sliderPathVertices[i]); + time += Vector2.Distance(sliderPathVertices[i - 1], sliderPathVertices[i]) / velocity; - if (!Precision.AlmostEquals(vertices[^1].Distance, distance)) - vertices.Add(new JuiceStreamPathVertex(distance, sliderPathVertices[i].X)); + if (!Precision.AlmostEquals(vertices[^1].Time, time)) + Add(time, sliderPathVertices[i].X); } invalidate(); } + /// + /// Computes the minimum slider velocity required to convert this path to a . + /// + public double ComputeRequiredVelocity() + { + double maximumSlope = 0; + + for (int i = 1; i < vertices.Count; i++) + { + double xDifference = Math.Abs((double)vertices[i].X - vertices[i - 1].X); + double timeDifference = vertices[i].Time - vertices[i - 1].Time; + maximumSlope = Math.Max(maximumSlope, xDifference / timeDifference); + } + + return maximumSlope; + } + /// /// Convert the path of this to a and write the result to . /// The resulting slider is "folded" to make it vertically contained in the playfield `(0..)` assuming the slider start position is . + /// + /// The velocity of the converted slider is assumed to be . + /// To preserve the path, should be at least the value returned by . /// - public void ConvertToSliderPath(SliderPath sliderPath, float sliderStartY) + public void ConvertToSliderPath(SliderPath sliderPath, float sliderStartY, double velocity) { const float margin = 1; // Note: these two variables and `sliderPath` are modified by the local functions. - double currentDistance = 0; + double currentTime = 0; Vector2 lastPosition = new Vector2(vertices[0].X, 0); sliderPath.ControlPoints.Clear(); @@ -237,10 +236,10 @@ namespace osu.Game.Rulesets.Catch.Objects sliderPath.ControlPoints[^1].Type = PathType.Linear; float deltaX = vertices[i].X - lastPosition.X; - double length = vertices[i].Distance - currentDistance; + double length = (vertices[i].Time - currentTime) * velocity; // Should satisfy `deltaX^2 + deltaY^2 = length^2`. - // By invariants, the expression inside the `sqrt` is (almost) non-negative. + // The expression inside the `sqrt` is (almost) non-negative if the slider velocity is large enough. double deltaY = Math.Sqrt(Math.Max(0, length * length - (double)deltaX * deltaX)); // When `deltaY` is small, one segment is always enough. @@ -280,59 +279,38 @@ namespace osu.Game.Rulesets.Catch.Objects { Vector2 nextPosition = new Vector2(nextX, nextY); sliderPath.ControlPoints.Add(new PathControlPoint(nextPosition)); - currentDistance += Vector2.Distance(lastPosition, nextPosition); + currentTime += Vector2.Distance(lastPosition, nextPosition) / velocity; lastPosition = nextPosition; } } /// - /// Find the index at which a new vertex with can be inserted. + /// Find the index at which a new vertex with can be inserted. /// - private int vertexIndexAtDistance(double distance) + private int vertexIndexAtTime(double time) { - // The position of `(distance, Infinity)` is uniquely determined because infinite positions are not allowed. - int i = vertices.BinarySearch(new JuiceStreamPathVertex(distance, float.PositiveInfinity)); + // The position of `(time, Infinity)` is uniquely determined because infinite positions are not allowed. + int i = vertices.BinarySearch(new JuiceStreamPathVertex(time, float.PositiveInfinity)); return i < 0 ? ~i : i; } /// - /// Compute the position at the given , assuming is the vertex index returned by . + /// Compute the position at the given , assuming is the vertex index returned by . /// - private float positionAtDistance(double distance, int index) + private float positionAtTime(double time, int index) { if (index <= 0) return vertices[0].X; if (index >= vertices.Count) return vertices[^1].X; - double length = vertices[index].Distance - vertices[index - 1].Distance; - if (Precision.AlmostEquals(length, 0)) + double duration = vertices[index].Time - vertices[index - 1].Time; + if (Precision.AlmostEquals(duration, 0)) return vertices[index].X; float deltaX = vertices[index].X - vertices[index - 1].X; - return (float)(vertices[index - 1].X + deltaX * ((distance - vertices[index - 1].Distance) / length)); - } - - /// - /// Check the two vertices can connected directly while satisfying the slope condition. - /// - private bool canConnect(JuiceStreamPathVertex vertex1, JuiceStreamPathVertex vertex2, float allowance = 0) - { - double xDistance = Math.Abs((double)vertex2.X - vertex1.X); - float length = (float)Math.Abs(vertex2.Distance - vertex1.Distance); - return xDistance <= length + allowance; - } - - /// - /// Move the position of towards the position of - /// until the vertex pair satisfies the condition . - /// - /// The resulting position of . - private float clampToConnectablePosition(JuiceStreamPathVertex fixedVertex, JuiceStreamPathVertex movableVertex) - { - float length = (float)Math.Abs(movableVertex.Distance - fixedVertex.Distance); - return Math.Clamp(movableVertex.X, fixedVertex.X - length, fixedVertex.X + length); + return (float)(vertices[index - 1].X + deltaX * ((time - vertices[index - 1].Time) / duration)); } private void invalidate() => InvalidationID++; diff --git a/osu.Game.Rulesets.Catch/Objects/JuiceStreamPathVertex.cs b/osu.Game.Rulesets.Catch/Objects/JuiceStreamPathVertex.cs index 58c50603c4..afef2e637f 100644 --- a/osu.Game.Rulesets.Catch/Objects/JuiceStreamPathVertex.cs +++ b/osu.Game.Rulesets.Catch/Objects/JuiceStreamPathVertex.cs @@ -12,22 +12,22 @@ namespace osu.Game.Rulesets.Catch.Objects /// public readonly struct JuiceStreamPathVertex : IComparable { - public readonly double Distance; + public readonly double Time; public readonly float X; - public JuiceStreamPathVertex(double distance, float x) + public JuiceStreamPathVertex(double time, float x) { - Distance = distance; + Time = time; X = x; } public int CompareTo(JuiceStreamPathVertex other) { - int c = Distance.CompareTo(other.Distance); + int c = Time.CompareTo(other.Time); return c != 0 ? c : X.CompareTo(other.X); } - public override string ToString() => $"({Distance}, {X})"; + public override string ToString() => $"({Time}, {X})"; } } From 9ffa90602bc675ed0a37eac66845b34f36948f8f Mon Sep 17 00:00:00 2001 From: ekrctb Date: Sun, 8 May 2022 21:17:57 +0900 Subject: [PATCH 014/395] Automatically set slider velocity from juice stream path --- .../Blueprints/Components/EditablePath.cs | 22 +++++++++++++++++-- .../Objects/JuiceStreamPath.cs | 5 +++++ 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/EditablePath.cs b/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/EditablePath.cs index 3004d3644c..20e303dad1 100644 --- a/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/EditablePath.cs +++ b/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/EditablePath.cs @@ -92,13 +92,31 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints.Components public void UpdateHitObjectFromPath(JuiceStream hitObject) { - path.ConvertToSliderPath(hitObject.Path, hitObject.LegacyConvertedY, hitObject.Velocity); + // The SV setting may need to be changed for the current path. + var svBindable = hitObject.DifficultyControlPoint.SliderVelocityBindable; + double svToVelocityFactor = hitObject.Velocity / svBindable.Value; + double requiredVelocity = path.ComputeRequiredVelocity(); + + // The value is pre-rounded here because setting it to the bindable will rounded to the nearest value + // but it should be always rounded up to satisfy the required minimum velocity condition. + // + // This is rounded to integers instead of using the precision of the bindable + // because it results in a smaller number of non-redundant control points. + // + // The value is clamped here by the bindable min and max values. + // In case the required velocity is too large, the path is not preserved. + svBindable.Value = Math.Ceiling(requiredVelocity / svToVelocityFactor); + + // Calculate the velocity using the resulting SV because `hitObject.Velocity` is not recomputed yet. + double velocity = svBindable.Value * svToVelocityFactor; + + path.ConvertToSliderPath(hitObject.Path, hitObject.LegacyConvertedY, velocity); if (beatSnapProvider == null) return; double endTime = hitObject.StartTime + path.Duration; double snappedEndTime = beatSnapProvider.SnapTime(endTime, hitObject.StartTime); - hitObject.Path.ExpectedDistance.Value = (snappedEndTime - hitObject.StartTime) * hitObject.Velocity; + hitObject.Path.ExpectedDistance.Value = (snappedEndTime - hitObject.StartTime) * velocity; } public Vector2 ToRelativePosition(Vector2 screenSpacePosition) diff --git a/osu.Game.Rulesets.Catch/Objects/JuiceStreamPath.cs b/osu.Game.Rulesets.Catch/Objects/JuiceStreamPath.cs index d8cea3945c..61f4c580ae 100644 --- a/osu.Game.Rulesets.Catch/Objects/JuiceStreamPath.cs +++ b/osu.Game.Rulesets.Catch/Objects/JuiceStreamPath.cs @@ -207,6 +207,11 @@ namespace osu.Game.Rulesets.Catch.Objects { double xDifference = Math.Abs((double)vertices[i].X - vertices[i - 1].X); double timeDifference = vertices[i].Time - vertices[i - 1].Time; + + // A short segment won't affect the resulting path much anyways so ignore it to avoid divide-by-zero. + if (Precision.AlmostEquals(timeDifference, 0)) + continue; + maximumSlope = Math.Max(maximumSlope, xDifference / timeDifference); } From 0e98bb28bda2c7dcedba4d4637493e61c3c28d8e Mon Sep 17 00:00:00 2001 From: ekrctb Date: Sun, 8 May 2022 21:21:51 +0900 Subject: [PATCH 015/395] Fix wrong resampling times are used for juice stream path --- .../Edit/Blueprints/Components/EditablePath.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/EditablePath.cs b/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/EditablePath.cs index 20e303dad1..652d083c4e 100644 --- a/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/EditablePath.cs +++ b/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/EditablePath.cs @@ -80,7 +80,7 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints.Components { path.ResampleVertices(hitObject.NestedHitObjects .Skip(1).TakeWhile(h => !(h is Fruit)) // Only droplets in the first span are used. - .Select(h => (h.StartTime - hitObject.StartTime) * hitObject.Velocity)); + .Select(h => h.StartTime - hitObject.StartTime)); } vertexStates.Clear(); From 670922c8e563c202b6d7e396450f64ce77ecb78d Mon Sep 17 00:00:00 2001 From: ekrctb Date: Sun, 8 May 2022 21:32:05 +0900 Subject: [PATCH 016/395] Use latest slider velocity for juice stream velocity computation. This fixes one-frame glitch in editor when slider velocity is changed. --- osu.Game.Rulesets.Catch/Objects/JuiceStream.cs | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs b/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs index 282afb6343..d34452cdbb 100644 --- a/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs +++ b/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs @@ -27,10 +27,16 @@ namespace osu.Game.Rulesets.Catch.Objects public int RepeatCount { get; set; } [JsonIgnore] - public double Velocity { get; private set; } + private double velocityFactor; [JsonIgnore] - public double TickDistance { get; private set; } + private double tickDistanceFactor; + + [JsonIgnore] + public double Velocity => velocityFactor * DifficultyControlPoint.SliderVelocity; + + [JsonIgnore] + public double TickDistance => tickDistanceFactor * DifficultyControlPoint.SliderVelocity; /// /// The length of one span of this . @@ -43,10 +49,8 @@ namespace osu.Game.Rulesets.Catch.Objects TimingControlPoint timingPoint = controlPointInfo.TimingPointAt(StartTime); - double scoringDistance = base_scoring_distance * difficulty.SliderMultiplier * DifficultyControlPoint.SliderVelocity; - - Velocity = scoringDistance / timingPoint.BeatLength; - TickDistance = scoringDistance / difficulty.SliderTickRate; + velocityFactor = base_scoring_distance * difficulty.SliderMultiplier / timingPoint.BeatLength; + tickDistanceFactor = base_scoring_distance * difficulty.SliderMultiplier / difficulty.SliderTickRate; } protected override void CreateNestedHitObjects(CancellationToken cancellationToken) From 37c9aac49f836bb49f57c0ea246998dcf6815144 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Sun, 8 May 2022 21:35:06 +0900 Subject: [PATCH 017/395] Make `ScrollingPath` use time instead of distance. This is consistent as other components now use time instead of distance. --- .../Edit/Blueprints/Components/ScrollingPath.cs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/ScrollingPath.cs b/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/ScrollingPath.cs index 109bf61ea5..cfaca2f9a4 100644 --- a/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/ScrollingPath.cs +++ b/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/ScrollingPath.cs @@ -17,7 +17,7 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints.Components { private readonly Path drawablePath; - private readonly List<(double Distance, float X)> vertices = new List<(double, float)>(); + private readonly List<(double Time, float X)> vertices = new List<(double, float)>(); public ScrollingPath() { @@ -35,16 +35,16 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints.Components public void UpdatePathFrom(ScrollingHitObjectContainer hitObjectContainer, JuiceStream hitObject) { - double distanceToYFactor = -hitObjectContainer.LengthAtTime(hitObject.StartTime, hitObject.StartTime + 1 / hitObject.Velocity); + double timeToYFactor = -hitObjectContainer.LengthAtTime(hitObject.StartTime, hitObject.StartTime + 1); - computeDistanceXs(hitObject); + computeTimeXs(hitObject); drawablePath.Vertices = vertices - .Select(v => new Vector2(v.X, (float)(v.Distance * distanceToYFactor))) + .Select(v => new Vector2(v.X, (float)(v.Time * timeToYFactor))) .ToArray(); drawablePath.OriginPosition = drawablePath.PositionInBoundingBox(Vector2.Zero); } - private void computeDistanceXs(JuiceStream hitObject) + private void computeTimeXs(JuiceStream hitObject) { vertices.Clear(); @@ -54,17 +54,17 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints.Components if (sliderVertices.Count == 0) return; - double distance = 0; + double time = 0; Vector2 lastPosition = Vector2.Zero; for (int repeat = 0; repeat < hitObject.RepeatCount + 1; repeat++) { foreach (var position in sliderVertices) { - distance += Vector2.Distance(lastPosition, position); + time += Vector2.Distance(lastPosition, position) / hitObject.Velocity; lastPosition = position; - vertices.Add((distance, position.X)); + vertices.Add((time, position.X)); } sliderVertices.Reverse(); From 7daa3d8eb79b59756fad96810012c01e3a167dfe Mon Sep 17 00:00:00 2001 From: ekrctb Date: Sun, 8 May 2022 21:54:14 +0900 Subject: [PATCH 018/395] Remove now-redundant velocity calculation Velocity is computed from the up-to-date SV now. --- .../Edit/Blueprints/Components/EditablePath.cs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/EditablePath.cs b/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/EditablePath.cs index 652d083c4e..e038562b4b 100644 --- a/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/EditablePath.cs +++ b/osu.Game.Rulesets.Catch/Edit/Blueprints/Components/EditablePath.cs @@ -107,16 +107,13 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints.Components // In case the required velocity is too large, the path is not preserved. svBindable.Value = Math.Ceiling(requiredVelocity / svToVelocityFactor); - // Calculate the velocity using the resulting SV because `hitObject.Velocity` is not recomputed yet. - double velocity = svBindable.Value * svToVelocityFactor; - - path.ConvertToSliderPath(hitObject.Path, hitObject.LegacyConvertedY, velocity); + path.ConvertToSliderPath(hitObject.Path, hitObject.LegacyConvertedY, hitObject.Velocity); if (beatSnapProvider == null) return; double endTime = hitObject.StartTime + path.Duration; double snappedEndTime = beatSnapProvider.SnapTime(endTime, hitObject.StartTime); - hitObject.Path.ExpectedDistance.Value = (snappedEndTime - hitObject.StartTime) * velocity; + hitObject.Path.ExpectedDistance.Value = (snappedEndTime - hitObject.StartTime) * hitObject.Velocity; } public Vector2 ToRelativePosition(Vector2 screenSpacePosition) From d8a4f9d37ddb42a2e0a73f51e5112edab432964a Mon Sep 17 00:00:00 2001 From: ekrctb Date: Sun, 8 May 2022 22:33:16 +0900 Subject: [PATCH 019/395] Update juice stream blueprint tests No "clipping" occur anymore for vertex positions. Instead, clipping may occur when the path is converted to a slider. Add tests for automatic slider velocity change. --- .../TestSceneJuiceStreamPlacementBlueprint.cs | 29 +++++------------ .../TestSceneJuiceStreamSelectionBlueprint.cs | 31 +++++-------------- 2 files changed, 15 insertions(+), 45 deletions(-) diff --git a/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneJuiceStreamPlacementBlueprint.cs b/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneJuiceStreamPlacementBlueprint.cs index 981efc9a13..b1adc4901c 100644 --- a/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneJuiceStreamPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneJuiceStreamPlacementBlueprint.cs @@ -19,7 +19,7 @@ namespace osu.Game.Rulesets.Catch.Tests.Editor { public class TestSceneJuiceStreamPlacementBlueprint : CatchPlacementBlueprintTestScene { - private const double velocity = 0.5; + private const double velocity_factor = 0.5; private JuiceStream lastObject => LastObject?.HitObject as JuiceStream; @@ -27,7 +27,7 @@ namespace osu.Game.Rulesets.Catch.Tests.Editor { var playable = base.GetPlayableBeatmap(); playable.Difficulty.SliderTickRate = 5; - playable.Difficulty.SliderMultiplier = velocity * 10; + playable.Difficulty.SliderMultiplier = velocity_factor * 10; return playable; } @@ -43,6 +43,7 @@ namespace osu.Game.Rulesets.Catch.Tests.Editor AddAssert("end time is correct", () => Precision.AlmostEquals(lastObject.EndTime, times[1])); AddAssert("start position is correct", () => Precision.AlmostEquals(lastObject.OriginalX, positions[0])); AddAssert("end position is correct", () => Precision.AlmostEquals(lastObject.EndX, positions[1])); + AddAssert("default slider velocity", () => lastObject.DifficultyControlPoint.SliderVelocityBindable.IsDefault); } [Test] @@ -66,28 +67,21 @@ namespace osu.Game.Rulesets.Catch.Tests.Editor } [Test] - public void TestVelocityLimit() + public void TestSliderVelocityChange() { double[] times = { 100, 300 }; float[] positions = { 200, 500 }; addPlacementSteps(times, positions); - addPathCheckStep(times, new float[] { 200, 300 }); - } + addPathCheckStep(times, positions); - [Test] - public void TestPreviousVerticesAreFixed() - { - double[] times = { 100, 300, 500, 700 }; - float[] positions = { 200, 400, 100, 500 }; - addPlacementSteps(times, positions); - addPathCheckStep(times, new float[] { 200, 300, 200, 300 }); + AddAssert("slider velocity changed", () => !lastObject.DifficultyControlPoint.SliderVelocityBindable.IsDefault); } [Test] public void TestClampedPositionIsRestored() { double[] times = { 100, 300, 500 }; - float[] positions = { 200, 200, 0, 250 }; + float[] positions = { 200, 200, -3000, 250 }; addMoveAndClickSteps(times[0], positions[0]); addMoveAndClickSteps(times[1], positions[1]); @@ -97,15 +91,6 @@ namespace osu.Game.Rulesets.Catch.Tests.Editor addPathCheckStep(times, new float[] { 200, 200, 250 }); } - [Test] - public void TestFirstVertexIsFixed() - { - double[] times = { 100, 200 }; - float[] positions = { 100, 300 }; - addPlacementSteps(times, positions); - addPathCheckStep(times, new float[] { 100, 150 }); - } - [Test] public void TestOutOfOrder() { diff --git a/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneJuiceStreamSelectionBlueprint.cs b/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneJuiceStreamSelectionBlueprint.cs index 123316f461..22a839d847 100644 --- a/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneJuiceStreamSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneJuiceStreamSelectionBlueprint.cs @@ -101,31 +101,16 @@ namespace osu.Game.Rulesets.Catch.Tests.Editor } [Test] - public void TestClampedPositionIsRestored() + public void TestSliderVelocityChange() { - const double velocity = 0.25; - double[] times = { 100, 500, 700 }; - float[] positions = { 100, 100, 100 }; - addBlueprintStep(times, positions, velocity); + double[] times = { 100, 300 }; + float[] positions = { 200, 300 }; + addBlueprintStep(times, positions); + AddAssert("default slider velocity", () => hitObject.DifficultyControlPoint.SliderVelocityBindable.IsDefault); addDragStartStep(times[1], positions[1]); - - AddMouseMoveStep(times[1], 200); - addVertexCheckStep(3, 1, times[1], 200); - addVertexCheckStep(3, 2, times[2], 150); - - AddMouseMoveStep(times[1], 100); - addVertexCheckStep(3, 1, times[1], 100); - // Stored position is restored. - addVertexCheckStep(3, 2, times[2], positions[2]); - - AddMouseMoveStep(times[1], 300); - addDragEndStep(); - addDragStartStep(times[1], 300); - - AddMouseMoveStep(times[1], 100); - // Position is different because a changed position is committed when the previous drag is ended. - addVertexCheckStep(3, 2, times[2], 250); + AddMouseMoveStep(times[1], 400); + AddAssert("slider velocity changed", () => !hitObject.DifficultyControlPoint.SliderVelocityBindable.IsDefault); } [Test] @@ -174,7 +159,7 @@ namespace osu.Game.Rulesets.Catch.Tests.Editor addAddVertexSteps(500, 150); addVertexCheckStep(3, 1, 500, 150); - addAddVertexSteps(90, 220); + addAddVertexSteps(90, 200); addVertexCheckStep(4, 1, times[0], positions[0]); addAddVertexSteps(750, 180); From c0abce918fbacbd57f10a100d04cc1649d8bfb73 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 12 May 2022 15:23:41 +0900 Subject: [PATCH 020/395] Add `enum` to snap method as alternative to mutliple nested invocations --- .../Edit/CatchHitObjectComposer.cs | 15 +++++--- .../Editor/TestSceneManiaBeatSnapGrid.cs | 7 +--- .../Edit/ManiaHitObjectComposer.cs | 4 +- .../Edit/OsuHitObjectComposer.cs | 37 ++++++++----------- .../Editing/TestSceneDistanceSnapGrid.cs | 5 +-- osu.Game/Rulesets/Edit/HitObjectComposer.cs | 23 ++++++------ .../Rulesets/Edit/IPositionSnapProvider.cs | 14 +------ osu.Game/Rulesets/Edit/SnapType.cs | 15 ++++++++ .../Compose/Components/BlueprintContainer.cs | 4 +- .../Compose/Components/Timeline/Timeline.cs | 5 +-- 10 files changed, 62 insertions(+), 67 deletions(-) create mode 100644 osu.Game/Rulesets/Edit/SnapType.cs diff --git a/osu.Game.Rulesets.Catch/Edit/CatchHitObjectComposer.cs b/osu.Game.Rulesets.Catch/Edit/CatchHitObjectComposer.cs index 630a2cf645..6f59b3e543 100644 --- a/osu.Game.Rulesets.Catch/Edit/CatchHitObjectComposer.cs +++ b/osu.Game.Rulesets.Catch/Edit/CatchHitObjectComposer.cs @@ -6,6 +6,7 @@ using System.Linq; using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Extensions.EnumExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; using osu.Framework.Input; @@ -89,15 +90,19 @@ namespace osu.Game.Rulesets.Catch.Edit new TernaryButton(distanceSnapToggle, "Distance Snap", () => new SpriteIcon { Icon = FontAwesome.Solid.Ruler }) }); - public override SnapResult FindSnappedPositionAndTime(Vector2 screenSpacePosition) + public override SnapResult FindSnappedPositionAndTime(Vector2 screenSpacePosition, SnapType snapType = SnapType.All) { - var result = base.FindSnappedPositionAndTime(screenSpacePosition); + var result = base.FindSnappedPositionAndTime(screenSpacePosition, snapType); + result.ScreenSpacePosition.X = screenSpacePosition.X; - if (distanceSnapGrid.IsPresent && distanceSnapGrid.GetSnappedPosition(result.ScreenSpacePosition) is SnapResult snapResult && - Vector2.Distance(snapResult.ScreenSpacePosition, result.ScreenSpacePosition) < distance_snap_radius) + if (snapType.HasFlagFast(SnapType.Grids)) { - result = snapResult; + if (distanceSnapGrid.IsPresent && distanceSnapGrid.GetSnappedPosition(result.ScreenSpacePosition) is SnapResult snapResult && + Vector2.Distance(snapResult.ScreenSpacePosition, result.ScreenSpacePosition) < distance_snap_radius) + { + result = snapResult; + } } return result; diff --git a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaBeatSnapGrid.cs b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaBeatSnapGrid.cs index 4bb049b1a4..6130a80bb4 100644 --- a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaBeatSnapGrid.cs +++ b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaBeatSnapGrid.cs @@ -97,12 +97,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor set => InternalChild = value; } - public override SnapResult FindSnappedPositionAndTime(Vector2 screenSpacePosition) - { - throw new System.NotImplementedException(); - } - - public override SnapResult FindSnappedPosition(Vector2 screenSpacePosition) + public override SnapResult FindSnappedPositionAndTime(Vector2 screenSpacePosition, SnapType snapType = SnapType.All) { throw new System.NotImplementedException(); } diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs b/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs index fef315e2ef..3c022dafc0 100644 --- a/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs +++ b/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs @@ -56,9 +56,9 @@ namespace osu.Game.Rulesets.Mania.Edit protected override Playfield PlayfieldAtScreenSpacePosition(Vector2 screenSpacePosition) => Playfield.GetColumnByPosition(screenSpacePosition); - public override SnapResult FindSnappedPositionAndTime(Vector2 screenSpacePosition) + public override SnapResult FindSnappedPositionAndTime(Vector2 screenSpacePosition, SnapType snapType = SnapType.All) { - var result = base.FindSnappedPositionAndTime(screenSpacePosition); + var result = base.FindSnappedPositionAndTime(screenSpacePosition, snapType); switch (ScrollingInfo.Direction.Value) { diff --git a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs index 02beb0f2a4..b0d6170190 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs @@ -7,6 +7,7 @@ using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Caching; +using osu.Framework.Extensions.EnumExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; @@ -123,33 +124,27 @@ namespace osu.Game.Rulesets.Osu.Edit } } - public override SnapResult FindSnappedPosition(Vector2 screenSpacePosition) + public override SnapResult FindSnappedPositionAndTime(Vector2 screenSpacePosition, SnapType snapType = SnapType.All) { - if (snapToVisibleBlueprints(screenSpacePosition, out var snapResult)) + if (snapType.HasFlagFast(SnapType.NearbyObjects) && snapToVisibleBlueprints(screenSpacePosition, out var snapResult)) return snapResult; - return new SnapResult(screenSpacePosition, null); - } - - public override SnapResult FindSnappedPositionAndTime(Vector2 screenSpacePosition) - { - var positionSnap = FindSnappedPosition(screenSpacePosition); - if (positionSnap.ScreenSpacePosition != screenSpacePosition) - return positionSnap; - - if (distanceSnapToggle.Value == TernaryState.True && distanceSnapGrid != null) + if (snapType.HasFlagFast(SnapType.Grids)) { - (Vector2 pos, double time) = distanceSnapGrid.GetSnappedPosition(distanceSnapGrid.ToLocalSpace(screenSpacePosition)); - return new SnapResult(distanceSnapGrid.ToScreenSpace(pos), time, PlayfieldAtScreenSpacePosition(screenSpacePosition)); + if (distanceSnapToggle.Value == TernaryState.True && distanceSnapGrid != null) + { + (Vector2 pos, double time) = distanceSnapGrid.GetSnappedPosition(distanceSnapGrid.ToLocalSpace(screenSpacePosition)); + return new SnapResult(distanceSnapGrid.ToScreenSpace(pos), time, PlayfieldAtScreenSpacePosition(screenSpacePosition)); + } + + if (rectangularGridSnapToggle.Value == TernaryState.True) + { + Vector2 pos = rectangularPositionSnapGrid.GetSnappedPosition(rectangularPositionSnapGrid.ToLocalSpace(screenSpacePosition)); + return new SnapResult(rectangularPositionSnapGrid.ToScreenSpace(pos), null, PlayfieldAtScreenSpacePosition(screenSpacePosition)); + } } - if (rectangularGridSnapToggle.Value == TernaryState.True) - { - Vector2 pos = rectangularPositionSnapGrid.GetSnappedPosition(rectangularPositionSnapGrid.ToLocalSpace(screenSpacePosition)); - return new SnapResult(rectangularPositionSnapGrid.ToScreenSpace(pos), null, PlayfieldAtScreenSpacePosition(screenSpacePosition)); - } - - return base.FindSnappedPositionAndTime(screenSpacePosition); + return base.FindSnappedPositionAndTime(screenSpacePosition, snapType); } private bool snapToVisibleBlueprints(Vector2 screenSpacePosition, out SnapResult snapResult) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneDistanceSnapGrid.cs b/osu.Game.Tests/Visual/Editing/TestSceneDistanceSnapGrid.cs index 3aa3481cbf..ef07c3e411 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneDistanceSnapGrid.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneDistanceSnapGrid.cs @@ -185,10 +185,7 @@ namespace osu.Game.Tests.Visual.Editing private class SnapProvider : IDistanceSnapProvider { - public SnapResult FindSnappedPosition(Vector2 screenSpacePosition) => - new SnapResult(screenSpacePosition, null); - - public SnapResult FindSnappedPositionAndTime(Vector2 screenSpacePosition) => new SnapResult(screenSpacePosition, 0); + public SnapResult FindSnappedPositionAndTime(Vector2 screenSpacePosition, SnapType snapType = SnapType.Grids) => new SnapResult(screenSpacePosition, 0); public Bindable DistanceSpacingMultiplier { get; } = new BindableDouble(1); diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index 216510fcf3..f8d796a778 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -7,6 +7,7 @@ using System.Collections.Specialized; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Extensions.EnumExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Input; @@ -361,20 +362,23 @@ namespace osu.Game.Rulesets.Edit /// The most relevant . protected virtual Playfield PlayfieldAtScreenSpacePosition(Vector2 screenSpacePosition) => drawableRulesetWrapper.Playfield; - public override SnapResult FindSnappedPositionAndTime(Vector2 screenSpacePosition) + public override SnapResult FindSnappedPositionAndTime(Vector2 screenSpacePosition, SnapType snapType = SnapType.All) { var playfield = PlayfieldAtScreenSpacePosition(screenSpacePosition); double? targetTime = null; - if (playfield is ScrollingPlayfield scrollingPlayfield) + if (snapType.HasFlagFast(SnapType.Grids)) { - targetTime = scrollingPlayfield.TimeAtScreenSpacePosition(screenSpacePosition); + if (playfield is ScrollingPlayfield scrollingPlayfield) + { + targetTime = scrollingPlayfield.TimeAtScreenSpacePosition(screenSpacePosition); - // apply beat snapping - targetTime = BeatSnapProvider.SnapTime(targetTime.Value); + // apply beat snapping + targetTime = BeatSnapProvider.SnapTime(targetTime.Value); - // convert back to screen space - screenSpacePosition = scrollingPlayfield.ScreenSpacePositionAtTime(targetTime.Value); + // convert back to screen space + screenSpacePosition = scrollingPlayfield.ScreenSpacePositionAtTime(targetTime.Value); + } } return new SnapResult(screenSpacePosition, targetTime, playfield); @@ -414,10 +418,7 @@ namespace osu.Game.Rulesets.Edit #region IPositionSnapProvider - public abstract SnapResult FindSnappedPositionAndTime(Vector2 screenSpacePosition); - - public virtual SnapResult FindSnappedPosition(Vector2 screenSpacePosition) => - new SnapResult(screenSpacePosition, null); + public abstract SnapResult FindSnappedPositionAndTime(Vector2 screenSpacePosition, SnapType snapType = SnapType.All); #endregion } diff --git a/osu.Game/Rulesets/Edit/IPositionSnapProvider.cs b/osu.Game/Rulesets/Edit/IPositionSnapProvider.cs index 837b04424a..a6a6e39e23 100644 --- a/osu.Game/Rulesets/Edit/IPositionSnapProvider.cs +++ b/osu.Game/Rulesets/Edit/IPositionSnapProvider.cs @@ -8,7 +8,6 @@ namespace osu.Game.Rulesets.Edit { /// /// A snap provider which given a proposed position for a hit object, potentially offers a more correct position and time value inferred from the context of the beatmap. - /// Provided values are inferred in an isolated context, without consideration of other nearby hit objects. /// [Cached] public interface IPositionSnapProvider @@ -16,18 +15,9 @@ namespace osu.Game.Rulesets.Edit /// /// Given a position, find a valid time and position snap. /// - /// - /// This call should be equivalent to running with any additional logic that can be performed without the time immutability restriction. - /// /// The screen-space position to be snapped. + /// The type of snapping to apply. /// The time and position post-snapping. - SnapResult FindSnappedPositionAndTime(Vector2 screenSpacePosition); - - /// - /// Given a position, find a valid position snap, without changing the time value. - /// - /// The screen-space position to be snapped. - /// The position post-snapping. Time will always be null. - SnapResult FindSnappedPosition(Vector2 screenSpacePosition); + SnapResult FindSnappedPositionAndTime(Vector2 screenSpacePosition, SnapType snapType = SnapType.All); } } diff --git a/osu.Game/Rulesets/Edit/SnapType.cs b/osu.Game/Rulesets/Edit/SnapType.cs new file mode 100644 index 0000000000..4ab67fee63 --- /dev/null +++ b/osu.Game/Rulesets/Edit/SnapType.cs @@ -0,0 +1,15 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; + +namespace osu.Game.Rulesets.Edit +{ + [Flags] + public enum SnapType + { + NearbyObjects = 0, + Grids = 1, + All = NearbyObjects | Grids, + } +} diff --git a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs index 186c66e0af..d56dc176f6 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs @@ -486,7 +486,7 @@ namespace osu.Game.Screens.Edit.Compose.Components Vector2 originalPosition = movementBlueprintOriginalPositions[i]; var testPosition = originalPosition + distanceTravelled; - var positionalResult = snapProvider.FindSnappedPosition(testPosition); + var positionalResult = snapProvider.FindSnappedPositionAndTime(testPosition, SnapType.NearbyObjects); if (positionalResult.ScreenSpacePosition == testPosition) continue; @@ -505,7 +505,7 @@ namespace osu.Game.Screens.Edit.Compose.Components Vector2 movePosition = movementBlueprintOriginalPositions.First() + distanceTravelled; // Retrieve a snapped position. - var result = snapProvider?.FindSnappedPositionAndTime(movePosition); + var result = snapProvider?.FindSnappedPositionAndTime(movePosition, ~SnapType.NearbyObjects); if (result == null) { diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs index 7e66c57917..992ab7947e 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs @@ -303,10 +303,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline /// public double VisibleRange => track.Length / Zoom; - public SnapResult FindSnappedPosition(Vector2 screenSpacePosition) => - new SnapResult(screenSpacePosition, null); - - public SnapResult FindSnappedPositionAndTime(Vector2 screenSpacePosition) => + public SnapResult FindSnappedPositionAndTime(Vector2 screenSpacePosition, SnapType snapType = SnapType.All) => new SnapResult(screenSpacePosition, beatSnapProvider.SnapTime(getTimeFromPosition(Content.ToLocalSpace(screenSpacePosition)))); private double getTimeFromPosition(Vector2 localPosition) => From f7e055dbfee9b68b6f93b69262e04c281a836245 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 12 May 2022 16:13:31 +0900 Subject: [PATCH 021/395] Move mania note height offset application to a much more suitable location --- .../Blueprints/ManiaPlacementBlueprint.cs | 25 ++++++++++++++- .../Edit/ManiaHitObjectComposer.cs | 31 +++---------------- .../UI/Scrolling/ScrollingPlayfield.cs | 2 +- 3 files changed, 29 insertions(+), 29 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaPlacementBlueprint.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaPlacementBlueprint.cs index 8f25668dd0..ad95fd5e87 100644 --- a/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaPlacementBlueprint.cs @@ -5,7 +5,10 @@ using osu.Framework.Graphics; using osu.Framework.Input.Events; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Mania.Objects; +using osu.Game.Rulesets.Mania.Skinning.Default; using osu.Game.Rulesets.Mania.UI; +using osu.Game.Rulesets.UI.Scrolling; +using osuTK; using osuTK.Input; namespace osu.Game.Rulesets.Mania.Edit.Blueprints @@ -52,8 +55,28 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints { base.UpdateTimeAndPosition(result); + var playfield = (Column)result.Playfield; + + // Apply an offset to better align with the visual grid. + // This should only be applied during placement, as during selection / drag operations the movement is relative + // to the initial point of interaction rather than the grid. + switch (playfield.ScrollingInfo.Direction.Value) + { + case ScrollingDirection.Down: + result.ScreenSpacePosition -= new Vector2(0, getNoteHeight(playfield) / 2); + break; + + case ScrollingDirection.Up: + result.ScreenSpacePosition += new Vector2(0, getNoteHeight(playfield) / 2); + break; + } + if (PlacementActive == PlacementState.Waiting) - Column = result.Playfield as Column; + Column = playfield; } + + private float getNoteHeight(Column resultPlayfield) => + resultPlayfield.ToScreenSpace(new Vector2(DefaultNotePiece.NOTE_HEIGHT)).Y - + resultPlayfield.ToScreenSpace(Vector2.Zero).Y; } } diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs b/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs index 3c022dafc0..c389e1bced 100644 --- a/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs +++ b/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs @@ -1,15 +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.Beatmaps; -using osu.Game.Rulesets.Edit; -using osu.Game.Rulesets.Edit.Tools; -using osu.Game.Rulesets.Mania.Objects; using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Input; -using osu.Game.Rulesets.Mania.Skinning.Default; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Edit.Tools; +using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mania.UI; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects; @@ -56,28 +55,6 @@ namespace osu.Game.Rulesets.Mania.Edit protected override Playfield PlayfieldAtScreenSpacePosition(Vector2 screenSpacePosition) => Playfield.GetColumnByPosition(screenSpacePosition); - public override SnapResult FindSnappedPositionAndTime(Vector2 screenSpacePosition, SnapType snapType = SnapType.All) - { - var result = base.FindSnappedPositionAndTime(screenSpacePosition, snapType); - - switch (ScrollingInfo.Direction.Value) - { - case ScrollingDirection.Down: - result.ScreenSpacePosition -= new Vector2(0, getNoteHeight() / 2); - break; - - case ScrollingDirection.Up: - result.ScreenSpacePosition += new Vector2(0, getNoteHeight() / 2); - break; - } - - return result; - } - - private float getNoteHeight() => - Playfield.GetColumn(0).ToScreenSpace(new Vector2(DefaultNotePiece.NOTE_HEIGHT)).Y - - Playfield.GetColumn(0).ToScreenSpace(Vector2.Zero).Y; - protected override DrawableRuleset CreateDrawableRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList mods = null) { drawableRuleset = new DrawableManiaEditorRuleset(ruleset, beatmap, mods); diff --git a/osu.Game/Rulesets/UI/Scrolling/ScrollingPlayfield.cs b/osu.Game/Rulesets/UI/Scrolling/ScrollingPlayfield.cs index 2b75f93f9e..782255733f 100644 --- a/osu.Game/Rulesets/UI/Scrolling/ScrollingPlayfield.cs +++ b/osu.Game/Rulesets/UI/Scrolling/ScrollingPlayfield.cs @@ -18,7 +18,7 @@ namespace osu.Game.Rulesets.UI.Scrolling public new ScrollingHitObjectContainer HitObjectContainer => (ScrollingHitObjectContainer)base.HitObjectContainer; [Resolved] - protected IScrollingInfo ScrollingInfo { get; private set; } + public IScrollingInfo ScrollingInfo { get; private set; } [BackgroundDependencyLoader] private void load() From 9f5351e5a195d490db4b19b808263aeeac725e85 Mon Sep 17 00:00:00 2001 From: Jai Sharma Date: Thu, 12 May 2022 23:48:58 +0100 Subject: [PATCH 022/395] Add drawable channel caching to new chat overlay --- osu.Game/Overlays/ChatOverlayV2.cs | 37 +++++++++++++++++++++++------- 1 file changed, 29 insertions(+), 8 deletions(-) diff --git a/osu.Game/Overlays/ChatOverlayV2.cs b/osu.Game/Overlays/ChatOverlayV2.cs index 6eec0bbbf4..bd3823f090 100644 --- a/osu.Game/Overlays/ChatOverlayV2.cs +++ b/osu.Game/Overlays/ChatOverlayV2.cs @@ -39,8 +39,9 @@ namespace osu.Game.Overlays private ChatTextBar textBar = null!; private Container currentChannelContainer = null!; - private readonly BindableFloat chatHeight = new BindableFloat(); + private readonly Dictionary loadedChannels = new Dictionary(); + private readonly BindableFloat chatHeight = new BindableFloat(); private bool isDraggingTopBar; private float dragStartChatHeight; @@ -173,7 +174,7 @@ namespace osu.Game.Overlays if (currentChannel.Value?.Id != channel.Id) { if (!channel.Joined.Value) - channel = channelManager.JoinChannel(channel); + channel = channelManager.JoinChannel(channel, false); channelManager.CurrentChannel.Value = channel; } @@ -248,13 +249,26 @@ namespace osu.Game.Overlays channelListing.State.Value = Visibility.Hidden; textBar.ShowSearch.Value = false; - loading.Show(); - LoadComponentAsync(new ChatOverlayDrawableChannel(newChannel), loaded => + if (loadedChannels.ContainsKey(newChannel)) { - currentChannelContainer.Clear(); - currentChannelContainer.Add(loaded); - loading.Hide(); - }); + currentChannelContainer.Clear(false); + currentChannelContainer.Add(loadedChannels[newChannel]); + } + else + { + loading.Show(); + + // Ensure the drawable channel is stored before async load to prevent double loading + ChatOverlayDrawableChannel drawableChannel = new ChatOverlayDrawableChannel(newChannel); + loadedChannels.Add(newChannel, drawableChannel); + + LoadComponentAsync(drawableChannel, loaded => + { + currentChannelContainer.Clear(false); + currentChannelContainer.Add(loaded); + loading.Hide(); + }); + } } } @@ -264,14 +278,21 @@ namespace osu.Game.Overlays { case NotifyCollectionChangedAction.Add: IEnumerable joinedChannels = filterChannels(args.NewItems); + foreach (var channel in joinedChannels) channelList.AddChannel(channel); + break; case NotifyCollectionChangedAction.Remove: IEnumerable leftChannels = filterChannels(args.OldItems); + foreach (var channel in leftChannels) + { channelList.RemoveChannel(channel); + loadedChannels.Remove(channel); + } + break; } } From f88e416d1a7b6f63b0177f8bc042ec7470912d5b Mon Sep 17 00:00:00 2001 From: Jai Sharma Date: Fri, 13 May 2022 23:39:09 +0100 Subject: [PATCH 023/395] Ensure the wrong drawable channel isn't shown after load --- osu.Game/Overlays/ChatOverlayV2.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/osu.Game/Overlays/ChatOverlayV2.cs b/osu.Game/Overlays/ChatOverlayV2.cs index bd3823f090..361c06b04d 100644 --- a/osu.Game/Overlays/ChatOverlayV2.cs +++ b/osu.Game/Overlays/ChatOverlayV2.cs @@ -264,6 +264,14 @@ namespace osu.Game.Overlays LoadComponentAsync(drawableChannel, loaded => { + // Ensure the current channel hasn't changed by the time the load completes + if (currentChannel.Value != newChannel) + return; + + // Ensure the cached reference hasn't been removed from leaving the channel + if (!loadedChannels.ContainsKey(newChannel)) + return; + currentChannelContainer.Clear(false); currentChannelContainer.Add(loaded); loading.Hide(); From 2163a78b7f591518a3e532ed2a4be7835a2953ea Mon Sep 17 00:00:00 2001 From: Jai Sharma Date: Sat, 14 May 2022 00:15:02 +0100 Subject: [PATCH 024/395] Ensure drawable channels removed from the cache are disposed --- osu.Game/Overlays/ChatOverlayV2.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/ChatOverlayV2.cs b/osu.Game/Overlays/ChatOverlayV2.cs index 361c06b04d..e76d395204 100644 --- a/osu.Game/Overlays/ChatOverlayV2.cs +++ b/osu.Game/Overlays/ChatOverlayV2.cs @@ -241,6 +241,7 @@ namespace osu.Game.Overlays if (newChannel == null) { // null channel denotes that we should be showing the listing. + currentChannelContainer.Clear(false); channelListing.State.Value = Visibility.Visible; textBar.ShowSearch.Value = true; } @@ -298,7 +299,13 @@ namespace osu.Game.Overlays foreach (var channel in leftChannels) { channelList.RemoveChannel(channel); - loadedChannels.Remove(channel); + if (loadedChannels.ContainsKey(channel)) + { + ChatOverlayDrawableChannel loaded = loadedChannels[channel]; + loadedChannels.Remove(channel); + // DrawableChannel removed from cache must be manually disposed + loaded.Dispose(); + } } break; From bd68ffa805a6f3ae1d1a4a9853ebfa4f9aa1303d Mon Sep 17 00:00:00 2001 From: Jai Sharma Date: Sat, 14 May 2022 12:16:00 +0100 Subject: [PATCH 025/395] Fix textbox focus test in `ChatOverlayV2` --- osu.Game.Tests/Visual/Online/TestSceneChatOverlayV2.cs | 6 +++--- osu.Game/Overlays/ChatOverlayV2.cs | 5 +++-- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneChatOverlayV2.cs b/osu.Game.Tests/Visual/Online/TestSceneChatOverlayV2.cs index bf1767cc96..82089ff3d1 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneChatOverlayV2.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneChatOverlayV2.cs @@ -365,19 +365,19 @@ namespace osu.Game.Tests.Visual.Online } [Test] - public void TextBoxRetainsFocus() + public void TestTextBoxRetainsFocus() { AddStep("Show overlay", () => chatOverlay.Show()); AddAssert("TextBox is focused", () => InputManager.FocusedDrawable == chatOverlayTextBox); AddStep("Join channel 1", () => channelManager.JoinChannel(testChannel1)); AddStep("Select channel 1", () => clickDrawable(getChannelListItem(testChannel1))); AddAssert("TextBox is focused", () => InputManager.FocusedDrawable == chatOverlayTextBox); + AddStep("Click drawable channel", () => clickDrawable(currentDrawableChannel)); + AddAssert("TextBox is focused", () => InputManager.FocusedDrawable == chatOverlayTextBox); AddStep("Click selector", () => clickDrawable(chatOverlay.ChildrenOfType().Single())); AddAssert("TextBox is focused", () => InputManager.FocusedDrawable == chatOverlayTextBox); AddStep("Click listing", () => clickDrawable(chatOverlay.ChildrenOfType().Single())); AddAssert("TextBox is focused", () => InputManager.FocusedDrawable == chatOverlayTextBox); - AddStep("Click drawable channel", () => clickDrawable(currentDrawableChannel)); - AddAssert("TextBox is focused", () => InputManager.FocusedDrawable == chatOverlayTextBox); AddStep("Click channel list", () => clickDrawable(chatOverlay.ChildrenOfType().Single())); AddAssert("TextBox is focused", () => InputManager.FocusedDrawable == chatOverlayTextBox); AddStep("Click top bar", () => clickDrawable(chatOverlay.ChildrenOfType().Single())); diff --git a/osu.Game/Overlays/ChatOverlayV2.cs b/osu.Game/Overlays/ChatOverlayV2.cs index e76d395204..e91d57f18b 100644 --- a/osu.Game/Overlays/ChatOverlayV2.cs +++ b/osu.Game/Overlays/ChatOverlayV2.cs @@ -242,12 +242,12 @@ namespace osu.Game.Overlays { // null channel denotes that we should be showing the listing. currentChannelContainer.Clear(false); - channelListing.State.Value = Visibility.Visible; + channelListing.Show(); textBar.ShowSearch.Value = true; } else { - channelListing.State.Value = Visibility.Hidden; + channelListing.Hide(); textBar.ShowSearch.Value = false; if (loadedChannels.ContainsKey(newChannel)) @@ -299,6 +299,7 @@ namespace osu.Game.Overlays foreach (var channel in leftChannels) { channelList.RemoveChannel(channel); + if (loadedChannels.ContainsKey(channel)) { ChatOverlayDrawableChannel loaded = loadedChannels[channel]; From ae5b6c3e1068a130396c3a2f8fab554c362603c5 Mon Sep 17 00:00:00 2001 From: Jai Sharma Date: Sun, 15 May 2022 19:38:37 +0100 Subject: [PATCH 026/395] Use dummy channel to show selector and remove `ChannelListSelector` Add dummy channel `DummySelectorChannel` which should be set as the current channel in the channel manager when the selector in the chat overlay should be shown. Refactors the `ChannelListItem` to not show mention pill and close button when the channel is the dummy selector channel. Ensure that the `ChannelList` selects the dummy channel on clicking the selector item. Removes `ChannelListSelector` as it is no longer needed. Removes the `setCurrent` parameter from `ChannelManager.JoinChannel` method as it is no longer needed. --- .../Visual/Online/TestSceneChannelList.cs | 12 +- .../Visual/Online/TestSceneChatOverlayV2.cs | 5 +- osu.Game/Online/Chat/ChannelManager.cs | 18 +-- .../Overlays/Chat/ChannelList/ChannelList.cs | 10 +- .../Chat/ChannelList/ChannelListItem.cs | 60 +++++++--- .../Chat/ChannelList/ChannelListSelector.cs | 103 ------------------ osu.Game/Overlays/ChatOverlayV2.cs | 13 ++- 7 files changed, 80 insertions(+), 141 deletions(-) delete mode 100644 osu.Game/Overlays/Chat/ChannelList/ChannelListSelector.cs diff --git a/osu.Game.Tests/Visual/Online/TestSceneChannelList.cs b/osu.Game.Tests/Visual/Online/TestSceneChannelList.cs index 9929642539..53a48fcc58 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneChannelList.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneChannelList.cs @@ -118,35 +118,37 @@ namespace osu.Game.Tests.Visual.Online { AddStep("Unread Selected", () => { - if (selected.Value != null) + if (validItem) channelList.GetItem(selected.Value).Unread.Value = true; }); AddStep("Read Selected", () => { - if (selected.Value != null) + if (validItem) channelList.GetItem(selected.Value).Unread.Value = false; }); AddStep("Add Mention Selected", () => { - if (selected.Value != null) + if (validItem) channelList.GetItem(selected.Value).Mentions.Value++; }); AddStep("Add 98 Mentions Selected", () => { - if (selected.Value != null) + if (validItem) channelList.GetItem(selected.Value).Mentions.Value += 98; }); AddStep("Clear Mentions Selected", () => { - if (selected.Value != null) + if (validItem) channelList.GetItem(selected.Value).Mentions.Value = 0; }); } + private bool validItem => selected.Value != null && !(selected.Value is DummySelectorChannel); + private Channel createRandomPublicChannel() { int id = RNG.Next(0, 10000); diff --git a/osu.Game.Tests/Visual/Online/TestSceneChatOverlayV2.cs b/osu.Game.Tests/Visual/Online/TestSceneChatOverlayV2.cs index bf1767cc96..48557a4ddb 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneChatOverlayV2.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneChatOverlayV2.cs @@ -372,7 +372,7 @@ namespace osu.Game.Tests.Visual.Online AddStep("Join channel 1", () => channelManager.JoinChannel(testChannel1)); AddStep("Select channel 1", () => clickDrawable(getChannelListItem(testChannel1))); AddAssert("TextBox is focused", () => InputManager.FocusedDrawable == chatOverlayTextBox); - AddStep("Click selector", () => clickDrawable(chatOverlay.ChildrenOfType().Single())); + AddStep("Click selector", () => clickDrawable(channelSelectorButton)); AddAssert("TextBox is focused", () => InputManager.FocusedDrawable == chatOverlayTextBox); AddStep("Click listing", () => clickDrawable(chatOverlay.ChildrenOfType().Single())); AddAssert("TextBox is focused", () => InputManager.FocusedDrawable == chatOverlayTextBox); @@ -407,6 +407,9 @@ namespace osu.Game.Tests.Visual.Online private ChatOverlayTopBar chatOverlayTopBar => chatOverlay.ChildrenOfType().Single(); + private ChannelListItem channelSelectorButton => + chatOverlay.ChildrenOfType().Single(item => item.Channel is DummySelectorChannel); + private void clickDrawable(Drawable d) { InputManager.MoveMouseTo(d); diff --git a/osu.Game/Online/Chat/ChannelManager.cs b/osu.Game/Online/Chat/ChannelManager.cs index 1fe784f68b..9524e8c1a7 100644 --- a/osu.Game/Online/Chat/ChannelManager.cs +++ b/osu.Game/Online/Chat/ChannelManager.cs @@ -14,6 +14,7 @@ using osu.Game.Input; using osu.Game.Online.API; using osu.Game.Online.API.Requests; using osu.Game.Online.API.Requests.Responses; +using osu.Game.Overlays; using osu.Game.Overlays.Chat.Tabs; namespace osu.Game.Online.Chat @@ -133,7 +134,9 @@ namespace osu.Game.Online.Chat private void currentChannelChanged(ValueChangedEvent e) { - if (!(e.NewValue is ChannelSelectorTabItem.ChannelSelectorTabChannel)) + bool isSelectorChannel = e.NewValue is ChannelSelectorTabItem.ChannelSelectorTabChannel || e.NewValue is DummySelectorChannel; + + if (!isSelectorChannel) JoinChannel(e.NewValue); } @@ -194,7 +197,6 @@ namespace osu.Game.Online.Chat createNewPrivateMessageRequest.Failure += exception => { - handlePostException(exception); target.ReplaceMessage(message, null); dequeueAndRun(); }; @@ -420,11 +422,10 @@ namespace osu.Game.Online.Chat /// Joins a channel if it has not already been joined. Must be called from the update thread. /// /// The channel to join. - /// Set the channel to join as the current channel if the current channel is null. /// The joined channel. Note that this may not match the parameter channel as it is a backed object. - public Channel JoinChannel(Channel channel, bool setCurrent = true) => joinChannel(channel, true, setCurrent); + public Channel JoinChannel(Channel channel) => joinChannel(channel, true); - private Channel joinChannel(Channel channel, bool fetchInitialMessages = false, bool setCurrent = true) + private Channel joinChannel(Channel channel, bool fetchInitialMessages = false) { if (channel == null) return null; @@ -440,7 +441,7 @@ namespace osu.Game.Online.Chat case ChannelType.Multiplayer: // join is implicit. happens when you join a multiplayer game. // this will probably change in the future. - joinChannel(channel, fetchInitialMessages, setCurrent); + joinChannel(channel, fetchInitialMessages); return channel; case ChannelType.PM: @@ -461,7 +462,7 @@ namespace osu.Game.Online.Chat default: var req = new JoinChannelRequest(channel); - req.Success += () => joinChannel(channel, fetchInitialMessages, setCurrent); + req.Success += () => joinChannel(channel, fetchInitialMessages); req.Failure += ex => LeaveChannel(channel); api.Queue(req); return channel; @@ -473,8 +474,7 @@ namespace osu.Game.Online.Chat this.fetchInitialMessages(channel); } - if (setCurrent) - CurrentChannel.Value ??= channel; + CurrentChannel.Value ??= channel; return channel; } diff --git a/osu.Game/Overlays/Chat/ChannelList/ChannelList.cs b/osu.Game/Overlays/Chat/ChannelList/ChannelList.cs index 6bdf5ee084..c9f3a4d380 100644 --- a/osu.Game/Overlays/Chat/ChannelList/ChannelList.cs +++ b/osu.Game/Overlays/Chat/ChannelList/ChannelList.cs @@ -18,13 +18,16 @@ namespace osu.Game.Overlays.Chat.ChannelList { public class ChannelList : Container { - public Action? OnRequestSelect; + public Action? OnRequestSelect; public Action? OnRequestLeave; private readonly Dictionary channelMap = new Dictionary(); + private readonly DummySelectorChannel dummySelectorChannel = new DummySelectorChannel(); + private ChannelListItemFlow publicChannelFlow = null!; private ChannelListItemFlow privateChannelFlow = null!; + private ChannelListItem selector = null!; [BackgroundDependencyLoader] private void load(OverlayColourProvider colourProvider) @@ -50,16 +53,17 @@ namespace osu.Game.Overlays.Chat.ChannelList Children = new Drawable[] { publicChannelFlow = new ChannelListItemFlow("CHANNELS"), - new ChannelListSelector + selector = new ChannelListItem(dummySelectorChannel) { Margin = new MarginPadding { Bottom = 10 }, - Action = () => OnRequestSelect?.Invoke(null), }, privateChannelFlow = new ChannelListItemFlow("DIRECT MESSAGES"), }, }, }, }; + + selector.OnRequestSelect += chan => OnRequestSelect?.Invoke(chan); } public void AddChannel(Channel channel) diff --git a/osu.Game/Overlays/Chat/ChannelList/ChannelListItem.cs b/osu.Game/Overlays/Chat/ChannelList/ChannelListItem.cs index dd571c9ad9..dfb0b6d781 100644 --- a/osu.Game/Overlays/Chat/ChannelList/ChannelListItem.cs +++ b/osu.Game/Overlays/Chat/ChannelList/ChannelListItem.cs @@ -34,7 +34,7 @@ namespace osu.Game.Overlays.Chat.ChannelList private Box hoverBox = null!; private Box selectBox = null!; private OsuSpriteText text = null!; - private ChannelListItemCloseButton close = null!; + private Drawable close = null!; [Resolved] private Bindable selectedChannel { get; set; } = null!; @@ -97,20 +97,8 @@ namespace osu.Game.Overlays.Chat.ChannelList RelativeSizeAxes = Axes.X, Truncate = true, }, - new ChannelListItemMentionPill - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Margin = new MarginPadding { Right = 3 }, - Mentions = { BindTarget = Mentions }, - }, - close = new ChannelListItemCloseButton - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Margin = new MarginPadding { Right = 3 }, - Action = () => OnRequestLeave?.Invoke(Channel), - } + createMentionPill(), + close = createCloseButton(), } }, }, @@ -131,14 +119,20 @@ namespace osu.Game.Overlays.Chat.ChannelList protected override bool OnHover(HoverEvent e) { hoverBox.FadeIn(300, Easing.OutQuint); - close.FadeIn(300, Easing.OutQuint); + + if (!isSelector) + close.FadeIn(300, Easing.OutQuint); + return base.OnHover(e); } protected override void OnHoverLost(HoverLostEvent e) { hoverBox.FadeOut(200, Easing.OutQuint); - close.FadeOut(200, Easing.OutQuint); + + if (!isSelector) + close.FadeOut(200, Easing.OutQuint); + base.OnHoverLost(e); } @@ -158,9 +152,37 @@ namespace osu.Game.Overlays.Chat.ChannelList }; } + private Drawable createMentionPill() + { + if (isSelector) + return Drawable.Empty(); + + return new ChannelListItemMentionPill + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Margin = new MarginPadding { Right = 3 }, + Mentions = { BindTarget = Mentions }, + }; + } + + private Drawable createCloseButton() + { + if (isSelector) + return Drawable.Empty(); + + return new ChannelListItemCloseButton + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Margin = new MarginPadding { Right = 3 }, + Action = () => OnRequestLeave?.Invoke(Channel), + }; + } + private void updateState() { - bool selected = selectedChannel.Value == Channel; + bool selected = selectedChannel.Value == Channel || (isSelector && selectedChannel.Value == null); if (selected) selectBox.FadeIn(300, Easing.OutQuint); @@ -172,5 +194,7 @@ namespace osu.Game.Overlays.Chat.ChannelList else text.FadeColour(colourProvider.Light3, 200, Easing.OutQuint); } + + private bool isSelector => Channel is DummySelectorChannel; } } diff --git a/osu.Game/Overlays/Chat/ChannelList/ChannelListSelector.cs b/osu.Game/Overlays/Chat/ChannelList/ChannelListSelector.cs deleted file mode 100644 index a07aad2041..0000000000 --- a/osu.Game/Overlays/Chat/ChannelList/ChannelListSelector.cs +++ /dev/null @@ -1,103 +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 enable - -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.Input.Events; -using osu.Game.Graphics; -using osu.Game.Graphics.Containers; -using osu.Game.Graphics.Sprites; -using osu.Game.Online.Chat; - -namespace osu.Game.Overlays.Chat.ChannelList -{ - public class ChannelListSelector : OsuClickableContainer - { - private Box hoverBox = null!; - private Box selectBox = null!; - private OsuSpriteText text = null!; - - [Resolved] - private Bindable currentChannel { get; set; } = null!; - - [Resolved] - private OverlayColourProvider colourProvider { get; set; } = null!; - - [BackgroundDependencyLoader] - private void load(OverlayColourProvider colourProvider) - { - Height = 30; - RelativeSizeAxes = Axes.X; - - Children = new Drawable[] - { - hoverBox = new Box - { - RelativeSizeAxes = Axes.Both, - Colour = colourProvider.Background3, - Alpha = 0f, - }, - selectBox = new Box - { - RelativeSizeAxes = Axes.Both, - Colour = colourProvider.Background4, - Alpha = 0f, - }, - new Container - { - RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding { Left = 18, Right = 10 }, - Child = text = new OsuSpriteText - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Text = "Add more channels", - Font = OsuFont.Torus.With(size: 17, weight: FontWeight.SemiBold), - Colour = colourProvider.Light3, - Margin = new MarginPadding { Bottom = 2 }, - RelativeSizeAxes = Axes.X, - Truncate = true, - }, - }, - }; - } - - protected override void LoadComplete() - { - base.LoadComplete(); - - currentChannel.BindValueChanged(channel => - { - // This logic should be handled by the chat overlay rather than this component. - // Selected state should be moved to an abstract class and shared with ChannelListItem. - if (channel.NewValue == null) - { - text.FadeColour(colourProvider.Content1, 300, Easing.OutQuint); - selectBox.FadeIn(300, Easing.OutQuint); - } - else - { - text.FadeColour(colourProvider.Light3, 200, Easing.OutQuint); - selectBox.FadeOut(200, Easing.OutQuint); - } - }, true); - } - - protected override bool OnHover(HoverEvent e) - { - hoverBox.FadeIn(300, Easing.OutQuint); - return base.OnHover(e); - } - - protected override void OnHoverLost(HoverLostEvent e) - { - hoverBox.FadeOut(200, Easing.OutQuint); - base.OnHoverLost(e); - } - } -} diff --git a/osu.Game/Overlays/ChatOverlayV2.cs b/osu.Game/Overlays/ChatOverlayV2.cs index 6eec0bbbf4..0307dcfdf3 100644 --- a/osu.Game/Overlays/ChatOverlayV2.cs +++ b/osu.Game/Overlays/ChatOverlayV2.cs @@ -154,7 +154,7 @@ namespace osu.Game.Overlays channelList.OnRequestSelect += channel => channelManager.CurrentChannel.Value = channel; channelList.OnRequestLeave += channel => channelManager.LeaveChannel(channel); - channelListing.OnRequestJoin += channel => channelManager.JoinChannel(channel, false); + channelListing.OnRequestJoin += channel => channelManager.JoinChannel(channel); channelListing.OnRequestLeave += channel => channelManager.LeaveChannel(channel); textBar.OnSearchTermsChanged += searchTerms => channelListing.SearchTerm = searchTerms; @@ -237,7 +237,7 @@ namespace osu.Game.Overlays { Channel? newChannel = channel.NewValue; - if (newChannel == null) + if (newChannel == null || newChannel is DummySelectorChannel) { // null channel denotes that we should be showing the listing. channelListing.State.Value = Visibility.Visible; @@ -293,4 +293,13 @@ namespace osu.Game.Overlays channelManager.PostMessage(message); } } + + public class DummySelectorChannel : Channel + { + public DummySelectorChannel() + { + Name = "Add more channels"; + Type = ChannelType.System; + } + } } From a633501687c0535f76b2422cbb5f04f0935afccf Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 16 May 2022 14:09:37 +0900 Subject: [PATCH 027/395] Change FilterTerms to use LocalisableString --- osu.Game/Graphics/UserInterface/TriangleButton.cs | 3 ++- osu.Game/Graphics/UserInterfaceV2/RoundedButton.cs | 3 ++- osu.Game/Overlays/Chat/Listing/ChannelListingItem.cs | 3 ++- osu.Game/Overlays/Chat/Selection/ChannelListItem.cs | 3 ++- osu.Game/Overlays/Chat/Selection/ChannelSection.cs | 3 ++- osu.Game/Overlays/Music/PlaylistItem.cs | 3 ++- .../Overlays/Settings/Sections/Audio/OffsetSettings.cs | 2 +- osu.Game/Overlays/Settings/Sections/AudioSection.cs | 2 +- .../Overlays/Settings/Sections/Gameplay/ModsSettings.cs | 2 +- osu.Game/Overlays/Settings/Sections/Input/KeyBindingRow.cs | 3 ++- .../Settings/Sections/Input/KeyBindingsSubsection.cs | 3 ++- osu.Game/Overlays/Settings/SettingsButton.cs | 5 ++--- osu.Game/Overlays/Settings/SettingsDropdown.cs | 3 ++- osu.Game/Overlays/Settings/SettingsItem.cs | 7 ++++--- osu.Game/Overlays/Settings/SettingsSection.cs | 2 +- osu.Game/Overlays/Settings/SettingsSubsection.cs | 6 +----- .../Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs | 5 ++++- osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs | 3 ++- 18 files changed, 35 insertions(+), 26 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/TriangleButton.cs b/osu.Game/Graphics/UserInterface/TriangleButton.cs index 003a81f562..5ae6130039 100644 --- a/osu.Game/Graphics/UserInterface/TriangleButton.cs +++ b/osu.Game/Graphics/UserInterface/TriangleButton.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Localisation; using osu.Game.Graphics.Backgrounds; namespace osu.Game.Graphics.UserInterface @@ -27,7 +28,7 @@ namespace osu.Game.Graphics.UserInterface }); } - public virtual IEnumerable FilterTerms => new[] { Text.ToString() }; + public virtual IEnumerable FilterTerms => new[] { Text }; public bool MatchingFilter { diff --git a/osu.Game/Graphics/UserInterfaceV2/RoundedButton.cs b/osu.Game/Graphics/UserInterfaceV2/RoundedButton.cs index ec56b6d784..3c0c3b69e8 100644 --- a/osu.Game/Graphics/UserInterfaceV2/RoundedButton.cs +++ b/osu.Game/Graphics/UserInterfaceV2/RoundedButton.cs @@ -6,6 +6,7 @@ using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Localisation; using osu.Game.Graphics.UserInterface; using osu.Game.Overlays; @@ -39,7 +40,7 @@ namespace osu.Game.Graphics.UserInterfaceV2 private void updateCornerRadius() => Content.CornerRadius = DrawHeight / 2; - public virtual IEnumerable FilterTerms => new[] { Text.ToString() }; + public virtual IEnumerable FilterTerms => new[] { Text }; public bool MatchingFilter { diff --git a/osu.Game/Overlays/Chat/Listing/ChannelListingItem.cs b/osu.Game/Overlays/Chat/Listing/ChannelListingItem.cs index 1f0cbae7e2..86c81d5d79 100644 --- a/osu.Game/Overlays/Chat/Listing/ChannelListingItem.cs +++ b/osu.Game/Overlays/Chat/Listing/ChannelListingItem.cs @@ -12,6 +12,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Events; +using osu.Framework.Localisation; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; @@ -28,7 +29,7 @@ namespace osu.Game.Overlays.Chat.Listing public readonly Channel Channel; public bool FilteringActive { get; set; } - public IEnumerable FilterTerms => new[] { Channel.Name, Channel.Topic ?? string.Empty }; + public IEnumerable FilterTerms => new LocalisableString[] { Channel.Name, Channel.Topic ?? string.Empty }; public bool MatchingFilter { set => this.FadeTo(value ? 1f : 0f, 100); } private Box hoverBox = null!; diff --git a/osu.Game/Overlays/Chat/Selection/ChannelListItem.cs b/osu.Game/Overlays/Chat/Selection/ChannelListItem.cs index 1e58e8b640..59989ade7b 100644 --- a/osu.Game/Overlays/Chat/Selection/ChannelListItem.cs +++ b/osu.Game/Overlays/Chat/Selection/ChannelListItem.cs @@ -11,6 +11,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Events; +using osu.Framework.Localisation; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Online.Chat; @@ -36,7 +37,7 @@ namespace osu.Game.Overlays.Chat.Selection private Color4 topicColour; private Color4 hoverColour; - public IEnumerable FilterTerms => new[] { Channel.Name, Channel.Topic ?? string.Empty }; + public IEnumerable FilterTerms => new LocalisableString[] { Channel.Name, Channel.Topic ?? string.Empty }; public bool MatchingFilter { diff --git a/osu.Game/Overlays/Chat/Selection/ChannelSection.cs b/osu.Game/Overlays/Chat/Selection/ChannelSection.cs index 537ac975ac..070332180c 100644 --- a/osu.Game/Overlays/Chat/Selection/ChannelSection.cs +++ b/osu.Game/Overlays/Chat/Selection/ChannelSection.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.Linq; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Localisation; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Online.Chat; @@ -18,7 +19,7 @@ namespace osu.Game.Overlays.Chat.Selection public readonly FillFlowContainer ChannelFlow; public IEnumerable FilterableChildren => ChannelFlow.Children; - public IEnumerable FilterTerms => Array.Empty(); + public IEnumerable FilterTerms => Array.Empty(); public bool MatchingFilter { diff --git a/osu.Game/Overlays/Music/PlaylistItem.cs b/osu.Game/Overlays/Music/PlaylistItem.cs index f081cc0503..82599d3ec9 100644 --- a/osu.Game/Overlays/Music/PlaylistItem.cs +++ b/osu.Game/Overlays/Music/PlaylistItem.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -113,7 +114,7 @@ namespace osu.Game.Overlays.Music } } - public IEnumerable FilterTerms => Model.PerformRead(m => m.Metadata.GetSearchableTerms()); + public IEnumerable FilterTerms => Model.PerformRead(m => m.Metadata.GetSearchableTerms()).Select(s => (LocalisableString)s).ToArray(); private bool matchingFilter = true; diff --git a/osu.Game/Overlays/Settings/Sections/Audio/OffsetSettings.cs b/osu.Game/Overlays/Settings/Sections/Audio/OffsetSettings.cs index 673252a99e..eaacb9293f 100644 --- a/osu.Game/Overlays/Settings/Sections/Audio/OffsetSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Audio/OffsetSettings.cs @@ -16,7 +16,7 @@ namespace osu.Game.Overlays.Settings.Sections.Audio { protected override LocalisableString Header => AudioSettingsStrings.OffsetHeader; - public override IEnumerable FilterTerms => base.FilterTerms.Concat(new[] { "universal", "uo", "timing" }); + public override IEnumerable FilterTerms => base.FilterTerms.Concat(new LocalisableString[] { "universal", "uo", "timing" }); [BackgroundDependencyLoader] private void load(OsuConfigManager config) diff --git a/osu.Game/Overlays/Settings/Sections/AudioSection.cs b/osu.Game/Overlays/Settings/Sections/AudioSection.cs index 694da0529a..7ffa0bd415 100644 --- a/osu.Game/Overlays/Settings/Sections/AudioSection.cs +++ b/osu.Game/Overlays/Settings/Sections/AudioSection.cs @@ -20,7 +20,7 @@ namespace osu.Game.Overlays.Settings.Sections Icon = FontAwesome.Solid.VolumeUp }; - public override IEnumerable FilterTerms => base.FilterTerms.Concat(new[] { "sound" }); + public override IEnumerable FilterTerms => base.FilterTerms.Concat(new LocalisableString[] { "sound" }); public AudioSection() { diff --git a/osu.Game/Overlays/Settings/Sections/Gameplay/ModsSettings.cs b/osu.Game/Overlays/Settings/Sections/Gameplay/ModsSettings.cs index dfa060e8d5..ed88d80570 100644 --- a/osu.Game/Overlays/Settings/Sections/Gameplay/ModsSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Gameplay/ModsSettings.cs @@ -14,7 +14,7 @@ namespace osu.Game.Overlays.Settings.Sections.Gameplay { protected override LocalisableString Header => GameplaySettingsStrings.ModsHeader; - public override IEnumerable FilterTerms => base.FilterTerms.Concat(new[] { "mod" }); + public override IEnumerable FilterTerms => base.FilterTerms.Concat(new LocalisableString[] { "mod" }); [BackgroundDependencyLoader] private void load(OsuConfigManager config) diff --git a/osu.Game/Overlays/Settings/Sections/Input/KeyBindingRow.cs b/osu.Game/Overlays/Settings/Sections/Input/KeyBindingRow.cs index 459405f57d..7312748435 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/KeyBindingRow.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/KeyBindingRow.cs @@ -15,6 +15,7 @@ using osu.Framework.Graphics.Shapes; using osu.Framework.Input; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; +using osu.Framework.Localisation; using osu.Game.Database; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; @@ -67,7 +68,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input private Bindable isDefault { get; } = new BindableBool(true); - public IEnumerable FilterTerms => bindings.Select(b => keyCombinationProvider.GetReadableString(b.KeyCombination)).Prepend(text.Text.ToString()); + public IEnumerable FilterTerms => bindings.Select(b => (LocalisableString)keyCombinationProvider.GetReadableString(b.KeyCombination)).Prepend(text.Text); public KeyBindingRow(object action, List bindings) { diff --git a/osu.Game/Overlays/Settings/Sections/Input/KeyBindingsSubsection.cs b/osu.Game/Overlays/Settings/Sections/Input/KeyBindingsSubsection.cs index 922d371261..297af35fb5 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/KeyBindingsSubsection.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/KeyBindingsSubsection.cs @@ -6,6 +6,7 @@ using System.Linq; using osu.Framework.Allocation; using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; +using osu.Framework.Localisation; using osu.Game.Database; using osu.Game.Input.Bindings; using osu.Game.Rulesets; @@ -74,6 +75,6 @@ namespace osu.Game.Overlays.Settings.Sections.Input } // Empty FilterTerms so that the ResetButton is visible only when the whole subsection is visible. - public override IEnumerable FilterTerms => Enumerable.Empty(); + public override IEnumerable FilterTerms => Enumerable.Empty(); } } diff --git a/osu.Game/Overlays/Settings/SettingsButton.cs b/osu.Game/Overlays/Settings/SettingsButton.cs index be7f2de480..10aea92b22 100644 --- a/osu.Game/Overlays/Settings/SettingsButton.cs +++ b/osu.Game/Overlays/Settings/SettingsButton.cs @@ -20,13 +20,12 @@ namespace osu.Game.Overlays.Settings public LocalisableString TooltipText { get; set; } - public override IEnumerable FilterTerms + public override IEnumerable FilterTerms { get { if (TooltipText != default) - // TODO: this won't work as intended once the tooltip text is translated. - return base.FilterTerms.Append(TooltipText.ToString()); + return base.FilterTerms.Append(TooltipText); return base.FilterTerms; } diff --git a/osu.Game/Overlays/Settings/SettingsDropdown.cs b/osu.Game/Overlays/Settings/SettingsDropdown.cs index 1e90222d28..3c10c084ab 100644 --- a/osu.Game/Overlays/Settings/SettingsDropdown.cs +++ b/osu.Game/Overlays/Settings/SettingsDropdown.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.Linq; using osu.Framework.Bindables; using osu.Framework.Graphics; +using osu.Framework.Localisation; using osu.Game.Graphics.UserInterface; namespace osu.Game.Overlays.Settings @@ -25,7 +26,7 @@ namespace osu.Game.Overlays.Settings set => Control.ItemSource = value; } - public override IEnumerable FilterTerms => base.FilterTerms.Concat(Control.Items.Select(i => i.ToString())); + public override IEnumerable FilterTerms => base.FilterTerms.Concat(Control.Items.Select(i => (LocalisableString)i.ToString())); protected sealed override Drawable CreateControl() => CreateDropdown(); diff --git a/osu.Game/Overlays/Settings/SettingsItem.cs b/osu.Game/Overlays/Settings/SettingsItem.cs index afcd41af22..ee9daa1c0d 100644 --- a/osu.Game/Overlays/Settings/SettingsItem.cs +++ b/osu.Game/Overlays/Settings/SettingsItem.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -98,13 +99,13 @@ namespace osu.Game.Overlays.Settings set => controlWithCurrent.Current = value; } - public virtual IEnumerable FilterTerms + public virtual IEnumerable FilterTerms { get { - var keywords = new List(Keywords ?? Array.Empty()) + var keywords = new List(Keywords?.Select(k => (LocalisableString)k) ?? Array.Empty()) { - LabelText.ToString() + LabelText }; if (HasClassicDefault) diff --git a/osu.Game/Overlays/Settings/SettingsSection.cs b/osu.Game/Overlays/Settings/SettingsSection.cs index cfb0212b8c..da596e4d9d 100644 --- a/osu.Game/Overlays/Settings/SettingsSection.cs +++ b/osu.Game/Overlays/Settings/SettingsSection.cs @@ -32,7 +32,7 @@ namespace osu.Game.Overlays.Settings public abstract LocalisableString Header { get; } public IEnumerable FilterableChildren => Children.OfType(); - public virtual IEnumerable FilterTerms => new[] { Header.ToString() }; + public virtual IEnumerable FilterTerms => new[] { Header }; public const int ITEM_SPACING = 14; diff --git a/osu.Game/Overlays/Settings/SettingsSubsection.cs b/osu.Game/Overlays/Settings/SettingsSubsection.cs index c2cf08ac98..21391d5ccf 100644 --- a/osu.Game/Overlays/Settings/SettingsSubsection.cs +++ b/osu.Game/Overlays/Settings/SettingsSubsection.cs @@ -25,11 +25,7 @@ namespace osu.Game.Overlays.Settings public IEnumerable FilterableChildren => Children.OfType(); - // FilterTerms should contains both original string and localised string for user to search. - // Since LocalisableString is unable to get original string at this time (2021-08-14), - // only call .ToString() to use localised one. - // TODO: Update here when FilterTerms accept LocalisableString. - public virtual IEnumerable FilterTerms => new[] { Header.ToString() }; + public virtual IEnumerable FilterTerms => new[] { Header }; public bool MatchingFilter { diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs index 74e4225f11..d61fbea387 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs @@ -80,7 +80,10 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components matchingFilter &= criteria.Ruleset == null || r.Room.PlaylistItemStats.Value?.RulesetIDs.Any(id => id == criteria.Ruleset.OnlineID) != false; if (!string.IsNullOrEmpty(criteria.SearchString)) - matchingFilter &= r.FilterTerms.Any(term => term.Contains(criteria.SearchString, StringComparison.InvariantCultureIgnoreCase)); + { + // Room name isn't translatable, so ToString() is used here for simplicity. + matchingFilter &= r.FilterTerms.Any(term => term.ToString().Contains(criteria.SearchString, StringComparison.InvariantCultureIgnoreCase)); + } r.MatchingFilter = matchingFilter; } diff --git a/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs b/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs index 7baa346c6f..35c903eb0c 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs @@ -14,6 +14,7 @@ using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.UserInterface; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; +using osu.Framework.Localisation; using osu.Game.Extensions; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; @@ -101,7 +102,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge public bool FilteringActive { get; set; } - public IEnumerable FilterTerms => new[] { Room.Name.Value }; + public IEnumerable FilterTerms => new LocalisableString[] { Room.Name.Value }; private bool matchingFilter = true; From f78eb0066c7066f1f0ba4c71344a17487e8ef7a5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 16 May 2022 17:51:12 +0900 Subject: [PATCH 028/395] Update `Storage` in line with new methods --- osu.Game/IO/WrappedStorage.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game/IO/WrappedStorage.cs b/osu.Game/IO/WrappedStorage.cs index a6605de1d2..d4d9d531fa 100644 --- a/osu.Game/IO/WrappedStorage.cs +++ b/osu.Game/IO/WrappedStorage.cs @@ -67,9 +67,13 @@ namespace osu.Game.IO public override IEnumerable GetFiles(string path, string pattern = "*") => ToLocalRelative(UnderlyingStorage.GetFiles(MutatePath(path), pattern)); + public override Stream CreateFileSafely(string path) => UnderlyingStorage.CreateFileSafely(path); + public override Stream GetStream(string path, FileAccess access = FileAccess.Read, FileMode mode = FileMode.OpenOrCreate) => UnderlyingStorage.GetStream(MutatePath(path), access, mode); + public override void Move(string from, string to) => UnderlyingStorage.Move(from, to); + public override bool OpenFileExternally(string filename) => UnderlyingStorage.OpenFileExternally(MutatePath(filename)); public override bool PresentFileExternally(string filename) => UnderlyingStorage.PresentFileExternally(MutatePath(filename)); From 218642c300417c38e0b20b7af19911e4a36f6364 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 16 May 2022 18:03:53 +0900 Subject: [PATCH 029/395] Update unsafe file write usages --- osu.Game.Tests/Database/BeatmapImporterTests.cs | 2 +- osu.Game.Tests/Visual/Navigation/TestEFToRealmMigration.cs | 3 +-- osu.Game.Tournament.Tests/NonVisual/DataLoadTest.cs | 2 +- osu.Game.Tournament.Tests/Screens/TestSceneDrawingsScreen.cs | 2 +- osu.Game.Tournament/Models/StableInfo.cs | 2 +- osu.Game.Tournament/Screens/Drawings/DrawingsScreen.cs | 2 +- osu.Game.Tournament/TournamentGameBase.cs | 2 +- osu.Game/Database/LegacyExporter.cs | 2 +- osu.Game/Database/RealmAccess.cs | 2 +- osu.Game/Graphics/ScreenshotManager.cs | 2 +- osu.Game/OsuGameBase.cs | 2 +- osu.Game/Stores/RealmFileStore.cs | 2 +- 12 files changed, 12 insertions(+), 13 deletions(-) diff --git a/osu.Game.Tests/Database/BeatmapImporterTests.cs b/osu.Game.Tests/Database/BeatmapImporterTests.cs index b7bfe14402..00276955aa 100644 --- a/osu.Game.Tests/Database/BeatmapImporterTests.cs +++ b/osu.Game.Tests/Database/BeatmapImporterTests.cs @@ -507,7 +507,7 @@ namespace osu.Game.Tests.Database using (var stream = storage.GetStream(firstFile.File.GetStoragePath())) originalLength = stream.Length; - using (var stream = storage.GetStream(firstFile.File.GetStoragePath(), FileAccess.Write, FileMode.Create)) + using (var stream = storage.CreateFileSafely(firstFile.File.GetStoragePath())) stream.WriteByte(0); var importedSecondTime = await LoadOszIntoStore(importer, realm.Realm); diff --git a/osu.Game.Tests/Visual/Navigation/TestEFToRealmMigration.cs b/osu.Game.Tests/Visual/Navigation/TestEFToRealmMigration.cs index 8498b9b28f..2ea768b878 100644 --- a/osu.Game.Tests/Visual/Navigation/TestEFToRealmMigration.cs +++ b/osu.Game.Tests/Visual/Navigation/TestEFToRealmMigration.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.IO; using System.Linq; using System.Runtime.InteropServices; using NUnit.Framework; @@ -25,7 +24,7 @@ namespace osu.Game.Tests.Visual.Navigation if (isDisposing) return; - using (var outStream = LocalStorage.GetStream(DatabaseContextFactory.DATABASE_NAME, FileAccess.Write, FileMode.Create)) + using (var outStream = LocalStorage.CreateFileSafely(DatabaseContextFactory.DATABASE_NAME)) using (var stream = TestResources.OpenResource(DatabaseContextFactory.DATABASE_NAME)) stream.CopyTo(outStream); } diff --git a/osu.Game.Tournament.Tests/NonVisual/DataLoadTest.cs b/osu.Game.Tournament.Tests/NonVisual/DataLoadTest.cs index 4c1256df2e..e5c539bbf1 100644 --- a/osu.Game.Tournament.Tests/NonVisual/DataLoadTest.cs +++ b/osu.Game.Tournament.Tests/NonVisual/DataLoadTest.cs @@ -28,7 +28,7 @@ namespace osu.Game.Tournament.Tests.NonVisual // ReSharper disable once AccessToDisposedClosure var storage = host.Storage.GetStorageForDirectory(Path.Combine("tournaments", "default")); - using (var stream = storage.GetStream("bracket.json", FileAccess.Write, FileMode.Create)) + using (var stream = storage.CreateFileSafely("bracket.json")) using (var writer = new StreamWriter(stream)) { writer.Write(@"{ diff --git a/osu.Game.Tournament.Tests/Screens/TestSceneDrawingsScreen.cs b/osu.Game.Tournament.Tests/Screens/TestSceneDrawingsScreen.cs index e2954c8f10..0e38c777ba 100644 --- a/osu.Game.Tournament.Tests/Screens/TestSceneDrawingsScreen.cs +++ b/osu.Game.Tournament.Tests/Screens/TestSceneDrawingsScreen.cs @@ -15,7 +15,7 @@ namespace osu.Game.Tournament.Tests.Screens [BackgroundDependencyLoader] private void load(Storage storage) { - using (var stream = storage.GetStream("drawings.txt", FileAccess.Write)) + using (var stream = storage.CreateFileSafely("drawings.txt")) using (var writer = new StreamWriter(stream)) { writer.WriteLine("KR : South Korea : KOR"); diff --git a/osu.Game.Tournament/Models/StableInfo.cs b/osu.Game.Tournament/Models/StableInfo.cs index 1ebc81c773..c835b11a4d 100644 --- a/osu.Game.Tournament/Models/StableInfo.cs +++ b/osu.Game.Tournament/Models/StableInfo.cs @@ -45,7 +45,7 @@ namespace osu.Game.Tournament.Models public void SaveChanges() { - using (var stream = configStorage.GetStream(config_path, FileAccess.Write, FileMode.Create)) + using (var stream = configStorage.CreateFileSafely(config_path)) using (var sw = new StreamWriter(stream)) { sw.Write(JsonConvert.SerializeObject(this, diff --git a/osu.Game.Tournament/Screens/Drawings/DrawingsScreen.cs b/osu.Game.Tournament/Screens/Drawings/DrawingsScreen.cs index d02e0ebf86..53ac93afea 100644 --- a/osu.Game.Tournament/Screens/Drawings/DrawingsScreen.cs +++ b/osu.Game.Tournament/Screens/Drawings/DrawingsScreen.cs @@ -205,7 +205,7 @@ namespace osu.Game.Tournament.Screens.Drawings try { // Write to drawings_results - using (Stream stream = storage.GetStream(results_filename, FileAccess.Write, FileMode.Create)) + using (Stream stream = storage.CreateFileSafely(results_filename)) using (StreamWriter sw = new StreamWriter(stream)) { sw.Write(text); diff --git a/osu.Game.Tournament/TournamentGameBase.cs b/osu.Game.Tournament/TournamentGameBase.cs index a251a043f7..363baccb37 100644 --- a/osu.Game.Tournament/TournamentGameBase.cs +++ b/osu.Game.Tournament/TournamentGameBase.cs @@ -321,7 +321,7 @@ namespace osu.Game.Tournament Converters = new JsonConverter[] { new JsonPointConverter() } }); - using (var stream = storage.GetStream(BRACKET_FILENAME, FileAccess.Write, FileMode.Create)) + using (var stream = storage.CreateFileSafely(BRACKET_FILENAME)) using (var sw = new StreamWriter(stream)) sw.Write(serialisedLadder); } diff --git a/osu.Game/Database/LegacyExporter.cs b/osu.Game/Database/LegacyExporter.cs index ee960b6b30..992d1854e7 100644 --- a/osu.Game/Database/LegacyExporter.cs +++ b/osu.Game/Database/LegacyExporter.cs @@ -37,7 +37,7 @@ namespace osu.Game.Database { string filename = $"{item.GetDisplayString().GetValidArchiveContentFilename()}{FileExtension}"; - using (var stream = exportStorage.GetStream(filename, FileAccess.Write, FileMode.Create)) + using (var stream = exportStorage.CreateFileSafely(filename)) ExportModelTo(item, stream); exportStorage.PresentFileExternally(filename); diff --git a/osu.Game/Database/RealmAccess.cs b/osu.Game/Database/RealmAccess.cs index 937876a70e..dbd3b96763 100644 --- a/osu.Game/Database/RealmAccess.cs +++ b/osu.Game/Database/RealmAccess.cs @@ -242,7 +242,7 @@ namespace osu.Game.Database storage.Delete(Filename); using (var inputStream = storage.GetStream(recoveryFilename)) - using (var outputStream = storage.GetStream(Filename, FileAccess.Write, FileMode.Create)) + using (var outputStream = storage.CreateFileSafely(Filename)) inputStream.CopyTo(outputStream); storage.Delete(recoveryFilename); diff --git a/osu.Game/Graphics/ScreenshotManager.cs b/osu.Game/Graphics/ScreenshotManager.cs index a2f1a3d7b9..1fc2633143 100644 --- a/osu.Game/Graphics/ScreenshotManager.cs +++ b/osu.Game/Graphics/ScreenshotManager.cs @@ -118,7 +118,7 @@ namespace osu.Game.Graphics if (filename == null) return; - using (var stream = storage.GetStream(filename, FileAccess.Write)) + using (var stream = storage.CreateFileSafely(filename)) { switch (screenshotFormat.Value) { diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 2e4758a134..564cb6c131 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -243,7 +243,7 @@ namespace osu.Game { if (source != null) { - using (var destination = Storage.GetStream(Path.Combine(backup_folder, $"collection.{migration}.db"), FileAccess.Write, FileMode.CreateNew)) + using (var destination = Storage.CreateFileSafely(Path.Combine(backup_folder, $"collection.{migration}.db"))) source.CopyTo(destination); } } diff --git a/osu.Game/Stores/RealmFileStore.cs b/osu.Game/Stores/RealmFileStore.cs index b5dd3d64e4..457d70f29a 100644 --- a/osu.Game/Stores/RealmFileStore.cs +++ b/osu.Game/Stores/RealmFileStore.cs @@ -65,7 +65,7 @@ namespace osu.Game.Stores { data.Seek(0, SeekOrigin.Begin); - using (var output = Storage.GetStream(file.GetStoragePath(), FileAccess.Write)) + using (var output = Storage.CreateFileSafely(file.GetStoragePath())) data.CopyTo(output); data.Seek(0, SeekOrigin.Begin); From 48b7e0f0900d52a23bb9fa1a753983c373ca6ec8 Mon Sep 17 00:00:00 2001 From: Jay L Date: Tue, 10 May 2022 18:17:40 +1000 Subject: [PATCH 030/395] Stamina Skill Rewrite and Refactor --- .../Difficulty/Skills/Stamina.cs | 139 +++++++++--------- .../Difficulty/TaikoDifficultyCalculator.cs | 20 +-- 2 files changed, 75 insertions(+), 84 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Difficulty/Skills/Stamina.cs b/osu.Game.Rulesets.Taiko/Difficulty/Skills/Stamina.cs index 54cf233d69..b10da95444 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/Skills/Stamina.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/Skills/Stamina.cs @@ -1,16 +1,50 @@ // 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 System; using osu.Game.Rulesets.Difficulty.Preprocessing; using osu.Game.Rulesets.Difficulty.Skills; -using osu.Game.Rulesets.Difficulty.Utils; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Taiko.Difficulty.Preprocessing; using osu.Game.Rulesets.Taiko.Objects; namespace osu.Game.Rulesets.Taiko.Difficulty.Skills { + class SingleKeyStamina + { + private double previousHitTime = -1; + + private double strainValueOf(DifficultyHitObject current) + { + if (previousHitTime == -1) + { + previousHitTime = current.StartTime; + return 0; + } + else + { + double objectStrain = 0.5; + objectStrain += speedBonus(current.StartTime - previousHitTime); + previousHitTime = current.StartTime; + return objectStrain; + } + } + + public double StrainValueAt(DifficultyHitObject current) + { + return strainValueOf(current); + } + + /// + /// Applies a speed bonus dependent on the time since the last hit performed using this key. + /// + /// The duration between the current and previous note hit using the same key. + private double speedBonus(double notePairDuration) + { + return 175 / Math.Pow(notePairDuration + 100, 1); + } + } + /// /// Calculates the stamina coefficient of taiko difficulty. /// @@ -22,39 +56,43 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Skills protected override double SkillMultiplier => 1; protected override double StrainDecayBase => 0.4; - /// - /// Maximum number of entries to keep in . - /// - private const int max_history_length = 2; + private SingleKeyStamina[] keyStamina = new SingleKeyStamina[4] + { + new SingleKeyStamina(), + new SingleKeyStamina(), + new SingleKeyStamina(), + new SingleKeyStamina() + }; - /// - /// The index of the hand this instance is associated with. - /// - /// - /// The value of 0 indicates the left hand (full alternating gameplay starting with left hand is assumed). - /// This naturally translates onto index offsets of the objects in the map. - /// - private readonly int hand; - - /// - /// Stores the last durations between notes hit with the hand indicated by . - /// - private readonly LimitedCapacityQueue notePairDurationHistory = new LimitedCapacityQueue(max_history_length); - - /// - /// Stores the of the last object that was hit by the other hand. - /// - private double offhandObjectDuration = double.MaxValue; + private int donIndex = 1; + private int katIndex = 3; /// /// Creates a skill. /// /// Mods for use in skill calculations. - /// Whether this instance is performing calculations for the right hand. - public Stamina(Mod[] mods, bool rightHand) + public Stamina(Mod[] mods) : base(mods) { - hand = rightHand ? 1 : 0; + } + + private SingleKeyStamina getNextSingleKeyStamina(TaikoDifficultyHitObject current) + { + if (current.HitType == HitType.Centre) + { + donIndex = donIndex == 0 ? 1 : 0; + return keyStamina[donIndex]; + } + else + { + katIndex = katIndex == 2 ? 3 : 2; + return keyStamina[katIndex]; + } + } + + private double sigmoid(double val, double center, double width) + { + return Math.Tanh(Math.E * -(val - center) / width); } protected override double StrainValueOf(DifficultyHitObject current) @@ -65,52 +103,9 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Skills } TaikoDifficultyHitObject hitObject = (TaikoDifficultyHitObject)current; + double objectStrain = getNextSingleKeyStamina(hitObject).StrainValueAt(hitObject); - if (hitObject.ObjectIndex % 2 == hand) - { - double objectStrain = 1; - - if (hitObject.ObjectIndex == 1) - return 1; - - notePairDurationHistory.Enqueue(hitObject.DeltaTime + offhandObjectDuration); - - double shortestRecentNote = notePairDurationHistory.Min(); - objectStrain += speedBonus(shortestRecentNote); - - if (hitObject.StaminaCheese) - objectStrain *= cheesePenalty(hitObject.DeltaTime + offhandObjectDuration); - - return objectStrain; - } - - offhandObjectDuration = hitObject.DeltaTime; - return 0; - } - - /// - /// Applies a penalty for hit objects marked with . - /// - /// The duration between the current and previous note hit using the hand indicated by . - private double cheesePenalty(double notePairDuration) - { - if (notePairDuration > 125) return 1; - if (notePairDuration < 100) return 0.6; - - return 0.6 + (notePairDuration - 100) * 0.016; - } - - /// - /// Applies a speed bonus dependent on the time since the last hit performed using this hand. - /// - /// The duration between the current and previous note hit using the hand indicated by . - private double speedBonus(double notePairDuration) - { - if (notePairDuration >= 200) return 0; - - double bonus = 200 - notePairDuration; - bonus *= bonus; - return bonus / 100000; + return objectStrain; } } } diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs index a9d512f076..de69f63bf4 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs @@ -22,7 +22,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty { private const double rhythm_skill_multiplier = 0.014; private const double colour_skill_multiplier = 0.01; - private const double stamina_skill_multiplier = 0.02; + private const double stamina_skill_multiplier = 0.021; public TaikoDifficultyCalculator(IRulesetInfo ruleset, IWorkingBeatmap beatmap) : base(ruleset, beatmap) @@ -33,8 +33,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty { new Colour(mods), new Rhythm(mods), - new Stamina(mods, true), - new Stamina(mods, false), + new Stamina(mods) }; protected override Mod[] DifficultyAdjustmentMods => new Mod[] @@ -58,7 +57,6 @@ namespace osu.Game.Rulesets.Taiko.Difficulty ); } - new StaminaCheeseDetector(taikoDifficultyHitObjects).FindCheese(); return taikoDifficultyHitObjects; } @@ -69,17 +67,16 @@ namespace osu.Game.Rulesets.Taiko.Difficulty var colour = (Colour)skills[0]; var rhythm = (Rhythm)skills[1]; - var staminaRight = (Stamina)skills[2]; - var staminaLeft = (Stamina)skills[3]; + var stamina = (Stamina)skills[2]; double colourRating = colour.DifficultyValue() * colour_skill_multiplier; double rhythmRating = rhythm.DifficultyValue() * rhythm_skill_multiplier; - double staminaRating = (staminaRight.DifficultyValue() + staminaLeft.DifficultyValue()) * stamina_skill_multiplier; + double staminaRating = stamina.DifficultyValue() * stamina_skill_multiplier; double staminaPenalty = simpleColourPenalty(staminaRating, colourRating); staminaRating *= staminaPenalty; - double combinedRating = locallyCombinedDifficulty(colour, rhythm, staminaRight, staminaLeft, staminaPenalty); + double combinedRating = locallyCombinedDifficulty(colour, rhythm, stamina, staminaPenalty); double separatedRating = norm(1.5, colourRating, rhythmRating, staminaRating); double starRating = 1.4 * separatedRating + 0.5 * combinedRating; starRating = rescale(starRating); @@ -127,20 +124,19 @@ namespace osu.Game.Rulesets.Taiko.Difficulty /// For each section, the peak strains of all separate skills are combined into a single peak strain for the section. /// The resulting partial rating of the beatmap is a weighted sum of the combined peaks (higher peaks are weighted more). /// - private double locallyCombinedDifficulty(Colour colour, Rhythm rhythm, Stamina staminaRight, Stamina staminaLeft, double staminaPenalty) + private double locallyCombinedDifficulty(Colour colour, Rhythm rhythm, Stamina stamina, double staminaPenalty) { List peaks = new List(); var colourPeaks = colour.GetCurrentStrainPeaks().ToList(); var rhythmPeaks = rhythm.GetCurrentStrainPeaks().ToList(); - var staminaRightPeaks = staminaRight.GetCurrentStrainPeaks().ToList(); - var staminaLeftPeaks = staminaLeft.GetCurrentStrainPeaks().ToList(); + var staminaPeaks = stamina.GetCurrentStrainPeaks().ToList(); for (int i = 0; i < colourPeaks.Count; i++) { double colourPeak = colourPeaks[i] * colour_skill_multiplier; double rhythmPeak = rhythmPeaks[i] * rhythm_skill_multiplier; - double staminaPeak = (staminaRightPeaks[i] + staminaLeftPeaks[i]) * stamina_skill_multiplier * staminaPenalty; + double staminaPeak = staminaPeaks[i] * stamina_skill_multiplier * staminaPenalty; double peak = norm(2, colourPeak, rhythmPeak, staminaPeak); From f15738fded36c06d022014871effae3c3c380709 Mon Sep 17 00:00:00 2001 From: Jay L Date: Sun, 15 May 2022 22:09:24 +1000 Subject: [PATCH 031/395] Remove Redundant Detection Not needed, as nerf is not required anymore and has negative effect. --- .../Preprocessing/StaminaCheeseDetector.cs | 145 ------------------ 1 file changed, 145 deletions(-) delete mode 100644 osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/StaminaCheeseDetector.cs diff --git a/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/StaminaCheeseDetector.cs b/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/StaminaCheeseDetector.cs deleted file mode 100644 index 3b1a9ad777..0000000000 --- a/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/StaminaCheeseDetector.cs +++ /dev/null @@ -1,145 +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 osu.Game.Rulesets.Difficulty.Utils; -using osu.Game.Rulesets.Taiko.Objects; - -namespace osu.Game.Rulesets.Taiko.Difficulty.Preprocessing -{ - /// - /// Detects special hit object patterns which are easier to hit using special techniques - /// than normally assumed in the fully-alternating play style. - /// - /// - /// This component detects two basic types of patterns, leveraged by the following techniques: - /// - /// Rolling allows hitting patterns with quickly and regularly alternating notes with a single hand. - /// TL tapping makes hitting longer sequences of consecutive same-colour notes with little to no colour changes in-between. - /// - /// - public class StaminaCheeseDetector - { - /// - /// The minimum number of consecutive objects with repeating patterns that can be classified as hittable using a roll. - /// - private const int roll_min_repetitions = 12; - - /// - /// The minimum number of consecutive objects with repeating patterns that can be classified as hittable using a TL tap. - /// - private const int tl_min_repetitions = 16; - - /// - /// The list of all s in the map. - /// - private readonly List hitObjects; - - public StaminaCheeseDetector(List hitObjects) - { - this.hitObjects = hitObjects; - } - - /// - /// Finds and marks all objects in that special difficulty-reducing techiques apply to - /// with the flag. - /// - public void FindCheese() - { - findRolls(3); - findRolls(4); - - findTlTap(0, HitType.Rim); - findTlTap(1, HitType.Rim); - findTlTap(0, HitType.Centre); - findTlTap(1, HitType.Centre); - } - - /// - /// Finds and marks all sequences hittable using a roll. - /// - /// The length of a single repeating pattern to consider (triplets/quadruplets). - private void findRolls(int patternLength) - { - var history = new LimitedCapacityQueue(2 * patternLength); - - // for convenience, we're tracking the index of the item *before* our suspected repeat's start, - // as that index can be simply subtracted from the current index to get the number of elements in between - // without off-by-one errors - int indexBeforeLastRepeat = -1; - int lastMarkEnd = 0; - - for (int i = 0; i < hitObjects.Count; i++) - { - history.Enqueue(hitObjects[i]); - if (!history.Full) - continue; - - if (!containsPatternRepeat(history, patternLength)) - { - // we're setting this up for the next iteration, hence the +1. - // right here this index will point at the queue's front (oldest item), - // but that item is about to be popped next loop with an enqueue. - indexBeforeLastRepeat = i - history.Count + 1; - continue; - } - - int repeatedLength = i - indexBeforeLastRepeat; - if (repeatedLength < roll_min_repetitions) - continue; - - markObjectsAsCheese(Math.Max(lastMarkEnd, i - repeatedLength + 1), i); - lastMarkEnd = i; - } - } - - /// - /// Determines whether the objects stored in contain a repetition of a pattern of length . - /// - private static bool containsPatternRepeat(LimitedCapacityQueue history, int patternLength) - { - for (int j = 0; j < patternLength; j++) - { - if (history[j].HitType != history[j + patternLength].HitType) - return false; - } - - return true; - } - - /// - /// Finds and marks all sequences hittable using a TL tap. - /// - /// Whether sequences starting with an odd- (1) or even-indexed (0) hit object should be checked. - /// The type of hit to check for TL taps. - private void findTlTap(int parity, HitType type) - { - int tlLength = -2; - int lastMarkEnd = 0; - - for (int i = parity; i < hitObjects.Count; i += 2) - { - if (hitObjects[i].HitType == type) - tlLength += 2; - else - tlLength = -2; - - if (tlLength < tl_min_repetitions) - continue; - - markObjectsAsCheese(Math.Max(lastMarkEnd, i - tlLength + 1), i); - lastMarkEnd = i; - } - } - - /// - /// Marks all objects from to (inclusive) as . - /// - private void markObjectsAsCheese(int start, int end) - { - for (int i = start; i <= end; i++) - hitObjects[i].StaminaCheese = true; - } - } -} From d174099016363de0cd77f62b5eb0c183d2df0b47 Mon Sep 17 00:00:00 2001 From: Jay L Date: Sun, 15 May 2022 22:09:54 +1000 Subject: [PATCH 032/395] Refactor and Cleanup of Stamina.cs --- .../Difficulty/Skills/Stamina.cs | 60 ++++++++++--------- 1 file changed, 32 insertions(+), 28 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Difficulty/Skills/Stamina.cs b/osu.Game.Rulesets.Taiko/Difficulty/Skills/Stamina.cs index b10da95444..6d4a1fd948 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/Skills/Stamina.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/Skills/Stamina.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 osu.Game.Rulesets.Difficulty.Preprocessing; using osu.Game.Rulesets.Difficulty.Skills; using osu.Game.Rulesets.Mods; @@ -10,29 +9,28 @@ using osu.Game.Rulesets.Taiko.Objects; namespace osu.Game.Rulesets.Taiko.Difficulty.Skills { - class SingleKeyStamina + /// + /// Stamina of a single key, calculated based on repetition speed. + /// + public class SingleKeyStamina { private double previousHitTime = -1; - private double strainValueOf(DifficultyHitObject current) + /// + /// Similar to + /// + public double StrainValueOf(DifficultyHitObject current) { if (previousHitTime == -1) { previousHitTime = current.StartTime; return 0; } - else - { - double objectStrain = 0.5; - objectStrain += speedBonus(current.StartTime - previousHitTime); - previousHitTime = current.StartTime; - return objectStrain; - } - } - public double StrainValueAt(DifficultyHitObject current) - { - return strainValueOf(current); + double objectStrain = 0.5; + objectStrain += speedBonus(current.StartTime - previousHitTime); + previousHitTime = current.StartTime; + return objectStrain; } /// @@ -41,7 +39,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Skills /// The duration between the current and previous note hit using the same key. private double speedBonus(double notePairDuration) { - return 175 / Math.Pow(notePairDuration + 100, 1); + return 175 / (notePairDuration + 100); } } @@ -56,7 +54,10 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Skills protected override double SkillMultiplier => 1; protected override double StrainDecayBase => 0.4; - private SingleKeyStamina[] keyStamina = new SingleKeyStamina[4] + /// + /// Stamina of each individual keys, calculated based on repetition speed. + /// + private readonly SingleKeyStamina[] keyStamina = { new SingleKeyStamina(), new SingleKeyStamina(), @@ -64,7 +65,14 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Skills new SingleKeyStamina() }; + /// + /// Current index to for a don hit. + /// private int donIndex = 1; + + /// + /// Current index to for a kat hit. + /// private int katIndex = 3; /// @@ -76,23 +84,21 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Skills { } + /// + /// Get the next to use for the given . + /// + /// The current . private SingleKeyStamina getNextSingleKeyStamina(TaikoDifficultyHitObject current) { + // Alternate key for the same color. if (current.HitType == HitType.Centre) { donIndex = donIndex == 0 ? 1 : 0; return keyStamina[donIndex]; } - else - { - katIndex = katIndex == 2 ? 3 : 2; - return keyStamina[katIndex]; - } - } - private double sigmoid(double val, double center, double width) - { - return Math.Tanh(Math.E * -(val - center) / width); + katIndex = katIndex == 2 ? 3 : 2; + return keyStamina[katIndex]; } protected override double StrainValueOf(DifficultyHitObject current) @@ -103,9 +109,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Skills } TaikoDifficultyHitObject hitObject = (TaikoDifficultyHitObject)current; - double objectStrain = getNextSingleKeyStamina(hitObject).StrainValueAt(hitObject); - - return objectStrain; + return getNextSingleKeyStamina(hitObject).StrainValueOf(hitObject); } } } From b44afb19b16a19e7464c9f1281307f4c396e1fc7 Mon Sep 17 00:00:00 2001 From: Jay L Date: Sun, 15 May 2022 22:10:18 +1000 Subject: [PATCH 033/395] Include Convert Penalty For low colour variance --- .../Difficulty/TaikoDifficultyCalculator.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs index de69f63bf4..5816b9957c 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs @@ -76,6 +76,11 @@ namespace osu.Game.Rulesets.Taiko.Difficulty double staminaPenalty = simpleColourPenalty(staminaRating, colourRating); staminaRating *= staminaPenalty; + if (beatmap.BeatmapInfo.Ruleset.OnlineID == 0 && colourRating < 0.05) + { + staminaPenalty *= 0.25; + } + double combinedRating = locallyCombinedDifficulty(colour, rhythm, stamina, staminaPenalty); double separatedRating = norm(1.5, colourRating, rhythmRating, staminaRating); double starRating = 1.4 * separatedRating + 0.5 * combinedRating; From d94e30bed3985f3301ea8d769ad5060affc94755 Mon Sep 17 00:00:00 2001 From: Jay L Date: Sun, 15 May 2022 22:11:01 +1000 Subject: [PATCH 034/395] Rewrite Performance difficultyValue Calculation To change values in line with the community survey, pp has been rescaled immensely, with changes on both the lower and upper end. --- .../Difficulty/TaikoPerformanceCalculator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs index a8122551ff..8d99fd3b87 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs @@ -59,7 +59,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty private double computeDifficultyValue(ScoreInfo score, TaikoDifficultyAttributes attributes) { - double difficultyValue = Math.Pow(5.0 * Math.Max(1.0, attributes.StarRating / 0.0075) - 4.0, 2.0) / 100000.0; + double difficultyValue = Math.Pow(5 * Math.Max(1.0, attributes.StarRating / 0.175) - 4.0, 2.25) / 450.0; double lengthBonus = 1 + 0.1 * Math.Min(1.0, totalHits / 1500.0); difficultyValue *= lengthBonus; From 94294ba351c2c4609328da2ada403a37b89c55b7 Mon Sep 17 00:00:00 2001 From: Jay L Date: Mon, 16 May 2022 10:26:47 +1000 Subject: [PATCH 035/395] Apply Review Changes --- osu.Game.Rulesets.Taiko/Difficulty/Skills/Stamina.cs | 2 +- osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Taiko/Difficulty/Skills/Stamina.cs b/osu.Game.Rulesets.Taiko/Difficulty/Skills/Stamina.cs index 6d4a1fd948..7846e97a65 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/Skills/Stamina.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/Skills/Stamina.cs @@ -14,7 +14,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Skills /// public class SingleKeyStamina { - private double previousHitTime = -1; + private double previousHitTime; /// /// Similar to diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs index 5816b9957c..1aa31c6fe4 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs @@ -76,6 +76,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty double staminaPenalty = simpleColourPenalty(staminaRating, colourRating); staminaRating *= staminaPenalty; + //TODO : This is a temporary fix for the stamina rating of converts, due to their low colour variance. if (beatmap.BeatmapInfo.Ruleset.OnlineID == 0 && colourRating < 0.05) { staminaPenalty *= 0.25; From c7ec95c0d0d4416561ef622e134b1aef81ea285e Mon Sep 17 00:00:00 2001 From: Jay L Date: Mon, 16 May 2022 14:25:07 +1000 Subject: [PATCH 036/395] Rectify Review Changes --- osu.Game.Rulesets.Taiko/Difficulty/Skills/Stamina.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Difficulty/Skills/Stamina.cs b/osu.Game.Rulesets.Taiko/Difficulty/Skills/Stamina.cs index 7846e97a65..d54da8b869 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/Skills/Stamina.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/Skills/Stamina.cs @@ -14,14 +14,14 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Skills /// public class SingleKeyStamina { - private double previousHitTime; + private double? previousHitTime; /// /// Similar to /// public double StrainValueOf(DifficultyHitObject current) { - if (previousHitTime == -1) + if (previousHitTime == null) { previousHitTime = current.StartTime; return 0; From db6abd86f03bc538fa2c3ffda98120b883f37d5c Mon Sep 17 00:00:00 2001 From: Jay L Date: Mon, 16 May 2022 14:48:39 +1000 Subject: [PATCH 037/395] Rectify null value call tired programming is never a good idea --- osu.Game.Rulesets.Taiko/Difficulty/Skills/Stamina.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Taiko/Difficulty/Skills/Stamina.cs b/osu.Game.Rulesets.Taiko/Difficulty/Skills/Stamina.cs index d54da8b869..744dafd57e 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/Skills/Stamina.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/Skills/Stamina.cs @@ -28,7 +28,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Skills } double objectStrain = 0.5; - objectStrain += speedBonus(current.StartTime - previousHitTime); + objectStrain += speedBonus(current.StartTime - previousHitTime.Value); previousHitTime = current.StartTime; return objectStrain; } From 934bcaf82e8851415a44656151f30a1c9eb5ed7f Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 16 May 2022 14:22:30 +0900 Subject: [PATCH 038/395] Adjust tests --- .../TaikoDifficultyCalculatorTest.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Taiko.Tests/TaikoDifficultyCalculatorTest.cs b/osu.Game.Rulesets.Taiko.Tests/TaikoDifficultyCalculatorTest.cs index 226da7df09..51332a1ece 100644 --- a/osu.Game.Rulesets.Taiko.Tests/TaikoDifficultyCalculatorTest.cs +++ b/osu.Game.Rulesets.Taiko.Tests/TaikoDifficultyCalculatorTest.cs @@ -14,13 +14,13 @@ namespace osu.Game.Rulesets.Taiko.Tests { protected override string ResourceAssembly => "osu.Game.Rulesets.Taiko"; - [TestCase(2.2420075288523802d, 200, "diffcalc-test")] - [TestCase(2.2420075288523802d, 200, "diffcalc-test-strong")] + [TestCase(1.9971301024093662d, 200, "diffcalc-test")] + [TestCase(1.9971301024093662d, 200, "diffcalc-test-strong")] public void Test(double expectedStarRating, int expectedMaxCombo, string name) => base.Test(expectedStarRating, expectedMaxCombo, name); - [TestCase(3.134084469440479d, 200, "diffcalc-test")] - [TestCase(3.134084469440479d, 200, "diffcalc-test-strong")] + [TestCase(3.1645810961313674d, 200, "diffcalc-test")] + [TestCase(3.1645810961313674d, 200, "diffcalc-test-strong")] public void TestClockRateAdjusted(double expectedStarRating, int expectedMaxCombo, string name) => Test(expectedStarRating, expectedMaxCombo, name, new TaikoModDoubleTime()); From ef5b2233d7403f671a9ac9e13c708e1d02aef52c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 16 May 2022 19:21:19 +0900 Subject: [PATCH 039/395] Improve the first run progress button to show loading and completion status --- .../FirstRunSetup/ProgressRoundedButton.cs | 103 ++++++++++++++++++ .../Overlays/FirstRunSetup/ScreenBeatmaps.cs | 45 +------- 2 files changed, 106 insertions(+), 42 deletions(-) create mode 100644 osu.Game/Overlays/FirstRunSetup/ProgressRoundedButton.cs diff --git a/osu.Game/Overlays/FirstRunSetup/ProgressRoundedButton.cs b/osu.Game/Overlays/FirstRunSetup/ProgressRoundedButton.cs new file mode 100644 index 0000000000..ee2db1f3d4 --- /dev/null +++ b/osu.Game/Overlays/FirstRunSetup/ProgressRoundedButton.cs @@ -0,0 +1,103 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +#nullable enable +using System; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; +using osu.Game.Graphics; +using osu.Game.Graphics.UserInterface; +using osu.Game.Graphics.UserInterfaceV2; +using osuTK; + +namespace osu.Game.Overlays.FirstRunSetup +{ + public class ProgressRoundedButton : RoundedButton + { + public new Action? Action; + + [Resolved] + private OsuColour colours { get; set; } = null!; + + private ProgressBar progressBar = null!; + + private LoadingSpinner loading = null!; + + private SpriteIcon tick = null!; + + public ProgressRoundedButton() + { + base.Action = () => + { + loading.Show(); + Enabled.Value = false; + + Action?.Invoke(); + }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + AddRange(new Drawable[] + { + progressBar = new ProgressBar(false) + { + RelativeSizeAxes = Axes.Both, + Blending = BlendingParameters.Additive, + FillColour = BackgroundColour, + Alpha = 0.5f, + Depth = float.MinValue + }, + new Container + { + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + Margin = new MarginPadding(15), + Size = new Vector2(20), + Children = new Drawable[] + { + loading = new LoadingSpinner + { + RelativeSizeAxes = Axes.Both, + Size = Vector2.One, + }, + tick = new SpriteIcon + { + Icon = FontAwesome.Solid.Check, + RelativeSizeAxes = Axes.Both, + Size = Vector2.One, + Alpha = 0, + } + } + }, + }); + } + + public void Complete() + { + loading.Hide(); + tick.FadeIn(500, Easing.OutQuint); + + Background.FadeColour(colours.Green, 500, Easing.OutQuint); + progressBar.FillColour = colours.Green; + + this.TransformBindableTo(progressBar.Current, 1, 500, Easing.OutQuint); + } + + public void Abort() + { + loading.Hide(); + Enabled.Value = true; + this.TransformBindableTo(progressBar.Current, 0, 500, Easing.OutQuint); + } + + public void SetProgress(double progress, bool animated) + { + this.TransformBindableTo(progressBar.Current, progress, animated ? 500 : 0, Easing.OutQuint); + } + } +} diff --git a/osu.Game/Overlays/FirstRunSetup/ScreenBeatmaps.cs b/osu.Game/Overlays/FirstRunSetup/ScreenBeatmaps.cs index 66acdca8c7..f7615d5ce9 100644 --- a/osu.Game/Overlays/FirstRunSetup/ScreenBeatmaps.cs +++ b/osu.Game/Overlays/FirstRunSetup/ScreenBeatmaps.cs @@ -14,8 +14,6 @@ using osu.Game.Beatmaps.Drawables; using osu.Game.Database; using osu.Game.Graphics; using osu.Game.Graphics.Containers; -using osu.Game.Graphics.UserInterface; -using osu.Game.Graphics.UserInterfaceV2; using osu.Game.Localisation; using osu.Game.Online; using osuTK; @@ -127,7 +125,10 @@ namespace osu.Game.Overlays.FirstRunSetup if (t.IsCompletedSuccessfully) importBeatmapsButton.Complete(); else + { importBeatmapsButton.Enabled.Value = true; + importBeatmapsButton.Abort(); + } })); } }, @@ -214,45 +215,5 @@ namespace osu.Game.Overlays.FirstRunSetup downloadBundledButton.SetProgress(progress, true); } } - - private class ProgressRoundedButton : RoundedButton - { - [Resolved] - private OsuColour colours { get; set; } = null!; - - private ProgressBar progressBar = null!; - - protected override void LoadComplete() - { - base.LoadComplete(); - - Add(progressBar = new ProgressBar(false) - { - RelativeSizeAxes = Axes.Both, - Blending = BlendingParameters.Additive, - FillColour = BackgroundColour, - Alpha = 0.5f, - Depth = float.MinValue - }); - } - - public void Complete() - { - Enabled.Value = false; - - Background.FadeColour(colours.Green, 500, Easing.OutQuint); - progressBar.FillColour = colours.Green; - - this.TransformBindableTo(progressBar.Current, 1, 500, Easing.OutQuint); - } - - public void SetProgress(double progress, bool animated) - { - if (!Enabled.Value) - return; - - this.TransformBindableTo(progressBar.Current, progress, animated ? 500 : 0, Easing.OutQuint); - } - } } } From 4412fec41a0fdc5ea7ff1c063dcbf0aff594210a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 16 May 2022 19:21:26 +0900 Subject: [PATCH 040/395] Add import from stable screen --- .../FirstRunSetupOverlayStrings.cs | 10 ++ .../Overlays/FirstRunSetup/ScreenBeatmaps.cs | 34 +----- .../FirstRunSetup/ScreenImportFromStable.cs | 105 ++++++++++++++++++ osu.Game/Overlays/FirstRunSetupOverlay.cs | 25 +++-- 4 files changed, 131 insertions(+), 43 deletions(-) create mode 100644 osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs diff --git a/osu.Game/Localisation/FirstRunSetupOverlayStrings.cs b/osu.Game/Localisation/FirstRunSetupOverlayStrings.cs index 91b427e2ca..6b5ca7534d 100644 --- a/osu.Game/Localisation/FirstRunSetupOverlayStrings.cs +++ b/osu.Game/Localisation/FirstRunSetupOverlayStrings.cs @@ -74,6 +74,16 @@ We recommend you give the new defaults a try, but if you'd like to have things f /// public static LocalisableString ClassicDefaults => new TranslatableString(getKey(@"classic_defaults"), @"Classic defaults"); + /// + /// "Welcome" + /// + public static LocalisableString ImportTitle => new TranslatableString(getKey(@"import_title"), @"Import"); + + /// + /// "Import content from stable" + /// + public static LocalisableString ImportContentFromStable => new TranslatableString(getKey(@"import_content_from_stable"), @"Import content from osu!(stable)"); + private static string getKey(string key) => $@"{prefix}:{key}"; } } diff --git a/osu.Game/Overlays/FirstRunSetup/ScreenBeatmaps.cs b/osu.Game/Overlays/FirstRunSetup/ScreenBeatmaps.cs index f7615d5ce9..17e04c0c99 100644 --- a/osu.Game/Overlays/FirstRunSetup/ScreenBeatmaps.cs +++ b/osu.Game/Overlays/FirstRunSetup/ScreenBeatmaps.cs @@ -25,7 +25,6 @@ namespace osu.Game.Overlays.FirstRunSetup public class ScreenBeatmaps : FirstRunSetupScreen { private ProgressRoundedButton downloadBundledButton = null!; - private ProgressRoundedButton importBeatmapsButton = null!; private ProgressRoundedButton downloadTutorialButton = null!; private OsuTextFlowContainer currentlyLoadedBeatmaps = null!; @@ -41,8 +40,8 @@ namespace osu.Game.Overlays.FirstRunSetup private IDisposable? beatmapSubscription; - [BackgroundDependencyLoader(permitNulls: true)] - private void load(LegacyImportManager? legacyImportManager) + [BackgroundDependencyLoader] + private void load() { Vector2 buttonSize = new Vector2(400, 50); @@ -104,35 +103,6 @@ namespace osu.Game.Overlays.FirstRunSetup Action = downloadBundled }, new OsuTextFlowContainer(cp => cp.Font = OsuFont.Default.With(size: CONTENT_FONT_SIZE)) - { - Colour = OverlayColourProvider.Content1, - Text = "If you have an existing osu! install, you can also choose to import your existing beatmap collection.", - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y - }, - importBeatmapsButton = new ProgressRoundedButton - { - Size = buttonSize, - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - BackgroundColour = colours.Blue3, - Text = MaintenanceSettingsStrings.ImportBeatmapsFromStable, - Action = () => - { - importBeatmapsButton.Enabled.Value = false; - legacyImportManager?.ImportFromStableAsync(StableContent.Beatmaps).ContinueWith(t => Schedule(() => - { - if (t.IsCompletedSuccessfully) - importBeatmapsButton.Complete(); - else - { - importBeatmapsButton.Enabled.Value = true; - importBeatmapsButton.Abort(); - } - })); - } - }, - new OsuTextFlowContainer(cp => cp.Font = OsuFont.Default.With(size: CONTENT_FONT_SIZE)) { Colour = OverlayColourProvider.Content1, Text = FirstRunSetupBeatmapScreenStrings.ObtainMoreBeatmaps, diff --git a/osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs b/osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs new file mode 100644 index 0000000000..c55bb07295 --- /dev/null +++ b/osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs @@ -0,0 +1,105 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +#nullable enable + +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Localisation; +using osu.Game.Database; +using osu.Game.Graphics; +using osu.Game.Graphics.Containers; +using osu.Game.Localisation; +using osu.Game.Overlays.Settings; +using osuTK; + +namespace osu.Game.Overlays.FirstRunSetup +{ + [LocalisableDescription(typeof(FirstRunSetupOverlayStrings), nameof(FirstRunSetupOverlayStrings.ImportTitle))] + public class ScreenImportFromStable : FirstRunSetupScreen + { + private ProgressRoundedButton importButton = null!; + + private SettingsCheckbox checkboxSkins = null!; + private SettingsCheckbox checkboxBeatmaps = null!; + private SettingsCheckbox checkboxScores = null!; + private SettingsCheckbox checkboxCollections = null!; + + [Resolved] + private OsuColour colours { get; set; } = null!; + + [Resolved(canBeNull: true)] + private LegacyImportManager? legacyImportManager { get; set; } + + [BackgroundDependencyLoader(permitNulls: true)] + private void load() + { + Vector2 buttonSize = new Vector2(400, 50); + + Content.Children = new Drawable[] + { + new OsuTextFlowContainer(cp => cp.Font = OsuFont.Default.With(size: CONTENT_FONT_SIZE)) + { + Colour = OverlayColourProvider.Content1, + Text = + "If you have an installation of a previous osu! version, you can choose to migrate your existing content. Note that this will create a copy, and not affect your existing installation.", + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y + }, + checkboxBeatmaps = new SettingsCheckbox + { + LabelText = "Beatmaps", + Current = { Value = true } + }, + checkboxScores = new SettingsCheckbox + { + LabelText = "Scores", + Current = { Value = true } + }, + checkboxSkins = new SettingsCheckbox + { + LabelText = "Skins", + Current = { Value = true } + }, + checkboxCollections = new SettingsCheckbox + { + LabelText = "Collections", + Current = { Value = true } + }, + importButton = new ProgressRoundedButton + { + Size = buttonSize, + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + BackgroundColour = colours.Blue3, + Text = FirstRunSetupOverlayStrings.ImportContentFromStable, + Action = runImport + }, + }; + } + + private void runImport() + { + importButton.Enabled.Value = false; + + StableContent importableContent = 0; + + if (checkboxBeatmaps.Current.Value) importableContent |= StableContent.Beatmaps; + if (checkboxScores.Current.Value) importableContent |= StableContent.Scores; + if (checkboxSkins.Current.Value) importableContent |= StableContent.Skins; + if (checkboxCollections.Current.Value) importableContent |= StableContent.Collections; + + legacyImportManager?.ImportFromStableAsync(importableContent) + .ContinueWith(t => Schedule(() => + { + if (t.IsCompletedSuccessfully) + importButton.Complete(); + else + { + importButton.Enabled.Value = true; + importButton.Abort(); + } + })); + } + } +} diff --git a/osu.Game/Overlays/FirstRunSetupOverlay.cs b/osu.Game/Overlays/FirstRunSetupOverlay.cs index cebb2f5e3b..dae2815e98 100644 --- a/osu.Game/Overlays/FirstRunSetupOverlay.cs +++ b/osu.Game/Overlays/FirstRunSetupOverlay.cs @@ -4,6 +4,7 @@ #nullable enable using System; +using System.Collections.Generic; using System.Diagnostics; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -17,6 +18,7 @@ using osu.Framework.Localisation; using osu.Framework.Screens; using osu.Framework.Threading; using osu.Game.Configuration; +using osu.Game.Database; using osu.Game.Graphics; using osu.Game.Graphics.UserInterface; using osu.Game.Input.Bindings; @@ -55,13 +57,7 @@ namespace osu.Game.Overlays /// public FirstRunSetupScreen? CurrentScreen => (FirstRunSetupScreen?)stack?.CurrentScreen; - private readonly Type[] steps = - { - typeof(ScreenWelcome), - typeof(ScreenBeatmaps), - typeof(ScreenUIScale), - typeof(ScreenBehaviour), - }; + private readonly List steps = new List(); private Container screenContent = null!; @@ -77,9 +73,16 @@ namespace osu.Game.Overlays { } - [BackgroundDependencyLoader] - private void load(OsuColour colours) + [BackgroundDependencyLoader(permitNulls: true)] + private void load(OsuColour colours, LegacyImportManager? legacyImportManager) { + steps.Add(typeof(ScreenWelcome)); + steps.Add(typeof(ScreenBeatmaps)); + if (legacyImportManager?.SupportsImportFromStable == true) + steps.Add(typeof(ScreenImportFromStable)); + steps.Add(typeof(ScreenUIScale)); + steps.Add(typeof(ScreenBehaviour)); + Header.Title = FirstRunSetupOverlayStrings.FirstRunSetupTitle; Header.Description = FirstRunSetupOverlayStrings.FirstRunSetupDescription; @@ -313,7 +316,7 @@ namespace osu.Game.Overlays currentStepIndex++; - if (currentStepIndex < steps.Length) + if (currentStepIndex < steps.Count) { var nextScreen = (Screen)Activator.CreateInstance(steps[currentStepIndex.Value]); @@ -345,7 +348,7 @@ namespace osu.Game.Overlays return; bool isFirstStep = currentStepIndex == 0; - bool isLastStep = currentStepIndex == steps.Length - 1; + bool isLastStep = currentStepIndex == steps.Count - 1; if (isFirstStep) { From 6448c97929d56ef86edb13a0808c58026fdfcbf8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 16 May 2022 19:57:00 +0900 Subject: [PATCH 041/395] Allow retrieving count of available stable imports --- osu.Game/Collections/CollectionManager.cs | 12 ++++++++++++ osu.Game/Database/LegacyImportManager.cs | 23 +++++++++++++++++++++++ osu.Game/Database/LegacyModelImporter.cs | 2 ++ 3 files changed, 37 insertions(+) diff --git a/osu.Game/Collections/CollectionManager.cs b/osu.Game/Collections/CollectionManager.cs index 5845e0d4d1..700b0f5dcb 100644 --- a/osu.Game/Collections/CollectionManager.cs +++ b/osu.Game/Collections/CollectionManager.cs @@ -111,6 +111,18 @@ namespace osu.Game.Collections public Action PostNotification { protected get; set; } + public Task GetAvailableCount(StableStorage stableStorage) + { + if (!stableStorage.Exists(database_name)) + return Task.FromResult(0); + + return Task.Run(() => + { + using (var stream = stableStorage.GetStream(database_name)) + return readCollections(stream).Count; + }); + } + /// /// This is a temporary method and will likely be replaced by a full-fledged (and more correctly placed) migration process in the future. /// diff --git a/osu.Game/Database/LegacyImportManager.cs b/osu.Game/Database/LegacyImportManager.cs index 59394c2952..a9f37fe4a7 100644 --- a/osu.Game/Database/LegacyImportManager.cs +++ b/osu.Game/Database/LegacyImportManager.cs @@ -49,6 +49,29 @@ namespace osu.Game.Database public bool SupportsImportFromStable => RuntimeInfo.IsDesktop; + public async Task GetImportCount(StableContent content) + { + var stableStorage = await getStableStorage().ConfigureAwait(false); + + switch (content) + { + case StableContent.Beatmaps: + return await new LegacyBeatmapImporter(beatmaps).GetAvailableCount(stableStorage); + + case StableContent.Skins: + return await new LegacySkinImporter(skins).GetAvailableCount(stableStorage); + + case StableContent.Collections: + return await collections.GetAvailableCount(stableStorage); + + case StableContent.Scores: + return await new LegacyScoreImporter(scores).GetAvailableCount(stableStorage); + + default: + throw new ArgumentException($"Only one {nameof(StableContent)} flag should be specified."); + } + } + public async Task ImportFromStableAsync(StableContent content) { var stableStorage = await getStableStorage().ConfigureAwait(false); diff --git a/osu.Game/Database/LegacyModelImporter.cs b/osu.Game/Database/LegacyModelImporter.cs index d85fb5aab2..a6e0f5bf8b 100644 --- a/osu.Game/Database/LegacyModelImporter.cs +++ b/osu.Game/Database/LegacyModelImporter.cs @@ -34,6 +34,8 @@ namespace osu.Game.Database Importer = importer; } + public Task GetAvailableCount(StableStorage stableStorage) => Task.Run(() => GetStableImportPaths(stableStorage).Count()); + public Task ImportFromStableAsync(StableStorage stableStorage) { var storage = PrepareStableStorage(stableStorage); From 1666d13d262455c2323fe097f1d4bfbde0006675 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 16 May 2022 20:13:34 +0900 Subject: [PATCH 042/395] Add item counts to import screen --- .../Overlays/FirstRunSetup/ScreenImportFromStable.cs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs b/osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs index c55bb07295..42a10854e8 100644 --- a/osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs +++ b/osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs @@ -4,6 +4,7 @@ #nullable enable using osu.Framework.Allocation; +using osu.Framework.Extensions; using osu.Framework.Graphics; using osu.Framework.Localisation; using osu.Game.Database; @@ -28,8 +29,8 @@ namespace osu.Game.Overlays.FirstRunSetup [Resolved] private OsuColour colours { get; set; } = null!; - [Resolved(canBeNull: true)] - private LegacyImportManager? legacyImportManager { get; set; } + [Resolved] + private LegacyImportManager legacyImportManager { get; set; } = null!; [BackgroundDependencyLoader(permitNulls: true)] private void load() @@ -76,6 +77,11 @@ namespace osu.Game.Overlays.FirstRunSetup Action = runImport }, }; + + legacyImportManager.GetImportCount(StableContent.Beatmaps).ContinueWith(task => Schedule(() => checkboxBeatmaps.LabelText += $" ({task.GetResultSafely()} items)")); + legacyImportManager.GetImportCount(StableContent.Scores).ContinueWith(task => Schedule(() => checkboxScores.LabelText += $" ({task.GetResultSafely()} items)")); + legacyImportManager.GetImportCount(StableContent.Skins).ContinueWith(task => Schedule(() => checkboxSkins.LabelText += $" ({task.GetResultSafely()} items)")); + legacyImportManager.GetImportCount(StableContent.Collections).ContinueWith(task => Schedule(() => checkboxCollections.LabelText += $" ({task.GetResultSafely()} items)")); } private void runImport() @@ -89,7 +95,7 @@ namespace osu.Game.Overlays.FirstRunSetup if (checkboxSkins.Current.Value) importableContent |= StableContent.Skins; if (checkboxCollections.Current.Value) importableContent |= StableContent.Collections; - legacyImportManager?.ImportFromStableAsync(importableContent) + legacyImportManager.ImportFromStableAsync(importableContent) .ContinueWith(t => Schedule(() => { if (t.IsCompletedSuccessfully) From 13e70eab515c2aeacec660fdf978908deb93e399 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 16 May 2022 20:37:38 +0900 Subject: [PATCH 043/395] Allow cancellation of count operations and bypassing interactive location logic --- osu.Game/Database/LegacyImportManager.cs | 35 +++++--- .../FirstRunSetup/ScreenImportFromStable.cs | 87 +++++++++++++++++-- 2 files changed, 107 insertions(+), 15 deletions(-) diff --git a/osu.Game/Database/LegacyImportManager.cs b/osu.Game/Database/LegacyImportManager.cs index a9f37fe4a7..9c436e8903 100644 --- a/osu.Game/Database/LegacyImportManager.cs +++ b/osu.Game/Database/LegacyImportManager.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Threading; using System.Threading.Tasks; using osu.Framework; using osu.Framework.Allocation; @@ -49,9 +50,14 @@ namespace osu.Game.Database public bool SupportsImportFromStable => RuntimeInfo.IsDesktop; - public async Task GetImportCount(StableContent content) + public async Task GetImportCount(StableContent content, CancellationToken cancellationToken) { - var stableStorage = await getStableStorage().ConfigureAwait(false); + var stableStorage = GetCurrentStableStorage(); + + if (stableStorage == null) + return 0; + + cancellationToken.ThrowIfCancellationRequested(); switch (content) { @@ -72,9 +78,22 @@ namespace osu.Game.Database } } - public async Task ImportFromStableAsync(StableContent content) + public async Task ImportFromStableAsync(StableContent content, bool interactiveLocateIfNotFound = true) { - var stableStorage = await getStableStorage().ConfigureAwait(false); + var stableStorage = GetCurrentStableStorage(); + + if (stableStorage == null) + { + if (!interactiveLocateIfNotFound) + return; + + var taskCompletionSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + Schedule(() => dialogOverlay.Push(new StableDirectoryLocationDialog(taskCompletionSource))); + string stablePath = await taskCompletionSource.Task.ConfigureAwait(false); + + stableStorage = cachedStorage = new StableStorage(stablePath, desktopGameHost); + } + var importTasks = new List(); Task beatmapImportTask = Task.CompletedTask; @@ -93,7 +112,7 @@ namespace osu.Game.Database await Task.WhenAll(importTasks.ToArray()).ConfigureAwait(false); } - private async Task getStableStorage() + public StableStorage GetCurrentStableStorage() { if (cachedStorage != null) return cachedStorage; @@ -102,11 +121,7 @@ namespace osu.Game.Database if (stableStorage != null) return cachedStorage = stableStorage; - var taskCompletionSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); - Schedule(() => dialogOverlay.Push(new StableDirectoryLocationDialog(taskCompletionSource))); - string stablePath = await taskCompletionSource.Task.ConfigureAwait(false); - - return cachedStorage = new StableStorage(stablePath, desktopGameHost); + return null; } } diff --git a/osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs b/osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs index 42a10854e8..e31f70b686 100644 --- a/osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs +++ b/osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs @@ -3,6 +3,8 @@ #nullable enable +using System.Linq; +using System.Threading; using osu.Framework.Allocation; using osu.Framework.Extensions; using osu.Framework.Graphics; @@ -10,6 +12,7 @@ using osu.Framework.Localisation; using osu.Game.Database; using osu.Game.Graphics; using osu.Game.Graphics.Containers; +using osu.Game.Graphics.UserInterfaceV2; using osu.Game.Localisation; using osu.Game.Overlays.Settings; using osuTK; @@ -26,12 +29,16 @@ namespace osu.Game.Overlays.FirstRunSetup private SettingsCheckbox checkboxScores = null!; private SettingsCheckbox checkboxCollections = null!; + private OsuTextFlowContainer currentStablePath = null!; + [Resolved] private OsuColour colours { get; set; } = null!; [Resolved] private LegacyImportManager legacyImportManager { get; set; } = null!; + private CancellationTokenSource? stablePathUpdateCancellation; + [BackgroundDependencyLoader(permitNulls: true)] private void load() { @@ -47,6 +54,21 @@ namespace osu.Game.Overlays.FirstRunSetup RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y }, + currentStablePath = new OsuTextFlowContainer(cp => cp.Font = OsuFont.Default.With(size: HEADER_FONT_SIZE, weight: FontWeight.SemiBold)) + { + Colour = OverlayColourProvider.Content2, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + }, + new RoundedButton + { + Size = buttonSize, + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + BackgroundColour = colours.Blue3, + Text = "Locate osu!(stable) install", + Action = locateStable, + }, checkboxBeatmaps = new SettingsCheckbox { LabelText = "Beatmaps", @@ -78,10 +100,65 @@ namespace osu.Game.Overlays.FirstRunSetup }, }; - legacyImportManager.GetImportCount(StableContent.Beatmaps).ContinueWith(task => Schedule(() => checkboxBeatmaps.LabelText += $" ({task.GetResultSafely()} items)")); - legacyImportManager.GetImportCount(StableContent.Scores).ContinueWith(task => Schedule(() => checkboxScores.LabelText += $" ({task.GetResultSafely()} items)")); - legacyImportManager.GetImportCount(StableContent.Skins).ContinueWith(task => Schedule(() => checkboxSkins.LabelText += $" ({task.GetResultSafely()} items)")); - legacyImportManager.GetImportCount(StableContent.Collections).ContinueWith(task => Schedule(() => checkboxCollections.LabelText += $" ({task.GetResultSafely()} items)")); + updateStablePath(); + } + + private void locateStable() + { + legacyImportManager.ImportFromStableAsync(0).ContinueWith(task => + { + Schedule(updateStablePath); + }); + } + + private void updateStablePath() + { + stablePathUpdateCancellation?.Cancel(); + + var storage = legacyImportManager.GetCurrentStableStorage(); + + if (storage == null) + { + foreach (var c in Content.Children.OfType()) + c.Current.Disabled = true; + currentStablePath.Text = "No installation found"; + return; + } + + foreach (var c in Content.Children.OfType()) + c.Current.Disabled = false; + + currentStablePath.Text = storage.GetFullPath(string.Empty); + stablePathUpdateCancellation = new CancellationTokenSource(); + + legacyImportManager.GetImportCount(StableContent.Beatmaps, stablePathUpdateCancellation.Token).ContinueWith(task => Schedule(() => + { + if (task.IsCanceled) + return; + + checkboxBeatmaps.LabelText = $"Beatmaps ({task.GetResultSafely()} items)"; + })); + legacyImportManager.GetImportCount(StableContent.Scores, stablePathUpdateCancellation.Token).ContinueWith(task => Schedule(() => + { + if (task.IsCanceled) + return; + + checkboxScores.LabelText = $"Scores ({task.GetResultSafely()} items)"; + })); + legacyImportManager.GetImportCount(StableContent.Skins, stablePathUpdateCancellation.Token).ContinueWith(task => Schedule(() => + { + if (task.IsCanceled) + return; + + checkboxSkins.LabelText = $"Skins ({task.GetResultSafely()} items)"; + })); + legacyImportManager.GetImportCount(StableContent.Collections, stablePathUpdateCancellation.Token).ContinueWith(task => Schedule(() => + { + if (task.IsCanceled) + return; + + checkboxCollections.LabelText = $"Collections ({task.GetResultSafely()} items)"; + })); } private void runImport() @@ -95,7 +172,7 @@ namespace osu.Game.Overlays.FirstRunSetup if (checkboxSkins.Current.Value) importableContent |= StableContent.Skins; if (checkboxCollections.Current.Value) importableContent |= StableContent.Collections; - legacyImportManager.ImportFromStableAsync(importableContent) + legacyImportManager.ImportFromStableAsync(importableContent, false) .ContinueWith(t => Schedule(() => { if (t.IsCompletedSuccessfully) From 1593f05ff37c34bad7cc25b2f3d0d9ad9c84738d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 16 May 2022 20:53:04 +0900 Subject: [PATCH 044/395] Tidy up checkbox implementation --- .../FirstRunSetup/ScreenImportFromStable.cs | 134 ++++++++---------- 1 file changed, 63 insertions(+), 71 deletions(-) diff --git a/osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs b/osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs index e31f70b686..dd3580508a 100644 --- a/osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs +++ b/osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs @@ -3,6 +3,7 @@ #nullable enable +using System.Collections.Generic; using System.Linq; using System.Threading; using osu.Framework.Allocation; @@ -24,11 +25,6 @@ namespace osu.Game.Overlays.FirstRunSetup { private ProgressRoundedButton importButton = null!; - private SettingsCheckbox checkboxSkins = null!; - private SettingsCheckbox checkboxBeatmaps = null!; - private SettingsCheckbox checkboxScores = null!; - private SettingsCheckbox checkboxCollections = null!; - private OsuTextFlowContainer currentStablePath = null!; [Resolved] @@ -39,6 +35,8 @@ namespace osu.Game.Overlays.FirstRunSetup private CancellationTokenSource? stablePathUpdateCancellation; + private IEnumerable contentCheckboxes => Content.Children.OfType(); + [BackgroundDependencyLoader(permitNulls: true)] private void load() { @@ -69,26 +67,10 @@ namespace osu.Game.Overlays.FirstRunSetup Text = "Locate osu!(stable) install", Action = locateStable, }, - checkboxBeatmaps = new SettingsCheckbox - { - LabelText = "Beatmaps", - Current = { Value = true } - }, - checkboxScores = new SettingsCheckbox - { - LabelText = "Scores", - Current = { Value = true } - }, - checkboxSkins = new SettingsCheckbox - { - LabelText = "Skins", - Current = { Value = true } - }, - checkboxCollections = new SettingsCheckbox - { - LabelText = "Collections", - Current = { Value = true } - }, + new ImportCheckbox("Beatmaps", StableContent.Beatmaps), + new ImportCheckbox("Scores", StableContent.Scores), + new ImportCheckbox("Skins", StableContent.Skins), + new ImportCheckbox("Collections", StableContent.Collections), importButton = new ProgressRoundedButton { Size = buttonSize, @@ -119,46 +101,20 @@ namespace osu.Game.Overlays.FirstRunSetup if (storage == null) { - foreach (var c in Content.Children.OfType()) + foreach (var c in contentCheckboxes) c.Current.Disabled = true; currentStablePath.Text = "No installation found"; return; } - foreach (var c in Content.Children.OfType()) + foreach (var c in contentCheckboxes) + { c.Current.Disabled = false; + c.UpdateCount(); + } currentStablePath.Text = storage.GetFullPath(string.Empty); stablePathUpdateCancellation = new CancellationTokenSource(); - - legacyImportManager.GetImportCount(StableContent.Beatmaps, stablePathUpdateCancellation.Token).ContinueWith(task => Schedule(() => - { - if (task.IsCanceled) - return; - - checkboxBeatmaps.LabelText = $"Beatmaps ({task.GetResultSafely()} items)"; - })); - legacyImportManager.GetImportCount(StableContent.Scores, stablePathUpdateCancellation.Token).ContinueWith(task => Schedule(() => - { - if (task.IsCanceled) - return; - - checkboxScores.LabelText = $"Scores ({task.GetResultSafely()} items)"; - })); - legacyImportManager.GetImportCount(StableContent.Skins, stablePathUpdateCancellation.Token).ContinueWith(task => Schedule(() => - { - if (task.IsCanceled) - return; - - checkboxSkins.LabelText = $"Skins ({task.GetResultSafely()} items)"; - })); - legacyImportManager.GetImportCount(StableContent.Collections, stablePathUpdateCancellation.Token).ContinueWith(task => Schedule(() => - { - if (task.IsCanceled) - return; - - checkboxCollections.LabelText = $"Collections ({task.GetResultSafely()} items)"; - })); } private void runImport() @@ -167,22 +123,58 @@ namespace osu.Game.Overlays.FirstRunSetup StableContent importableContent = 0; - if (checkboxBeatmaps.Current.Value) importableContent |= StableContent.Beatmaps; - if (checkboxScores.Current.Value) importableContent |= StableContent.Scores; - if (checkboxSkins.Current.Value) importableContent |= StableContent.Skins; - if (checkboxCollections.Current.Value) importableContent |= StableContent.Collections; + foreach (var c in contentCheckboxes.Where(c => c.Current.Value)) + importableContent |= c.StableContent; - legacyImportManager.ImportFromStableAsync(importableContent, false) - .ContinueWith(t => Schedule(() => - { - if (t.IsCompletedSuccessfully) - importButton.Complete(); - else - { - importButton.Enabled.Value = true; - importButton.Abort(); - } - })); + legacyImportManager.ImportFromStableAsync(importableContent, false).ContinueWith(t => Schedule(() => + { + if (t.IsCompletedSuccessfully) + importButton.Complete(); + else + { + importButton.Enabled.Value = true; + importButton.Abort(); + } + })); + } + + private class ImportCheckbox : SettingsCheckbox + { + public readonly StableContent StableContent; + + private readonly LocalisableString title; + + [Resolved] + private LegacyImportManager legacyImportManager { get; set; } = null!; + + private CancellationTokenSource? countUpdateCancellation; + + public ImportCheckbox(LocalisableString title, StableContent stableContent) + { + this.title = title; + + StableContent = stableContent; + + Current.Value = true; + + LabelText = title; + } + + public void UpdateCount() + { + LabelText = LocalisableString.Interpolate($"{title} (calculating...)"); + + countUpdateCancellation?.Cancel(); + countUpdateCancellation = new CancellationTokenSource(); + + legacyImportManager.GetImportCount(StableContent, countUpdateCancellation.Token).ContinueWith(task => Schedule(() => + { + if (task.IsCanceled) + return; + + LabelText = LocalisableString.Interpolate($"{title} ({task.GetResultSafely()} items)"); + })); + } } } } From 4af1a788d14fadffcbd98953a769aacdd5b87d6a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 16 May 2022 21:07:42 +0900 Subject: [PATCH 045/395] Add locate stable button / screen --- osu.Game/Database/LegacyImportManager.cs | 5 +- .../FirstRunSetup/FirstRunSetupScreen.cs | 4 +- .../FirstRunSetup/ScreenImportFromStable.cs | 134 +++++++++++++++++- 3 files changed, 136 insertions(+), 7 deletions(-) diff --git a/osu.Game/Database/LegacyImportManager.cs b/osu.Game/Database/LegacyImportManager.cs index 9c436e8903..35b1d05fa1 100644 --- a/osu.Game/Database/LegacyImportManager.cs +++ b/osu.Game/Database/LegacyImportManager.cs @@ -50,6 +50,8 @@ namespace osu.Game.Database public bool SupportsImportFromStable => RuntimeInfo.IsDesktop; + public void UpdateStorage(string stablePath) => cachedStorage = new StableStorage(stablePath, desktopGameHost); + public async Task GetImportCount(StableContent content, CancellationToken cancellationToken) { var stableStorage = GetCurrentStableStorage(); @@ -91,7 +93,8 @@ namespace osu.Game.Database Schedule(() => dialogOverlay.Push(new StableDirectoryLocationDialog(taskCompletionSource))); string stablePath = await taskCompletionSource.Task.ConfigureAwait(false); - stableStorage = cachedStorage = new StableStorage(stablePath, desktopGameHost); + UpdateStorage(stablePath); + stableStorage = GetCurrentStableStorage(); } var importTasks = new List(); diff --git a/osu.Game/Overlays/FirstRunSetup/FirstRunSetupScreen.cs b/osu.Game/Overlays/FirstRunSetup/FirstRunSetupScreen.cs index d1ea91e51a..b043f05bd8 100644 --- a/osu.Game/Overlays/FirstRunSetup/FirstRunSetupScreen.cs +++ b/osu.Game/Overlays/FirstRunSetup/FirstRunSetupScreen.cs @@ -21,6 +21,8 @@ namespace osu.Game.Overlays.FirstRunSetup protected const float CONTENT_FONT_SIZE = 16; + protected const float CONTENT_PADDING = 30; + protected const float HEADER_FONT_SIZE = 24; [Resolved] @@ -41,7 +43,7 @@ namespace osu.Game.Overlays.FirstRunSetup { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, - Padding = new MarginPadding { Horizontal = 30 }, + Padding = new MarginPadding { Horizontal = CONTENT_PADDING }, Children = new Drawable[] { new OsuSpriteText diff --git a/osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs b/osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs index dd3580508a..acc0fdbb7c 100644 --- a/osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs +++ b/osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs @@ -4,19 +4,24 @@ #nullable enable using System.Collections.Generic; +using System.IO; using System.Linq; using System.Threading; using osu.Framework.Allocation; using osu.Framework.Extensions; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Framework.Localisation; +using osu.Framework.Screens; using osu.Game.Database; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterfaceV2; +using osu.Game.IO; using osu.Game.Localisation; using osu.Game.Overlays.Settings; using osuTK; +using CommonStrings = osu.Game.Resources.Localisation.Web.CommonStrings; namespace osu.Game.Overlays.FirstRunSetup { @@ -24,6 +29,7 @@ namespace osu.Game.Overlays.FirstRunSetup public class ScreenImportFromStable : FirstRunSetupScreen { private ProgressRoundedButton importButton = null!; + private RoundedButton locateStableButton = null!; private OsuTextFlowContainer currentStablePath = null!; @@ -58,7 +64,7 @@ namespace osu.Game.Overlays.FirstRunSetup RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, }, - new RoundedButton + locateStableButton = new RoundedButton { Size = buttonSize, Anchor = Anchor.TopCentre, @@ -85,12 +91,15 @@ namespace osu.Game.Overlays.FirstRunSetup updateStablePath(); } - private void locateStable() + private void locateStable() => this.Push(new LocateStableScreen()); + + public override void OnResuming(ScreenTransitionEvent e) { - legacyImportManager.ImportFromStableAsync(0).ContinueWith(task => - { + base.OnResuming(e); + + if (e.Last is LocateStableScreen) + // stable storage may have changed. Schedule(updateStablePath); - }); } private void updateStablePath() @@ -120,6 +129,7 @@ namespace osu.Game.Overlays.FirstRunSetup private void runImport() { importButton.Enabled.Value = false; + locateStableButton.Enabled.Value = false; StableContent importableContent = 0; @@ -128,6 +138,8 @@ namespace osu.Game.Overlays.FirstRunSetup legacyImportManager.ImportFromStableAsync(importableContent, false).ContinueWith(t => Schedule(() => { + locateStableButton.Enabled.Value = true; + if (t.IsCompletedSuccessfully) importButton.Complete(); else @@ -176,5 +188,117 @@ namespace osu.Game.Overlays.FirstRunSetup })); } } + + private class LocateStableScreen : FirstRunSetupScreen + { + private RoundedButton selectionButton = null!; + + private OsuDirectorySelector directorySelector = null!; + + protected bool IsValidDirectory(DirectoryInfo? info) => info?.GetFiles("osu!.*.cfg").Any() ?? false; + + public LocalisableString HeaderText => "Please select your osu!stable install location"; + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + // Don't want the scroll content provided by `FirstRunSetupScreen` so we don't use `Content`. + InternalChild = new Container + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding { Horizontal = CONTENT_PADDING }, + Children = new Drawable[] + { + new GridContainer + { + RelativeSizeAxes = Axes.Both, + RowDimensions = new[] + { + new Dimension(GridSizeMode.AutoSize), + new Dimension(), + new Dimension(GridSizeMode.AutoSize), + }, + Content = new[] + { + new Drawable[] + { + new OsuTextFlowContainer(cp => + { + cp.Font = OsuFont.Default.With(size: 24); + }) + { + Text = HeaderText, + TextAnchor = Anchor.TopCentre, + Margin = new MarginPadding(10), + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + } + }, + new Drawable[] + { + directorySelector = new OsuDirectorySelector + { + RelativeSizeAxes = Axes.Both, + } + }, + new Drawable[] + { + new Container + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Children = new Drawable[] + { + new RoundedButton + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Width = 300, + Margin = new MarginPadding(10), + Colour = colours.Pink2, + Text = CommonStrings.ButtonsCancel, + Action = this.Exit + }, + selectionButton = new RoundedButton + { + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + Width = 300, + Margin = new MarginPadding(10), + Text = MaintenanceSettingsStrings.SelectDirectory, + Action = () => + { + legacyImportManager.UpdateStorage(directorySelector.CurrentPath.Value.FullName); + this.Exit(); + } + }, + } + }, + } + } + } + } + }; + } + + [Resolved] + private LegacyImportManager legacyImportManager { get; set; } = null!; + + protected override void LoadComplete() + { + if (legacyImportManager.GetCurrentStableStorage() is StableStorage storage) + directorySelector.CurrentPath.Value = new DirectoryInfo(storage.GetFullPath(string.Empty)); + + directorySelector.CurrentPath.BindValueChanged(e => selectionButton.Enabled.Value = e.NewValue != null && IsValidDirectory(e.NewValue), true); + base.LoadComplete(); + } + + public override void OnSuspending(ScreenTransitionEvent e) + { + base.OnSuspending(e); + + this.FadeOut(250); + } + } } } From 98e5ad44a71a090476a55ba40d7a0dfbb67cc327 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 16 May 2022 21:24:36 +0900 Subject: [PATCH 046/395] Add `OverlayColourProvider` support to `OsuDirectorySelector` --- .../UserInterfaceV2/OsuDirectorySelectorDirectory.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/osu.Game/Graphics/UserInterfaceV2/OsuDirectorySelectorDirectory.cs b/osu.Game/Graphics/UserInterfaceV2/OsuDirectorySelectorDirectory.cs index ce2e7794a9..456bde6d1b 100644 --- a/osu.Game/Graphics/UserInterfaceV2/OsuDirectorySelectorDirectory.cs +++ b/osu.Game/Graphics/UserInterfaceV2/OsuDirectorySelectorDirectory.cs @@ -10,6 +10,7 @@ using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.UserInterface; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; +using osu.Game.Overlays; namespace osu.Game.Graphics.UserInterfaceV2 { @@ -44,8 +45,8 @@ namespace osu.Game.Graphics.UserInterfaceV2 internal class Background : CompositeDrawable { - [BackgroundDependencyLoader] - private void load(OsuColour colours) + [BackgroundDependencyLoader(true)] + private void load(OverlayColourProvider overlayColourProvider, OsuColour colours) { RelativeSizeAxes = Axes.Both; @@ -54,7 +55,7 @@ namespace osu.Game.Graphics.UserInterfaceV2 InternalChild = new Box { - Colour = colours.GreySeaFoamDarker, + Colour = overlayColourProvider?.Background5 ?? colours.GreySeaFoamDarker, RelativeSizeAxes = Axes.Both, }; } From e2ea98693f8761ba0fc0fd76f786963d434eba5e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 16 May 2022 21:33:15 +0900 Subject: [PATCH 047/395] Tidy up buttons and text --- .../FirstRunSetup/ScreenImportFromStable.cs | 33 +++++++++++-------- 1 file changed, 20 insertions(+), 13 deletions(-) diff --git a/osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs b/osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs index acc0fdbb7c..f867adff79 100644 --- a/osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs +++ b/osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs @@ -28,6 +28,8 @@ namespace osu.Game.Overlays.FirstRunSetup [LocalisableDescription(typeof(FirstRunSetupOverlayStrings), nameof(FirstRunSetupOverlayStrings.ImportTitle))] public class ScreenImportFromStable : FirstRunSetupScreen { + private static readonly Vector2 button_size = new Vector2(400, 50); + private ProgressRoundedButton importButton = null!; private RoundedButton locateStableButton = null!; @@ -46,8 +48,6 @@ namespace osu.Game.Overlays.FirstRunSetup [BackgroundDependencyLoader(permitNulls: true)] private void load() { - Vector2 buttonSize = new Vector2(400, 50); - Content.Children = new Drawable[] { new OsuTextFlowContainer(cp => cp.Font = OsuFont.Default.With(size: CONTENT_FONT_SIZE)) @@ -63,14 +63,14 @@ namespace osu.Game.Overlays.FirstRunSetup Colour = OverlayColourProvider.Content2, RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, + TextAnchor = Anchor.Centre, }, locateStableButton = new RoundedButton { - Size = buttonSize, + Size = button_size, Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, - BackgroundColour = colours.Blue3, - Text = "Locate osu!(stable) install", + Text = "Change location", Action = locateStable, }, new ImportCheckbox("Beatmaps", StableContent.Beatmaps), @@ -79,10 +79,9 @@ namespace osu.Game.Overlays.FirstRunSetup new ImportCheckbox("Collections", StableContent.Collections), importButton = new ProgressRoundedButton { - Size = buttonSize, + Size = button_size, Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, - BackgroundColour = colours.Blue3, Text = FirstRunSetupOverlayStrings.ImportContentFromStable, Action = runImport }, @@ -112,7 +111,10 @@ namespace osu.Game.Overlays.FirstRunSetup { foreach (var c in contentCheckboxes) c.Current.Disabled = true; + currentStablePath.FadeColour(colours.Red1, 500, Easing.OutQuint); currentStablePath.Text = "No installation found"; + + importButton.Enabled.Value = false; return; } @@ -122,8 +124,10 @@ namespace osu.Game.Overlays.FirstRunSetup c.UpdateCount(); } - currentStablePath.Text = storage.GetFullPath(string.Empty); + currentStablePath.FadeColour(OverlayColourProvider.Content2); + currentStablePath.Text = $"Found installation: {storage.GetFullPath(string.Empty)}"; stablePathUpdateCancellation = new CancellationTokenSource(); + importButton.Enabled.Value = true; } private void runImport() @@ -138,13 +142,12 @@ namespace osu.Game.Overlays.FirstRunSetup legacyImportManager.ImportFromStableAsync(importableContent, false).ContinueWith(t => Schedule(() => { - locateStableButton.Enabled.Value = true; - if (t.IsCompletedSuccessfully) importButton.Complete(); else { importButton.Enabled.Value = true; + locateStableButton.Enabled.Value = true; importButton.Abort(); } })); @@ -253,9 +256,11 @@ namespace osu.Game.Overlays.FirstRunSetup { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, - Width = 300, + RelativeSizeAxes = Axes.X, + Width = 0.45f, + Height = button_size.Y, Margin = new MarginPadding(10), - Colour = colours.Pink2, + BackgroundColour = colours.Pink2, Text = CommonStrings.ButtonsCancel, Action = this.Exit }, @@ -263,7 +268,9 @@ namespace osu.Game.Overlays.FirstRunSetup { Anchor = Anchor.CentreRight, Origin = Anchor.CentreRight, - Width = 300, + RelativeSizeAxes = Axes.X, + Width = 0.45f, + Height = button_size.Y, Margin = new MarginPadding(10), Text = MaintenanceSettingsStrings.SelectDirectory, Action = () => From 8112416335429ee4d18475887d88c40558fd6c1c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 17 May 2022 15:25:45 +0900 Subject: [PATCH 048/395] Assert that downloads are queued early enough to work --- osu.Game/Beatmaps/Drawables/BundledBeatmapDownloader.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game/Beatmaps/Drawables/BundledBeatmapDownloader.cs b/osu.Game/Beatmaps/Drawables/BundledBeatmapDownloader.cs index df0a69cb25..84903d381a 100644 --- a/osu.Game/Beatmaps/Drawables/BundledBeatmapDownloader.cs +++ b/osu.Game/Beatmaps/Drawables/BundledBeatmapDownloader.cs @@ -3,9 +3,11 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using System.Text.RegularExpressions; using osu.Framework.Allocation; +using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Database; using osu.Game.Online; @@ -89,6 +91,8 @@ namespace osu.Game.Beatmaps.Drawables private void queueDownloads(string[] sourceFilenames, int? limit = null) { + Debug.Assert(LoadState == LoadState.NotLoaded); + try { // Matches osu-stable, in order to provide new users with roughly the same randomised selection of bundled beatmaps. From 3ff0399281f2f2ca0b5356aa08c48133623962db Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 17 May 2022 16:32:31 +0900 Subject: [PATCH 049/395] Split out `LabelledTextBoxWithPopover` for reuse --- .../Edit/Setup/FileChooserLabelledTextBox.cs | 37 +------------- .../Edit/Setup/LabelledTextBoxWithPopover.cs | 49 +++++++++++++++++++ 2 files changed, 51 insertions(+), 35 deletions(-) create mode 100644 osu.Game/Screens/Edit/Setup/LabelledTextBoxWithPopover.cs diff --git a/osu.Game/Screens/Edit/Setup/FileChooserLabelledTextBox.cs b/osu.Game/Screens/Edit/Setup/FileChooserLabelledTextBox.cs index d1e35ae20d..aae19396db 100644 --- a/osu.Game/Screens/Edit/Setup/FileChooserLabelledTextBox.cs +++ b/osu.Game/Screens/Edit/Setup/FileChooserLabelledTextBox.cs @@ -11,11 +11,8 @@ using osu.Framework.Bindables; using osu.Framework.Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.UserInterface; -using osu.Framework.Input.Events; using osu.Game.Database; -using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterfaceV2; using osuTK; @@ -24,7 +21,7 @@ namespace osu.Game.Screens.Edit.Setup /// /// A labelled textbox which reveals an inline file chooser when clicked. /// - internal class FileChooserLabelledTextBox : LabelledTextBox, ICanAcceptFiles, IHasPopover + internal class FileChooserLabelledTextBox : LabelledTextBoxWithPopover, ICanAcceptFiles { private readonly string[] handledExtensions; @@ -40,16 +37,6 @@ namespace osu.Game.Screens.Edit.Setup this.handledExtensions = handledExtensions; } - protected override OsuTextBox CreateTextBox() => - new FileChooserOsuTextBox - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.X, - CornerRadius = CORNER_RADIUS, - OnFocused = this.ShowPopover - }; - protected override void LoadComplete() { base.LoadComplete(); @@ -81,27 +68,7 @@ namespace osu.Game.Screens.Edit.Setup game.UnregisterImportHandler(this); } - internal class FileChooserOsuTextBox : OsuTextBox - { - public Action OnFocused; - - protected override bool OnDragStart(DragStartEvent e) - { - // This text box is intended to be "read only" without actually specifying that. - // As such we don't want to allow the user to select its content with a drag. - return false; - } - - protected override void OnFocus(FocusEvent e) - { - OnFocused?.Invoke(); - base.OnFocus(e); - - GetContainingInputManager().TriggerFocusContention(this); - } - } - - public Popover GetPopover() => new FileChooserPopover(handledExtensions, currentFile); + public override Popover GetPopover() => new FileChooserPopover(handledExtensions, currentFile); private class FileChooserPopover : OsuPopover { diff --git a/osu.Game/Screens/Edit/Setup/LabelledTextBoxWithPopover.cs b/osu.Game/Screens/Edit/Setup/LabelledTextBoxWithPopover.cs new file mode 100644 index 0000000000..1136675347 --- /dev/null +++ b/osu.Game/Screens/Edit/Setup/LabelledTextBoxWithPopover.cs @@ -0,0 +1,49 @@ +// 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.Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Cursor; +using osu.Framework.Graphics.UserInterface; +using osu.Framework.Input.Events; +using osu.Game.Graphics.UserInterface; +using osu.Game.Graphics.UserInterfaceV2; + +namespace osu.Game.Screens.Edit.Setup +{ + internal abstract class LabelledTextBoxWithPopover : LabelledTextBox, IHasPopover + { + public abstract Popover GetPopover(); + + protected override OsuTextBox CreateTextBox() => + new PopoverTextBox + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.X, + CornerRadius = CORNER_RADIUS, + OnFocused = this.ShowPopover + }; + + internal class PopoverTextBox : OsuTextBox + { + public Action OnFocused; + + protected override bool OnDragStart(DragStartEvent e) + { + // This text box is intended to be "read only" without actually specifying that. + // As such we don't want to allow the user to select its content with a drag. + return false; + } + + protected override void OnFocus(FocusEvent e) + { + OnFocused?.Invoke(); + base.OnFocus(e); + + GetContainingInputManager().TriggerFocusContention(this); + } + } + } +} From adc7b61240c55d9b2bec397f1a05a1427cf56558 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 17 May 2022 16:33:02 +0900 Subject: [PATCH 050/395] Switch directory selector to use a semi-shared popover implementation --- .../FirstRunSetup/ScreenImportFromStable.cs | 226 +++++++----------- osu.Game/Overlays/FirstRunSetupOverlay.cs | 3 +- 2 files changed, 86 insertions(+), 143 deletions(-) diff --git a/osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs b/osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs index f867adff79..8b7550e301 100644 --- a/osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs +++ b/osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs @@ -3,25 +3,27 @@ #nullable enable +using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading; +using System.Threading.Tasks; using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.UserInterface; using osu.Framework.Localisation; -using osu.Framework.Screens; using osu.Game.Database; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterfaceV2; -using osu.Game.IO; using osu.Game.Localisation; using osu.Game.Overlays.Settings; +using osu.Game.Screens.Edit.Setup; using osuTK; -using CommonStrings = osu.Game.Resources.Localisation.Web.CommonStrings; namespace osu.Game.Overlays.FirstRunSetup { @@ -31,18 +33,14 @@ namespace osu.Game.Overlays.FirstRunSetup private static readonly Vector2 button_size = new Vector2(400, 50); private ProgressRoundedButton importButton = null!; - private RoundedButton locateStableButton = null!; - - private OsuTextFlowContainer currentStablePath = null!; - - [Resolved] - private OsuColour colours { get; set; } = null!; [Resolved] private LegacyImportManager legacyImportManager { get; set; } = null!; private CancellationTokenSource? stablePathUpdateCancellation; + private StableLocatorLabelledTextBox stableLocatorTextBox = null!; + private IEnumerable contentCheckboxes => Content.Children.OfType(); [BackgroundDependencyLoader(permitNulls: true)] @@ -58,20 +56,10 @@ namespace osu.Game.Overlays.FirstRunSetup RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y }, - currentStablePath = new OsuTextFlowContainer(cp => cp.Font = OsuFont.Default.With(size: HEADER_FONT_SIZE, weight: FontWeight.SemiBold)) + stableLocatorTextBox = new StableLocatorLabelledTextBox { - Colour = OverlayColourProvider.Content2, - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - TextAnchor = Anchor.Centre, - }, - locateStableButton = new RoundedButton - { - Size = button_size, - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - Text = "Change location", - Action = locateStable, + Label = "previous osu! install", + PlaceholderText = "Click to locate a previous osu! install" }, new ImportCheckbox("Beatmaps", StableContent.Beatmaps), new ImportCheckbox("Scores", StableContent.Scores), @@ -87,18 +75,7 @@ namespace osu.Game.Overlays.FirstRunSetup }, }; - updateStablePath(); - } - - private void locateStable() => this.Push(new LocateStableScreen()); - - public override void OnResuming(ScreenTransitionEvent e) - { - base.OnResuming(e); - - if (e.Last is LocateStableScreen) - // stable storage may have changed. - Schedule(updateStablePath); + stableLocatorTextBox.Current.BindValueChanged(_ => updateStablePath(), true); } private void updateStablePath() @@ -111,9 +88,8 @@ namespace osu.Game.Overlays.FirstRunSetup { foreach (var c in contentCheckboxes) c.Current.Disabled = true; - currentStablePath.FadeColour(colours.Red1, 500, Easing.OutQuint); - currentStablePath.Text = "No installation found"; + stableLocatorTextBox.Current.Value = string.Empty; importButton.Enabled.Value = false; return; } @@ -124,8 +100,8 @@ namespace osu.Game.Overlays.FirstRunSetup c.UpdateCount(); } - currentStablePath.FadeColour(OverlayColourProvider.Content2); - currentStablePath.Text = $"Found installation: {storage.GetFullPath(string.Empty)}"; + stableLocatorTextBox.Current.Value = storage.GetFullPath(string.Empty); + stablePathUpdateCancellation = new CancellationTokenSource(); importButton.Enabled.Value = true; } @@ -133,7 +109,7 @@ namespace osu.Game.Overlays.FirstRunSetup private void runImport() { importButton.Enabled.Value = false; - locateStableButton.Enabled.Value = false; + stableLocatorTextBox.Current.Disabled = true; StableContent importableContent = 0; @@ -147,7 +123,7 @@ namespace osu.Game.Overlays.FirstRunSetup else { importButton.Enabled.Value = true; - locateStableButton.Enabled.Value = true; + stableLocatorTextBox.Current.Disabled = false; importButton.Abort(); } })); @@ -192,119 +168,85 @@ namespace osu.Game.Overlays.FirstRunSetup } } - private class LocateStableScreen : FirstRunSetupScreen + internal class StableLocatorLabelledTextBox : LabelledTextBoxWithPopover, ICanAcceptFiles { - private RoundedButton selectionButton = null!; - - private OsuDirectorySelector directorySelector = null!; - - protected bool IsValidDirectory(DirectoryInfo? info) => info?.GetFiles("osu!.*.cfg").Any() ?? false; - - public LocalisableString HeaderText => "Please select your osu!stable install location"; - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - // Don't want the scroll content provided by `FirstRunSetupScreen` so we don't use `Content`. - InternalChild = new Container - { - RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding { Horizontal = CONTENT_PADDING }, - Children = new Drawable[] - { - new GridContainer - { - RelativeSizeAxes = Axes.Both, - RowDimensions = new[] - { - new Dimension(GridSizeMode.AutoSize), - new Dimension(), - new Dimension(GridSizeMode.AutoSize), - }, - Content = new[] - { - new Drawable[] - { - new OsuTextFlowContainer(cp => - { - cp.Font = OsuFont.Default.With(size: 24); - }) - { - Text = HeaderText, - TextAnchor = Anchor.TopCentre, - Margin = new MarginPadding(10), - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - } - }, - new Drawable[] - { - directorySelector = new OsuDirectorySelector - { - RelativeSizeAxes = Axes.Both, - } - }, - new Drawable[] - { - new Container - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Children = new Drawable[] - { - new RoundedButton - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - RelativeSizeAxes = Axes.X, - Width = 0.45f, - Height = button_size.Y, - Margin = new MarginPadding(10), - BackgroundColour = colours.Pink2, - Text = CommonStrings.ButtonsCancel, - Action = this.Exit - }, - selectionButton = new RoundedButton - { - Anchor = Anchor.CentreRight, - Origin = Anchor.CentreRight, - RelativeSizeAxes = Axes.X, - Width = 0.45f, - Height = button_size.Y, - Margin = new MarginPadding(10), - Text = MaintenanceSettingsStrings.SelectDirectory, - Action = () => - { - legacyImportManager.UpdateStorage(directorySelector.CurrentPath.Value.FullName); - this.Exit(); - } - }, - } - }, - } - } - } - } - }; - } - [Resolved] private LegacyImportManager legacyImportManager { get; set; } = null!; + // TODO: test + public IEnumerable HandledExtensions { get; } = new[] { string.Empty }; + + private readonly Bindable currentDirectory = new Bindable(); + + [Resolved] + private OsuGameBase game { get; set; } = null!; + protected override void LoadComplete() { - if (legacyImportManager.GetCurrentStableStorage() is StableStorage storage) - directorySelector.CurrentPath.Value = new DirectoryInfo(storage.GetFullPath(string.Empty)); - - directorySelector.CurrentPath.BindValueChanged(e => selectionButton.Enabled.Value = e.NewValue != null && IsValidDirectory(e.NewValue), true); base.LoadComplete(); + + game.RegisterImportHandler(this); + + currentDirectory.BindValueChanged(onDirectorySelected); + + string? fullPath = legacyImportManager.GetCurrentStableStorage()?.GetFullPath(string.Empty); + if (fullPath != null) + currentDirectory.Value = new DirectoryInfo(fullPath); } - public override void OnSuspending(ScreenTransitionEvent e) + private void onDirectorySelected(ValueChangedEvent directory) { - base.OnSuspending(e); + if (directory.NewValue == null || directory.OldValue == null) + { + Current.Value = string.Empty; + return; + } - this.FadeOut(250); + // DirectorySelectors can trigger a noop value changed, but `DirectoryInfo` equality doesn't catch this. + if (directory.OldValue.FullName == directory.NewValue.FullName) + return; + + if (directory.NewValue?.GetFiles(@"osu!.*.cfg").Any() ?? false) + { + this.HidePopover(); + + string path = directory.NewValue.FullName; + + legacyImportManager.UpdateStorage(path); + Current.Value = path; + } + } + + Task ICanAcceptFiles.Import(params string[] paths) + { + Schedule(() => currentDirectory.Value = new DirectoryInfo(paths.First())); + return Task.CompletedTask; + } + + Task ICanAcceptFiles.Import(params ImportTask[] tasks) => throw new NotImplementedException(); + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + game.UnregisterImportHandler(this); + } + + public override Popover GetPopover() => new DirectoryChooserPopover(currentDirectory); + + private class DirectoryChooserPopover : OsuPopover + { + public DirectoryChooserPopover(Bindable currentDirectory) + { + Child = new Container + { + Size = new Vector2(600, 400), + Child = new OsuDirectorySelector(currentDirectory.Value?.FullName) + { + RelativeSizeAxes = Axes.Both, + CurrentPath = { BindTarget = currentDirectory } + }, + }; + } } } } diff --git a/osu.Game/Overlays/FirstRunSetupOverlay.cs b/osu.Game/Overlays/FirstRunSetupOverlay.cs index dae2815e98..890f17b013 100644 --- a/osu.Game/Overlays/FirstRunSetupOverlay.cs +++ b/osu.Game/Overlays/FirstRunSetupOverlay.cs @@ -11,6 +11,7 @@ using osu.Framework.Bindables; using osu.Framework.Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Events; @@ -88,7 +89,7 @@ namespace osu.Game.Overlays MainAreaContent.AddRange(new Drawable[] { - content = new Container + content = new PopoverContainer { Anchor = Anchor.Centre, Origin = Anchor.Centre, From 30fdbc3de070fffecdf6e7666de15b4e6e264b42 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 17 May 2022 16:55:41 +0900 Subject: [PATCH 051/395] Add safety against calling `GetStableImportPaths` when path doesn't exist --- osu.Game/Database/LegacyModelImporter.cs | 10 ++++++++-- osu.Game/Database/LegacyScoreImporter.cs | 10 ++++++++-- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/osu.Game/Database/LegacyModelImporter.cs b/osu.Game/Database/LegacyModelImporter.cs index a6e0f5bf8b..13a9025ece 100644 --- a/osu.Game/Database/LegacyModelImporter.cs +++ b/osu.Game/Database/LegacyModelImporter.cs @@ -24,8 +24,14 @@ namespace osu.Game.Database /// /// Select paths to import from stable where all paths should be absolute. Default implementation iterates all directories in . /// - protected virtual IEnumerable GetStableImportPaths(Storage storage) => storage.GetDirectories(ImportFromStablePath) - .Select(path => storage.GetFullPath(path)); + protected virtual IEnumerable GetStableImportPaths(Storage storage) + { + if (!storage.ExistsDirectory(ImportFromStablePath)) + return Enumerable.Empty(); + + return storage.GetDirectories(ImportFromStablePath) + .Select(path => storage.GetFullPath(path)); + } protected readonly IModelImporter Importer; diff --git a/osu.Game/Database/LegacyScoreImporter.cs b/osu.Game/Database/LegacyScoreImporter.cs index 48445b7bdb..131b4ffb0e 100644 --- a/osu.Game/Database/LegacyScoreImporter.cs +++ b/osu.Game/Database/LegacyScoreImporter.cs @@ -15,8 +15,14 @@ namespace osu.Game.Database protected override string ImportFromStablePath => Path.Combine("Data", "r"); protected override IEnumerable GetStableImportPaths(Storage storage) - => storage.GetFiles(ImportFromStablePath).Where(p => Importer.HandledExtensions.Any(ext => Path.GetExtension(p)?.Equals(ext, StringComparison.OrdinalIgnoreCase) ?? false)) - .Select(path => storage.GetFullPath(path)); + { + if (!storage.ExistsDirectory(ImportFromStablePath)) + return Enumerable.Empty(); + + return storage.GetFiles(ImportFromStablePath) + .Where(p => Importer.HandledExtensions.Any(ext => Path.GetExtension(p)?.Equals(ext, StringComparison.OrdinalIgnoreCase) ?? false)) + .Select(path => storage.GetFullPath(path)); + } public LegacyScoreImporter(IModelImporter importer) : base(importer) From 03d3900a02ede396fe0fa0eb095b2579586fde66 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 17 May 2022 17:12:10 +0900 Subject: [PATCH 052/395] Fix incorrect default state of checkboxes --- osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs b/osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs index 8b7550e301..7791c3614e 100644 --- a/osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs +++ b/osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs @@ -146,6 +146,7 @@ namespace osu.Game.Overlays.FirstRunSetup StableContent = stableContent; + Current.Default = true; Current.Value = true; LabelText = title; From 17e0105c2cff5b17aefd5f9b52c1dd29d6b9482c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 17 May 2022 17:26:26 +0900 Subject: [PATCH 053/395] Fix interaction with popover when textbox is disabled --- osu.Game/Screens/Edit/Setup/LabelledTextBoxWithPopover.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Screens/Edit/Setup/LabelledTextBoxWithPopover.cs b/osu.Game/Screens/Edit/Setup/LabelledTextBoxWithPopover.cs index 1136675347..799311dd2d 100644 --- a/osu.Game/Screens/Edit/Setup/LabelledTextBoxWithPopover.cs +++ b/osu.Game/Screens/Edit/Setup/LabelledTextBoxWithPopover.cs @@ -39,6 +39,9 @@ namespace osu.Game.Screens.Edit.Setup protected override void OnFocus(FocusEvent e) { + if (Current.Disabled) + return; + OnFocused?.Invoke(); base.OnFocus(e); From c0de1f96ff2158a293fc546a11b729d84ea4df77 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 17 May 2022 17:26:44 +0900 Subject: [PATCH 054/395] Tidy up interaction toggling and add progress message --- .../FirstRunSetupOverlayStrings.cs | 4 +- .../FirstRunSetup/ScreenImportFromStable.cs | 42 ++++++++++++------- 2 files changed, 30 insertions(+), 16 deletions(-) diff --git a/osu.Game/Localisation/FirstRunSetupOverlayStrings.cs b/osu.Game/Localisation/FirstRunSetupOverlayStrings.cs index 6b5ca7534d..181e35e4ce 100644 --- a/osu.Game/Localisation/FirstRunSetupOverlayStrings.cs +++ b/osu.Game/Localisation/FirstRunSetupOverlayStrings.cs @@ -80,9 +80,9 @@ We recommend you give the new defaults a try, but if you'd like to have things f public static LocalisableString ImportTitle => new TranslatableString(getKey(@"import_title"), @"Import"); /// - /// "Import content from stable" + /// "Import content from previous version" /// - public static LocalisableString ImportContentFromStable => new TranslatableString(getKey(@"import_content_from_stable"), @"Import content from osu!(stable)"); + public static LocalisableString ImportContentFromPreviousVersion => new TranslatableString(getKey(@"import_content_from_previous_version"), @"Import content from previous version"); private static string getKey(string key) => $@"{prefix}:{key}"; } diff --git a/osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs b/osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs index 7791c3614e..3b3cf4caf7 100644 --- a/osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs +++ b/osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs @@ -34,11 +34,11 @@ namespace osu.Game.Overlays.FirstRunSetup private ProgressRoundedButton importButton = null!; + private OsuTextFlowContainer progressText = null!; + [Resolved] private LegacyImportManager legacyImportManager { get; set; } = null!; - private CancellationTokenSource? stablePathUpdateCancellation; - private StableLocatorLabelledTextBox stableLocatorTextBox = null!; private IEnumerable contentCheckboxes => Content.Children.OfType(); @@ -70,9 +70,18 @@ namespace osu.Game.Overlays.FirstRunSetup Size = button_size, Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, - Text = FirstRunSetupOverlayStrings.ImportContentFromStable, + Text = FirstRunSetupOverlayStrings.ImportContentFromPreviousVersion, Action = runImport }, + progressText = new OsuTextFlowContainer(cp => cp.Font = OsuFont.Default.With(size: CONTENT_FONT_SIZE)) + { + Colour = OverlayColourProvider.Content1, + Text = + "Your import will continue in the background. Check on its progress in the notifications sidebar!", + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Alpha = 0, + }, }; stableLocatorTextBox.Current.BindValueChanged(_ => updateStablePath(), true); @@ -80,17 +89,14 @@ namespace osu.Game.Overlays.FirstRunSetup private void updateStablePath() { - stablePathUpdateCancellation?.Cancel(); - var storage = legacyImportManager.GetCurrentStableStorage(); if (storage == null) { - foreach (var c in contentCheckboxes) - c.Current.Disabled = true; + allowInteraction(false); + stableLocatorTextBox.Current.Disabled = false; stableLocatorTextBox.Current.Value = string.Empty; - importButton.Enabled.Value = false; return; } @@ -100,16 +106,15 @@ namespace osu.Game.Overlays.FirstRunSetup c.UpdateCount(); } + allowInteraction(true); stableLocatorTextBox.Current.Value = storage.GetFullPath(string.Empty); - - stablePathUpdateCancellation = new CancellationTokenSource(); importButton.Enabled.Value = true; } private void runImport() { - importButton.Enabled.Value = false; - stableLocatorTextBox.Current.Disabled = true; + allowInteraction(false); + progressText.FadeIn(1000, Easing.OutQuint); StableContent importableContent = 0; @@ -118,17 +123,26 @@ namespace osu.Game.Overlays.FirstRunSetup legacyImportManager.ImportFromStableAsync(importableContent, false).ContinueWith(t => Schedule(() => { + progressText.FadeOut(500, Easing.OutQuint); + if (t.IsCompletedSuccessfully) importButton.Complete(); else { - importButton.Enabled.Value = true; - stableLocatorTextBox.Current.Disabled = false; + allowInteraction(true); importButton.Abort(); } })); } + private void allowInteraction(bool allow) + { + importButton.Enabled.Value = allow; + stableLocatorTextBox.Current.Disabled = !allow; + foreach (var c in contentCheckboxes) + c.Current.Disabled = !allow; + } + private class ImportCheckbox : SettingsCheckbox { public readonly StableContent StableContent; From 83e781d5a1ef9da5e3c0d6f071ca33c44df20279 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 17 May 2022 17:42:46 +0900 Subject: [PATCH 055/395] Allow localisation of `PlaceholderText` --- osu.Game/Graphics/UserInterfaceV2/LabelledTextBox.cs | 3 ++- .../Edit/Compose/Components/Timeline/SamplePointPiece.cs | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game/Graphics/UserInterfaceV2/LabelledTextBox.cs b/osu.Game/Graphics/UserInterfaceV2/LabelledTextBox.cs index fd64cc2056..82b9fe559f 100644 --- a/osu.Game/Graphics/UserInterfaceV2/LabelledTextBox.cs +++ b/osu.Game/Graphics/UserInterfaceV2/LabelledTextBox.cs @@ -6,6 +6,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.UserInterface; using osu.Framework.Input.Events; +using osu.Framework.Localisation; using osu.Game.Graphics.UserInterface; namespace osu.Game.Graphics.UserInterfaceV2 @@ -25,7 +26,7 @@ namespace osu.Game.Graphics.UserInterfaceV2 set => Component.ReadOnly = value; } - public string PlaceholderText + public LocalisableString PlaceholderText { set => Component.PlaceholderText = value; } diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs index ab21a83c43..9abea73f6b 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/SamplePointPiece.cs @@ -154,7 +154,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline private void updateBankPlaceholderText(IEnumerable objects) { string? commonBank = getCommonBank(objects.Select(h => h.SampleControlPoint).ToArray()); - bank.PlaceholderText = string.IsNullOrEmpty(commonBank) ? "(multiple)" : null; + bank.PlaceholderText = string.IsNullOrEmpty(commonBank) ? "(multiple)" : string.Empty; } private void updateVolumeFor(IEnumerable objects, int? newVolume) From f4e0ad8c4c87c41454ce28033f150569fa01174a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 17 May 2022 17:51:28 +0900 Subject: [PATCH 056/395] Fix drag drop of osu! folder not working as expected --- osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs b/osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs index 3b3cf4caf7..b145dadd39 100644 --- a/osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs +++ b/osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs @@ -211,14 +211,14 @@ namespace osu.Game.Overlays.FirstRunSetup private void onDirectorySelected(ValueChangedEvent directory) { - if (directory.NewValue == null || directory.OldValue == null) + if (directory.NewValue == null) { Current.Value = string.Empty; return; } // DirectorySelectors can trigger a noop value changed, but `DirectoryInfo` equality doesn't catch this. - if (directory.OldValue.FullName == directory.NewValue.FullName) + if (directory.OldValue?.FullName == directory.NewValue.FullName) return; if (directory.NewValue?.GetFiles(@"osu!.*.cfg").Any() ?? false) From bf00b062ad05b000f5cb955881494c6da57ece9e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 17 May 2022 17:58:24 +0900 Subject: [PATCH 057/395] Add full localisation of import beatmaps screen --- ...RunOverlayImportFromStableScreenStrings.cs | 56 +++++++++++++++++++ .../FirstRunSetupOverlayStrings.cs | 10 ---- .../FirstRunSetup/ScreenImportFromStable.cs | 20 +++---- 3 files changed, 66 insertions(+), 20 deletions(-) create mode 100644 osu.Game/Localisation/FirstRunOverlayImportFromStableScreenStrings.cs diff --git a/osu.Game/Localisation/FirstRunOverlayImportFromStableScreenStrings.cs b/osu.Game/Localisation/FirstRunOverlayImportFromStableScreenStrings.cs new file mode 100644 index 0000000000..deac7d8628 --- /dev/null +++ b/osu.Game/Localisation/FirstRunOverlayImportFromStableScreenStrings.cs @@ -0,0 +1,56 @@ +// 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.Localisation; + +namespace osu.Game.Localisation +{ + public static class FirstRunOverlayImportFromStableScreenStrings + { + private const string prefix = @"osu.Game.Resources.Localisation.ScreenImportFromStable"; + + /// + /// "Import" + /// + public static LocalisableString Header => new TranslatableString(getKey(@"header"), @"Import"); + + /// + /// "If you have an installation of a previous osu! version, you can choose to migrate your existing content. Note that this will create a copy, and not affect your existing installation." + /// + public static LocalisableString Description => new TranslatableString(getKey(@"description"), + @"If you have an installation of a previous osu! version, you can choose to migrate your existing content. Note that this will create a copy, and not affect your existing installation."); + + /// + /// "previous osu! install" + /// + public static LocalisableString LocateDirectoryLabel => new TranslatableString(getKey(@"locate_directory_label"), @"previous osu! install"); + + /// + /// "Click to locate a previous osu! install" + /// + public static LocalisableString LocateDirectoryPlaceholder => new TranslatableString(getKey(@"locate_directory_placeholder"), @"Click to locate a previous osu! install"); + + /// + /// "Import content from previous version" + /// + public static LocalisableString ImportButton => new TranslatableString(getKey(@"import_button"), @"Import content from previous version"); + + /// + /// "Your import will continue in the background. Check on its progress in the notifications sidebar!" + /// + public static LocalisableString ImportInProgress => + new TranslatableString(getKey(@"import_in_progress"), @"Your import will continue in the background. Check on its progress in the notifications sidebar!"); + + /// + /// "calculating..." + /// + public static LocalisableString Calculating => new TranslatableString(getKey(@"calculating"), @"calculating..."); + + /// + /// "{0} items" + /// + public static LocalisableString Items(int arg0) => new TranslatableString(getKey(@"items"), @"{0} item(s)", arg0); + + private static string getKey(string key) => $@"{prefix}:{key}"; + } +} diff --git a/osu.Game/Localisation/FirstRunSetupOverlayStrings.cs b/osu.Game/Localisation/FirstRunSetupOverlayStrings.cs index 181e35e4ce..91b427e2ca 100644 --- a/osu.Game/Localisation/FirstRunSetupOverlayStrings.cs +++ b/osu.Game/Localisation/FirstRunSetupOverlayStrings.cs @@ -74,16 +74,6 @@ We recommend you give the new defaults a try, but if you'd like to have things f /// public static LocalisableString ClassicDefaults => new TranslatableString(getKey(@"classic_defaults"), @"Classic defaults"); - /// - /// "Welcome" - /// - public static LocalisableString ImportTitle => new TranslatableString(getKey(@"import_title"), @"Import"); - - /// - /// "Import content from previous version" - /// - public static LocalisableString ImportContentFromPreviousVersion => new TranslatableString(getKey(@"import_content_from_previous_version"), @"Import content from previous version"); - private static string getKey(string key) => $@"{prefix}:{key}"; } } diff --git a/osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs b/osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs index b145dadd39..d5ef879479 100644 --- a/osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs +++ b/osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs @@ -27,7 +27,7 @@ using osuTK; namespace osu.Game.Overlays.FirstRunSetup { - [LocalisableDescription(typeof(FirstRunSetupOverlayStrings), nameof(FirstRunSetupOverlayStrings.ImportTitle))] + [LocalisableDescription(typeof(FirstRunOverlayImportFromStableScreenStrings), nameof(FirstRunOverlayImportFromStableScreenStrings.Header))] public class ScreenImportFromStable : FirstRunSetupScreen { private static readonly Vector2 button_size = new Vector2(400, 50); @@ -51,15 +51,14 @@ namespace osu.Game.Overlays.FirstRunSetup new OsuTextFlowContainer(cp => cp.Font = OsuFont.Default.With(size: CONTENT_FONT_SIZE)) { Colour = OverlayColourProvider.Content1, - Text = - "If you have an installation of a previous osu! version, you can choose to migrate your existing content. Note that this will create a copy, and not affect your existing installation.", + Text = FirstRunOverlayImportFromStableScreenStrings.Description, RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y }, stableLocatorTextBox = new StableLocatorLabelledTextBox { - Label = "previous osu! install", - PlaceholderText = "Click to locate a previous osu! install" + Label = FirstRunOverlayImportFromStableScreenStrings.LocateDirectoryLabel, + PlaceholderText = FirstRunOverlayImportFromStableScreenStrings.LocateDirectoryPlaceholder }, new ImportCheckbox("Beatmaps", StableContent.Beatmaps), new ImportCheckbox("Scores", StableContent.Scores), @@ -70,14 +69,13 @@ namespace osu.Game.Overlays.FirstRunSetup Size = button_size, Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, - Text = FirstRunSetupOverlayStrings.ImportContentFromPreviousVersion, + Text = FirstRunOverlayImportFromStableScreenStrings.ImportButton, Action = runImport }, progressText = new OsuTextFlowContainer(cp => cp.Font = OsuFont.Default.With(size: CONTENT_FONT_SIZE)) { Colour = OverlayColourProvider.Content1, - Text = - "Your import will continue in the background. Check on its progress in the notifications sidebar!", + Text = FirstRunOverlayImportFromStableScreenStrings.ImportInProgress, RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, Alpha = 0, @@ -168,7 +166,7 @@ namespace osu.Game.Overlays.FirstRunSetup public void UpdateCount() { - LabelText = LocalisableString.Interpolate($"{title} (calculating...)"); + LabelText = LocalisableString.Interpolate($"{title} ({FirstRunOverlayImportFromStableScreenStrings.Calculating})"); countUpdateCancellation?.Cancel(); countUpdateCancellation = new CancellationTokenSource(); @@ -178,7 +176,9 @@ namespace osu.Game.Overlays.FirstRunSetup if (task.IsCanceled) return; - LabelText = LocalisableString.Interpolate($"{title} ({task.GetResultSafely()} items)"); + int count = task.GetResultSafely(); + + LabelText = LocalisableString.Interpolate($"{title} ({FirstRunOverlayImportFromStableScreenStrings.Items(count)})"); })); } } From 1b7ec1be26e806796258298641cba65a9d043370 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 17 May 2022 18:15:14 +0900 Subject: [PATCH 058/395] Add basic test coverage of new screen --- ...TestSceneFirstRunScreenImportFromStable.cs | 48 +++++++++++++++++++ osu.Game/Database/LegacyImportManager.cs | 8 ++-- 2 files changed, 52 insertions(+), 4 deletions(-) create mode 100644 osu.Game.Tests/Visual/UserInterface/TestSceneFirstRunScreenImportFromStable.cs diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneFirstRunScreenImportFromStable.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneFirstRunScreenImportFromStable.cs new file mode 100644 index 0000000000..081b240795 --- /dev/null +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneFirstRunScreenImportFromStable.cs @@ -0,0 +1,48 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Threading; +using System.Threading.Tasks; +using Moq; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Cursor; +using osu.Framework.Screens; +using osu.Framework.Utils; +using osu.Game.Database; +using osu.Game.Overlays; +using osu.Game.Overlays.FirstRunSetup; + +namespace osu.Game.Tests.Visual.UserInterface +{ + public class TestSceneFirstRunScreenImportFromStable : OsuManualInputManagerTestScene + { + [Cached] + private OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Purple); + + private readonly Mock legacyImportManager = new Mock(); + + [BackgroundDependencyLoader] + private void load() + { + legacyImportManager.Setup(m => m.GetImportCount(It.IsAny(), It.IsAny())).Returns(() => Task.FromResult(RNG.Next(0, 256))); + + Dependencies.CacheAs(legacyImportManager.Object); + } + + public TestSceneFirstRunScreenImportFromStable() + { + AddStep("load screen", () => + { + Child = new PopoverContainer + { + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + new ScreenStack(new ScreenImportFromStable()) + } + }; + }); + } + } +} diff --git a/osu.Game/Database/LegacyImportManager.cs b/osu.Game/Database/LegacyImportManager.cs index 35b1d05fa1..af9db1b6ec 100644 --- a/osu.Game/Database/LegacyImportManager.cs +++ b/osu.Game/Database/LegacyImportManager.cs @@ -37,13 +37,13 @@ namespace osu.Game.Database [Resolved] private CollectionManager collections { get; set; } - [Resolved] + [Resolved(canBeNull: true)] private OsuGame game { get; set; } [Resolved] private IDialogOverlay dialogOverlay { get; set; } - [Resolved(CanBeNull = true)] + [Resolved(canBeNull: true)] private DesktopGameHost desktopGameHost { get; set; } private StableStorage cachedStorage; @@ -52,7 +52,7 @@ namespace osu.Game.Database public void UpdateStorage(string stablePath) => cachedStorage = new StableStorage(stablePath, desktopGameHost); - public async Task GetImportCount(StableContent content, CancellationToken cancellationToken) + public virtual async Task GetImportCount(StableContent content, CancellationToken cancellationToken) { var stableStorage = GetCurrentStableStorage(); @@ -120,7 +120,7 @@ namespace osu.Game.Database if (cachedStorage != null) return cachedStorage; - var stableStorage = game.GetStorageForStableInstall(); + var stableStorage = game?.GetStorageForStableInstall(); if (stableStorage != null) return cachedStorage = stableStorage; From 5ad969246702e79eef2b10f155891fa4b61ee3ce Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 17 May 2022 14:59:18 +0900 Subject: [PATCH 059/395] Adjust minimum health targets to match osu-stable --- osu.Game/Rulesets/Scoring/DrainingHealthProcessor.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Rulesets/Scoring/DrainingHealthProcessor.cs b/osu.Game/Rulesets/Scoring/DrainingHealthProcessor.cs index dfeb6b4788..e7bb6d79f8 100644 --- a/osu.Game/Rulesets/Scoring/DrainingHealthProcessor.cs +++ b/osu.Game/Rulesets/Scoring/DrainingHealthProcessor.cs @@ -27,17 +27,17 @@ namespace osu.Game.Rulesets.Scoring /// /// The minimum health target at an HP drain rate of 0. /// - private const double min_health_target = 0.95; + private const double min_health_target = 0.99; /// /// The minimum health target at an HP drain rate of 5. /// - private const double mid_health_target = 0.70; + private const double mid_health_target = 0.9; /// /// The minimum health target at an HP drain rate of 10. /// - private const double max_health_target = 0.30; + private const double max_health_target = 0.4; private IBeatmap beatmap; From 932442e2422db30333071d6a441b993d4f50667b Mon Sep 17 00:00:00 2001 From: Jai Sharma Date: Tue, 17 May 2022 18:52:19 +0100 Subject: [PATCH 060/395] Use `null` for empty grid cells in `ChannelListItem` --- .../Chat/ChannelList/ChannelListItem.cs | 22 ++++++++----------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/osu.Game/Overlays/Chat/ChannelList/ChannelListItem.cs b/osu.Game/Overlays/Chat/ChannelList/ChannelListItem.cs index dfb0b6d781..da1cfaf617 100644 --- a/osu.Game/Overlays/Chat/ChannelList/ChannelListItem.cs +++ b/osu.Game/Overlays/Chat/ChannelList/ChannelListItem.cs @@ -34,7 +34,7 @@ namespace osu.Game.Overlays.Chat.ChannelList private Box hoverBox = null!; private Box selectBox = null!; private OsuSpriteText text = null!; - private Drawable close = null!; + private Drawable? close; [Resolved] private Bindable selectedChannel { get; set; } = null!; @@ -119,9 +119,7 @@ namespace osu.Game.Overlays.Chat.ChannelList protected override bool OnHover(HoverEvent e) { hoverBox.FadeIn(300, Easing.OutQuint); - - if (!isSelector) - close.FadeIn(300, Easing.OutQuint); + close?.FadeIn(300, Easing.OutQuint); return base.OnHover(e); } @@ -129,17 +127,15 @@ namespace osu.Game.Overlays.Chat.ChannelList protected override void OnHoverLost(HoverLostEvent e) { hoverBox.FadeOut(200, Easing.OutQuint); - - if (!isSelector) - close.FadeOut(200, Easing.OutQuint); + close?.FadeOut(200, Easing.OutQuint); base.OnHoverLost(e); } - private Drawable createIcon() + private UpdateableAvatar? createIcon() { if (Channel.Type != ChannelType.PM) - return Drawable.Empty(); + return null; return new UpdateableAvatar(Channel.Users.First(), isInteractive: false) { @@ -152,10 +148,10 @@ namespace osu.Game.Overlays.Chat.ChannelList }; } - private Drawable createMentionPill() + private ChannelListItemMentionPill? createMentionPill() { if (isSelector) - return Drawable.Empty(); + return null; return new ChannelListItemMentionPill { @@ -166,10 +162,10 @@ namespace osu.Game.Overlays.Chat.ChannelList }; } - private Drawable createCloseButton() + private ChannelListItemCloseButton? createCloseButton() { if (isSelector) - return Drawable.Empty(); + return null; return new ChannelListItemCloseButton { From 4015042e369a6d0900cca61beb0a13ee795d1639 Mon Sep 17 00:00:00 2001 From: Jai Sharma Date: Wed, 18 May 2022 01:35:39 +0100 Subject: [PATCH 061/395] Ensure close button component is not stored as a `Drawable` --- osu.Game/Overlays/Chat/ChannelList/ChannelListItem.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/Chat/ChannelList/ChannelListItem.cs b/osu.Game/Overlays/Chat/ChannelList/ChannelListItem.cs index da1cfaf617..eb2bdfdf8a 100644 --- a/osu.Game/Overlays/Chat/ChannelList/ChannelListItem.cs +++ b/osu.Game/Overlays/Chat/ChannelList/ChannelListItem.cs @@ -34,7 +34,7 @@ namespace osu.Game.Overlays.Chat.ChannelList private Box hoverBox = null!; private Box selectBox = null!; private OsuSpriteText text = null!; - private Drawable? close; + private ChannelListItemCloseButton? close; [Resolved] private Bindable selectedChannel { get; set; } = null!; @@ -83,7 +83,7 @@ namespace osu.Game.Overlays.Chat.ChannelList }, Content = new[] { - new[] + new Drawable?[] { createIcon(), text = new OsuSpriteText From dd4b11c593f7869295e246241e388c701e641f99 Mon Sep 17 00:00:00 2001 From: Jai Sharma Date: Wed, 18 May 2022 01:47:23 +0100 Subject: [PATCH 062/395] Re-add exception handling on PM message request failure --- osu.Game/Online/Chat/ChannelManager.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Online/Chat/ChannelManager.cs b/osu.Game/Online/Chat/ChannelManager.cs index 9524e8c1a7..9e85067f15 100644 --- a/osu.Game/Online/Chat/ChannelManager.cs +++ b/osu.Game/Online/Chat/ChannelManager.cs @@ -197,6 +197,7 @@ namespace osu.Game.Online.Chat createNewPrivateMessageRequest.Failure += exception => { + handlePostException(exception); target.ReplaceMessage(message, null); dequeueAndRun(); }; From 49340888c09cd27e9e030648404947474d07510f Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 18 May 2022 10:56:42 +0900 Subject: [PATCH 063/395] Fix compile errors --- osu.Game.Tests/Visual/Settings/TestSceneSettingsPanel.cs | 2 +- osu.Game.Tests/Visual/UserInterface/TestScenePlaylistOverlay.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Settings/TestSceneSettingsPanel.cs b/osu.Game.Tests/Visual/Settings/TestSceneSettingsPanel.cs index 377873f64a..97463b7466 100644 --- a/osu.Game.Tests/Visual/Settings/TestSceneSettingsPanel.cs +++ b/osu.Game.Tests/Visual/Settings/TestSceneSettingsPanel.cs @@ -55,7 +55,7 @@ namespace osu.Game.Tests.Visual.Settings .OfType() .OfType() .Where(f => !(f is IHasFilterableChildren)) - .All(f => f.FilterTerms.Any(t => t.Contains("scaling"))) + .All(f => f.FilterTerms.Any(t => t.ToString().Contains("scaling"))) )); AddAssert("ensure section is current", () => settings.CurrentSection.Value is GraphicsSection); diff --git a/osu.Game.Tests/Visual/UserInterface/TestScenePlaylistOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestScenePlaylistOverlay.cs index 09e5bc849e..84a06d97df 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestScenePlaylistOverlay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestScenePlaylistOverlay.cs @@ -89,7 +89,7 @@ namespace osu.Game.Tests.Visual.UserInterface AddAssert("results filtered correctly", () => playlistOverlay.ChildrenOfType() .Where(item => item.MatchingFilter) - .All(item => item.FilterTerms.Any(term => term.Contains("10")))); + .All(item => item.FilterTerms.Any(term => term.ToString().Contains("10")))); } } } From 3f71224dfc26a7cd87c6737bac79f86af84de2b2 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 18 May 2022 14:05:21 +0900 Subject: [PATCH 064/395] Package .json files in nupkg output --- osu.Desktop/osu.nuspec | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Desktop/osu.nuspec b/osu.Desktop/osu.nuspec index dc1ec17e2c..db58c325bd 100644 --- a/osu.Desktop/osu.nuspec +++ b/osu.Desktop/osu.nuspec @@ -18,5 +18,6 @@ + From fe49a7e6782e66dedbe5805192c6930589aa6522 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 18 May 2022 17:10:19 +0900 Subject: [PATCH 065/395] Add failing tests --- .../Formats/LegacyBeatmapDecoderTest.cs | 35 +++++++++++++++++++ .../Resources/adjacent-catmull-segments.osu | 2 ++ 2 files changed, 37 insertions(+) create mode 100644 osu.Game.Tests/Resources/adjacent-catmull-segments.osu diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs index 468cb7683c..89baaf228d 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs @@ -863,5 +863,40 @@ namespace osu.Game.Tests.Beatmaps.Formats Assert.That(decoded.Difficulty.OverallDifficulty, Is.EqualTo(1)); } } + + [Test] + public void TestLegacyAdjacentImplicitCatmullSegmentsAreMerged() + { + var decoder = new LegacyBeatmapDecoder { ApplyOffsets = false }; + + using (var resStream = TestResources.OpenResource("adjacent-catmull-segments.osu")) + using (var stream = new LineBufferedReader(resStream)) + { + var decoded = decoder.Decode(stream); + var controlPoints = ((IHasPath)decoded.HitObjects[0]).Path.ControlPoints; + + Assert.That(controlPoints.Count, Is.EqualTo(6)); + Assert.That(controlPoints.Single(c => c.Type != null).Type, Is.EqualTo(PathType.Catmull)); + } + } + + [Test] + public void TestNonLegacyAdjacentImplicitCatmullSegmentsAreNotMerged() + { + var decoder = new LegacyBeatmapDecoder(LegacyBeatmapEncoder.FIRST_LAZER_VERSION) { ApplyOffsets = false }; + + using (var resStream = TestResources.OpenResource("adjacent-catmull-segments.osu")) + using (var stream = new LineBufferedReader(resStream)) + { + var decoded = decoder.Decode(stream); + var controlPoints = ((IHasPath)decoded.HitObjects[0]).Path.ControlPoints; + + Assert.That(controlPoints.Count, Is.EqualTo(4)); + Assert.That(controlPoints[0].Type, Is.EqualTo(PathType.Catmull)); + Assert.That(controlPoints[1].Type, Is.EqualTo(PathType.Catmull)); + Assert.That(controlPoints[2].Type, Is.EqualTo(PathType.Catmull)); + Assert.That(controlPoints[3].Type, Is.Null); + } + } } } diff --git a/osu.Game.Tests/Resources/adjacent-catmull-segments.osu b/osu.Game.Tests/Resources/adjacent-catmull-segments.osu new file mode 100644 index 0000000000..a436fe5228 --- /dev/null +++ b/osu.Game.Tests/Resources/adjacent-catmull-segments.osu @@ -0,0 +1,2 @@ +[HitObjects] +200,304,23875,6,0,C|288:304|288:304|288:208|288:208|352:208,1,260,8|0 \ No newline at end of file From 731f0960ec53ed3a6059ae5a29cfd5a27f5c3d67 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 18 May 2022 17:11:08 +0900 Subject: [PATCH 066/395] Don't merge adjacent legacy Catmull segments --- osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs | 4 ++-- osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs | 6 +++++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs index b91a74c4a1..3b4200e7a9 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs @@ -22,7 +22,7 @@ namespace osu.Game.Beatmaps.Formats { public class LegacyBeatmapEncoder { - public const int LATEST_VERSION = 128; + public const int FIRST_LAZER_VERSION = 128; /// /// osu! is generally slower than taiko, so a factor is added to increase @@ -55,7 +55,7 @@ namespace osu.Game.Beatmaps.Formats public void Encode(TextWriter writer) { - writer.WriteLine($"osu file format v{LATEST_VERSION}"); + writer.WriteLine($"osu file format v{FIRST_LAZER_VERSION}"); writer.WriteLine(); handleGeneral(writer); diff --git a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs index 2a7f2b037f..7cf68a2df7 100644 --- a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs @@ -336,10 +336,14 @@ namespace osu.Game.Rulesets.Objects.Legacy while (++endIndex < vertices.Length - endPointLength) { - // Keep incrementing while an implicit segment doesn't need to be started + // Keep incrementing while an implicit segment doesn't need to be started. if (vertices[endIndex].Position != vertices[endIndex - 1].Position) continue; + // Adjacent legacy Catmull segments should be treated as a single segment. + if (FormatVersion < LegacyBeatmapEncoder.FIRST_LAZER_VERSION && type == PathType.Catmull) + continue; + // The last control point of each segment is not allowed to start a new implicit segment. if (endIndex == vertices.Length - endPointLength - 1) continue; From df4968a55bda4c0d6dcc0cd9bb9d18ca94f511f9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 18 May 2022 21:20:14 +0900 Subject: [PATCH 067/395] Add `new Guid` bannedsymbols rule --- CodeAnalysis/BannedSymbols.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/CodeAnalysis/BannedSymbols.txt b/CodeAnalysis/BannedSymbols.txt index e96ad48325..b3cca21c98 100644 --- a/CodeAnalysis/BannedSymbols.txt +++ b/CodeAnalysis/BannedSymbols.txt @@ -11,6 +11,7 @@ T:Microsoft.EntityFrameworkCore.Internal.TypeExtensions;Don't use internal exten T:NuGet.Packaging.CollectionExtensions;Don't use internal extension methods. M:System.Enum.HasFlag(System.Enum);Use osu.Framework.Extensions.EnumExtensions.HasFlagFast() instead. M:Realms.IRealmCollection`1.SubscribeForNotifications`1(Realms.NotificationCallbackDelegate{``0});Use osu.Game.Database.RealmObjectExtensions.QueryAsyncWithNotifications(IRealmCollection,NotificationCallbackDelegate) instead. +M:System.Guid.#ctor;Probably meaning to use Guid.New() instead. If actually wanting empty, use Guid.Empty(). M:Realms.CollectionExtensions.SubscribeForNotifications`1(System.Linq.IQueryable{``0},Realms.NotificationCallbackDelegate{``0});Use osu.Game.Database.RealmObjectExtensions.QueryAsyncWithNotifications(IQueryable,NotificationCallbackDelegate) instead. M:Realms.CollectionExtensions.SubscribeForNotifications`1(System.Collections.Generic.IList{``0},Realms.NotificationCallbackDelegate{``0});Use osu.Game.Database.RealmObjectExtensions.QueryAsyncWithNotifications(IList,NotificationCallbackDelegate) instead. M:System.Threading.Tasks.Task.Wait();Don't use Task.Wait. Use Task.WaitSafely() to ensure we avoid deadlocks. From fa09270e6278ad808c82021938227b1fb3f46b36 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 18 May 2022 21:21:20 +0900 Subject: [PATCH 068/395] Remove left-over todo --- osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs b/osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs index d5ef879479..ad6ff21353 100644 --- a/osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs +++ b/osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs @@ -188,7 +188,6 @@ namespace osu.Game.Overlays.FirstRunSetup [Resolved] private LegacyImportManager legacyImportManager { get; set; } = null!; - // TODO: test public IEnumerable HandledExtensions { get; } = new[] { string.Empty }; private readonly Bindable currentDirectory = new Bindable(); From d54e1503c763f735360bc8caac09019a4a5dbd60 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 18 May 2022 21:22:17 +0900 Subject: [PATCH 069/395] Rename interaction toggle method --- .../Overlays/FirstRunSetup/ScreenImportFromStable.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs b/osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs index ad6ff21353..106e78dc18 100644 --- a/osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs +++ b/osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs @@ -91,7 +91,7 @@ namespace osu.Game.Overlays.FirstRunSetup if (storage == null) { - allowInteraction(false); + toggleInteraction(false); stableLocatorTextBox.Current.Disabled = false; stableLocatorTextBox.Current.Value = string.Empty; @@ -104,14 +104,14 @@ namespace osu.Game.Overlays.FirstRunSetup c.UpdateCount(); } - allowInteraction(true); + toggleInteraction(true); stableLocatorTextBox.Current.Value = storage.GetFullPath(string.Empty); importButton.Enabled.Value = true; } private void runImport() { - allowInteraction(false); + toggleInteraction(false); progressText.FadeIn(1000, Easing.OutQuint); StableContent importableContent = 0; @@ -127,13 +127,13 @@ namespace osu.Game.Overlays.FirstRunSetup importButton.Complete(); else { - allowInteraction(true); + toggleInteraction(true); importButton.Abort(); } })); } - private void allowInteraction(bool allow) + private void toggleInteraction(bool allow) { importButton.Enabled.Value = allow; stableLocatorTextBox.Current.Disabled = !allow; From 0cee90e156f31492bd81283c7e4b39238f10734d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 18 May 2022 21:32:32 +0900 Subject: [PATCH 070/395] Add common strings for missing localisable content --- osu.Game/Localisation/CommonStrings.cs | 22 ++++++++++++++++++- .../FirstRunSetup/ScreenImportFromStable.cs | 8 +++---- 2 files changed, 25 insertions(+), 5 deletions(-) diff --git a/osu.Game/Localisation/CommonStrings.cs b/osu.Game/Localisation/CommonStrings.cs index 9cd626af0f..1fd677034d 100644 --- a/osu.Game/Localisation/CommonStrings.cs +++ b/osu.Game/Localisation/CommonStrings.cs @@ -69,6 +69,26 @@ namespace osu.Game.Localisation /// public static LocalisableString SelectAll => new TranslatableString(getKey(@"select_all"), @"Select All"); + /// + /// "Beatmaps" + /// + public static LocalisableString Beatmaps => new TranslatableString(getKey(@"beatmaps"), @"Beatmaps"); + + /// + /// "Scores" + /// + public static LocalisableString Scores => new TranslatableString(getKey(@"scores"), @"Scores"); + + /// + /// "Skins" + /// + public static LocalisableString Skins => new TranslatableString(getKey(@"skins"), @"Skins"); + + /// + /// "Collections" + /// + public static LocalisableString Collections => new TranslatableString(getKey(@"collections"), @"Collections"); + private static string getKey(string key) => $@"{prefix}:{key}"; } -} +} \ No newline at end of file diff --git a/osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs b/osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs index 106e78dc18..cf8abf0a76 100644 --- a/osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs +++ b/osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs @@ -60,10 +60,10 @@ namespace osu.Game.Overlays.FirstRunSetup Label = FirstRunOverlayImportFromStableScreenStrings.LocateDirectoryLabel, PlaceholderText = FirstRunOverlayImportFromStableScreenStrings.LocateDirectoryPlaceholder }, - new ImportCheckbox("Beatmaps", StableContent.Beatmaps), - new ImportCheckbox("Scores", StableContent.Scores), - new ImportCheckbox("Skins", StableContent.Skins), - new ImportCheckbox("Collections", StableContent.Collections), + new ImportCheckbox(CommonStrings.Beatmaps, StableContent.Beatmaps), + new ImportCheckbox(CommonStrings.Scores, StableContent.Scores), + new ImportCheckbox(CommonStrings.Skins, StableContent.Skins), + new ImportCheckbox(CommonStrings.Collections, StableContent.Collections), importButton = new ProgressRoundedButton { Size = button_size, From f5235f6a563a013529387a73ea481ea3dc9fd5aa Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 18 May 2022 20:38:13 +0300 Subject: [PATCH 071/395] Correctify recommended method and property --- CodeAnalysis/BannedSymbols.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CodeAnalysis/BannedSymbols.txt b/CodeAnalysis/BannedSymbols.txt index b3cca21c98..b72df0a306 100644 --- a/CodeAnalysis/BannedSymbols.txt +++ b/CodeAnalysis/BannedSymbols.txt @@ -11,7 +11,7 @@ T:Microsoft.EntityFrameworkCore.Internal.TypeExtensions;Don't use internal exten T:NuGet.Packaging.CollectionExtensions;Don't use internal extension methods. M:System.Enum.HasFlag(System.Enum);Use osu.Framework.Extensions.EnumExtensions.HasFlagFast() instead. M:Realms.IRealmCollection`1.SubscribeForNotifications`1(Realms.NotificationCallbackDelegate{``0});Use osu.Game.Database.RealmObjectExtensions.QueryAsyncWithNotifications(IRealmCollection,NotificationCallbackDelegate) instead. -M:System.Guid.#ctor;Probably meaning to use Guid.New() instead. If actually wanting empty, use Guid.Empty(). +M:System.Guid.#ctor;Probably meaning to use Guid.NewGuid() instead. If actually wanting empty, use Guid.Empty. M:Realms.CollectionExtensions.SubscribeForNotifications`1(System.Linq.IQueryable{``0},Realms.NotificationCallbackDelegate{``0});Use osu.Game.Database.RealmObjectExtensions.QueryAsyncWithNotifications(IQueryable,NotificationCallbackDelegate) instead. M:Realms.CollectionExtensions.SubscribeForNotifications`1(System.Collections.Generic.IList{``0},Realms.NotificationCallbackDelegate{``0});Use osu.Game.Database.RealmObjectExtensions.QueryAsyncWithNotifications(IList,NotificationCallbackDelegate) instead. M:System.Threading.Tasks.Task.Wait();Don't use Task.Wait. Use Task.WaitSafely() to ensure we avoid deadlocks. From 371738b047105ec1f14b84baf992b1acc52b068d Mon Sep 17 00:00:00 2001 From: Jai Sharma Date: Wed, 18 May 2022 20:00:42 +0100 Subject: [PATCH 072/395] Check against the loaded drawable channel --- osu.Game/Overlays/ChatOverlayV2.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Overlays/ChatOverlayV2.cs b/osu.Game/Overlays/ChatOverlayV2.cs index e91d57f18b..611c539f8f 100644 --- a/osu.Game/Overlays/ChatOverlayV2.cs +++ b/osu.Game/Overlays/ChatOverlayV2.cs @@ -263,18 +263,18 @@ namespace osu.Game.Overlays ChatOverlayDrawableChannel drawableChannel = new ChatOverlayDrawableChannel(newChannel); loadedChannels.Add(newChannel, drawableChannel); - LoadComponentAsync(drawableChannel, loaded => + LoadComponentAsync(drawableChannel, loadedDrawable => { // Ensure the current channel hasn't changed by the time the load completes - if (currentChannel.Value != newChannel) + if (currentChannel.Value != loadedDrawable.Channel) return; // Ensure the cached reference hasn't been removed from leaving the channel - if (!loadedChannels.ContainsKey(newChannel)) + if (!loadedChannels.ContainsKey(loadedDrawable.Channel)) return; currentChannelContainer.Clear(false); - currentChannelContainer.Add(loaded); + currentChannelContainer.Add(loadedDrawable); loading.Hide(); }); } From 5d3878a737ae957c359aff643285954172731a52 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 18 May 2022 22:58:39 +0300 Subject: [PATCH 073/395] Add test coverage for slow-loading channels --- .../Visual/Online/TestSceneChatOverlayV2.cs | 64 ++++++++++++++++++- osu.Game/Overlays/ChatOverlayV2.cs | 6 +- 2 files changed, 67 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneChatOverlayV2.cs b/osu.Game.Tests/Visual/Online/TestSceneChatOverlayV2.cs index 82089ff3d1..3e3c8122b3 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneChatOverlayV2.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneChatOverlayV2.cs @@ -5,6 +5,8 @@ using System; using System.Linq; using System.Collections.Generic; using System.Net; +using System.Threading; +using JetBrains.Annotations; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -31,7 +33,7 @@ namespace osu.Game.Tests.Visual.Online [TestFixture] public class TestSceneChatOverlayV2 : OsuManualInputManagerTestScene { - private ChatOverlayV2 chatOverlay; + private TestChatOverlayV2 chatOverlay; private ChannelManager channelManager; private APIUser testUser; @@ -61,7 +63,7 @@ namespace osu.Game.Tests.Visual.Online Children = new Drawable[] { channelManager, - chatOverlay = new ChatOverlayV2 { RelativeSizeAxes = Axes.Both }, + chatOverlay = new TestChatOverlayV2 { RelativeSizeAxes = Axes.Both }, }, }; }); @@ -386,6 +388,34 @@ namespace osu.Game.Tests.Visual.Online AddAssert("TextBox is not focused", () => InputManager.FocusedDrawable == null); } + [Test] + public void TestSlowLoadingChannel() + { + AddStep("Show overlay (slow-loading)", () => + { + chatOverlay.Show(); + chatOverlay.SlowLoading = true; + }); + AddStep("Join channel 1", () => channelManager.JoinChannel(testChannel1)); + AddAssert("Channel 1 loading", () => !channelIsVisible && chatOverlay.GetSlowLoadingChannel(testChannel1).LoadState == LoadState.Loading); + + AddStep("Join channel 2", () => channelManager.JoinChannel(testChannel2)); + AddStep("Select channel 2", () => clickDrawable(getChannelListItem(testChannel2))); + AddAssert("Channel 2 loading", () => !channelIsVisible && chatOverlay.GetSlowLoadingChannel(testChannel2).LoadState == LoadState.Loading); + + AddStep("Finish channel 1 load", () => chatOverlay.GetSlowLoadingChannel(testChannel1).LoadEvent.Set()); + AddAssert("Channel 1 ready", () => chatOverlay.GetSlowLoadingChannel(testChannel1).LoadState == LoadState.Ready); + AddAssert("Channel 1 not displayed", () => !channelIsVisible); + + AddStep("Finish channel 2 load", () => chatOverlay.GetSlowLoadingChannel(testChannel2).LoadEvent.Set()); + AddAssert("Channel 2 loaded", () => chatOverlay.GetSlowLoadingChannel(testChannel2).IsLoaded); + AddAssert("Channel 2 displayed", () => channelIsVisible && currentDrawableChannel.Channel == testChannel2); + + AddStep("Select channel 1", () => clickDrawable(getChannelListItem(testChannel1))); + AddAssert("Channel 1 loaded", () => chatOverlay.GetSlowLoadingChannel(testChannel1).IsLoaded); + AddAssert("Channel 1 displayed", () => channelIsVisible && currentDrawableChannel.Channel == testChannel1); + } + private bool listingIsVisible => chatOverlay.ChildrenOfType().Single().State.Value == Visibility.Visible; @@ -432,5 +462,35 @@ namespace osu.Game.Tests.Visual.Online Topic = $"We talk about the number {id} here", Type = ChannelType.Public, }; + + private class TestChatOverlayV2 : ChatOverlayV2 + { + public bool SlowLoading { get; set; } + + public SlowLoadingDrawableChannel GetSlowLoadingChannel(Channel channel) => DrawableChannels.OfType().Single(c => c.Channel == channel); + + protected override ChatOverlayDrawableChannel CreateDrawableChannel(Channel newChannel) + { + return SlowLoading + ? new SlowLoadingDrawableChannel(newChannel) + : new ChatOverlayDrawableChannel(newChannel); + } + } + + private class SlowLoadingDrawableChannel : ChatOverlayDrawableChannel + { + public readonly ManualResetEventSlim LoadEvent = new ManualResetEventSlim(); + + public SlowLoadingDrawableChannel([NotNull] Channel channel) + : base(channel) + { + } + + [BackgroundDependencyLoader] + private void load() + { + LoadEvent.Wait(10000); + } + } } } diff --git a/osu.Game/Overlays/ChatOverlayV2.cs b/osu.Game/Overlays/ChatOverlayV2.cs index 611c539f8f..b2c1f6858c 100644 --- a/osu.Game/Overlays/ChatOverlayV2.cs +++ b/osu.Game/Overlays/ChatOverlayV2.cs @@ -41,6 +41,8 @@ namespace osu.Game.Overlays private readonly Dictionary loadedChannels = new Dictionary(); + protected IEnumerable DrawableChannels => loadedChannels.Values; + private readonly BindableFloat chatHeight = new BindableFloat(); private bool isDraggingTopBar; private float dragStartChatHeight; @@ -260,7 +262,7 @@ namespace osu.Game.Overlays loading.Show(); // Ensure the drawable channel is stored before async load to prevent double loading - ChatOverlayDrawableChannel drawableChannel = new ChatOverlayDrawableChannel(newChannel); + ChatOverlayDrawableChannel drawableChannel = CreateDrawableChannel(newChannel); loadedChannels.Add(newChannel, drawableChannel); LoadComponentAsync(drawableChannel, loadedDrawable => @@ -281,6 +283,8 @@ namespace osu.Game.Overlays } } + protected virtual ChatOverlayDrawableChannel CreateDrawableChannel(Channel newChannel) => new ChatOverlayDrawableChannel(newChannel); + private void joinedChannelsChanged(object sender, NotifyCollectionChangedEventArgs args) { switch (args.Action) From 3bca014b52b2b7ea11b3b0ce491d0f0456854311 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 19 May 2022 09:44:11 +0900 Subject: [PATCH 074/395] Bust CI cache on CodeAnalysis ruleset changes --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 729f2f266d..1d5c565d60 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -31,7 +31,7 @@ jobs: uses: actions/cache@v3 with: path: ${{ github.workspace }}/inspectcode - key: inspectcode-${{ hashFiles('.config/dotnet-tools.json', '.github/workflows/ci.yml', 'osu.sln*', '.editorconfig', '.globalconfig') }} + key: inspectcode-${{ hashFiles('.config/dotnet-tools.json', '.github/workflows/ci.yml', 'osu.sln*', '.editorconfig', '.globalconfig', 'CodeAnalysis/*') }} - name: Dotnet code style run: dotnet build -c Debug -warnaserror osu.Desktop.slnf -p:EnforceCodeStyleInBuild=true From a443200d0d25c54a0e28a0691b9ada5a9bd2bab1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 19 May 2022 13:49:52 +0900 Subject: [PATCH 075/395] Make dependency nullable to allow for safer disposal unbinding --- .../Skinning/Legacy/LegacyMainCirclePiece.cs | 2 +- osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyMainCirclePiece.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyMainCirclePiece.cs index 391147648f..dd6226e19b 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyMainCirclePiece.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyMainCirclePiece.cs @@ -41,7 +41,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy private readonly Bindable accentColour = new Bindable(); private readonly IBindable indexInCurrentCombo = new Bindable(); - [Resolved(canBeNull: true)] + [Resolved(canBeNull: true)] // Can't really be null but required to handle potential of disposal before DI completes. private DrawableHitObject? drawableObject { get; set; } [Resolved] diff --git a/osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs b/osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs index cf8abf0a76..62b517d982 100644 --- a/osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs +++ b/osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs @@ -192,14 +192,14 @@ namespace osu.Game.Overlays.FirstRunSetup private readonly Bindable currentDirectory = new Bindable(); - [Resolved] - private OsuGameBase game { get; set; } = null!; + [Resolved(canBeNull: true)] // Can't really be null but required to handle potential of disposal before DI completes. + private OsuGameBase? game { get; set; } protected override void LoadComplete() { base.LoadComplete(); - game.RegisterImportHandler(this); + game?.RegisterImportHandler(this); currentDirectory.BindValueChanged(onDirectorySelected); @@ -242,7 +242,7 @@ namespace osu.Game.Overlays.FirstRunSetup protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); - game.UnregisterImportHandler(this); + game?.UnregisterImportHandler(this); } public override Popover GetPopover() => new DirectoryChooserPopover(currentDirectory); From f6fb8f3fb99be7352e40d4cd7b4df12e755349b7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 19 May 2022 13:54:44 +0900 Subject: [PATCH 076/395] Delete and ignore rider's `misc.xml` This file has changed on us too many times. Doesn't seem to contain anything of value. --- .gitignore | 2 ++ .idea/.idea.osu.Desktop/.idea/misc.xml | 11 ----------- 2 files changed, 2 insertions(+), 11 deletions(-) delete mode 100644 .idea/.idea.osu.Desktop/.idea/misc.xml diff --git a/.gitignore b/.gitignore index 5b19270ab9..0c7a18b437 100644 --- a/.gitignore +++ b/.gitignore @@ -340,3 +340,5 @@ inspectcode # Fody (pulled in by Realm) - schema file FodyWeavers.xsd **/FodyWeavers.xml + +.idea/.idea.osu.Desktop/.idea/misc.xml \ No newline at end of file diff --git a/.idea/.idea.osu.Desktop/.idea/misc.xml b/.idea/.idea.osu.Desktop/.idea/misc.xml deleted file mode 100644 index 4e1d56f4dd..0000000000 --- a/.idea/.idea.osu.Desktop/.idea/misc.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - \ No newline at end of file From 70bd40ce443cf7ece4df644dd013afe9f3842c89 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 19 May 2022 14:01:24 +0900 Subject: [PATCH 077/395] Fix incorrect count of beatmaps available to import --- osu.Game/Database/LegacyModelImporter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Database/LegacyModelImporter.cs b/osu.Game/Database/LegacyModelImporter.cs index 13a9025ece..9b2a54dada 100644 --- a/osu.Game/Database/LegacyModelImporter.cs +++ b/osu.Game/Database/LegacyModelImporter.cs @@ -40,7 +40,7 @@ namespace osu.Game.Database Importer = importer; } - public Task GetAvailableCount(StableStorage stableStorage) => Task.Run(() => GetStableImportPaths(stableStorage).Count()); + public Task GetAvailableCount(StableStorage stableStorage) => Task.Run(() => GetStableImportPaths(PrepareStableStorage(stableStorage)).Count()); public Task ImportFromStableAsync(StableStorage stableStorage) { From 5af7641e946939a15e7729f58ac76de4b6511269 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 19 May 2022 15:53:53 +0900 Subject: [PATCH 078/395] Add safety against playfield potentially not being available during mania note placement --- .../Blueprints/ManiaPlacementBlueprint.cs | 31 ++++++++++--------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaPlacementBlueprint.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaPlacementBlueprint.cs index ad95fd5e87..7a99565e8a 100644 --- a/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaPlacementBlueprint.cs @@ -55,24 +55,25 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints { base.UpdateTimeAndPosition(result); - var playfield = (Column)result.Playfield; - - // Apply an offset to better align with the visual grid. - // This should only be applied during placement, as during selection / drag operations the movement is relative - // to the initial point of interaction rather than the grid. - switch (playfield.ScrollingInfo.Direction.Value) + if (result.Playfield is Column col) { - case ScrollingDirection.Down: - result.ScreenSpacePosition -= new Vector2(0, getNoteHeight(playfield) / 2); - break; + // Apply an offset to better align with the visual grid. + // This should only be applied during placement, as during selection / drag operations the movement is relative + // to the initial point of interaction rather than the grid. + switch (col.ScrollingInfo.Direction.Value) + { + case ScrollingDirection.Down: + result.ScreenSpacePosition -= new Vector2(0, getNoteHeight(col) / 2); + break; - case ScrollingDirection.Up: - result.ScreenSpacePosition += new Vector2(0, getNoteHeight(playfield) / 2); - break; + case ScrollingDirection.Up: + result.ScreenSpacePosition += new Vector2(0, getNoteHeight(col) / 2); + break; + } + + if (PlacementActive == PlacementState.Waiting) + Column = col; } - - if (PlacementActive == PlacementState.Waiting) - Column = playfield; } private float getNoteHeight(Column resultPlayfield) => From b3d6f76cfacdca8ec3906c8f92d324b71f1e54fc Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 19 May 2022 15:57:17 +0900 Subject: [PATCH 079/395] Add "None" snap type to fix flags not working correctly --- osu.Game/Rulesets/Edit/SnapType.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/Edit/SnapType.cs b/osu.Game/Rulesets/Edit/SnapType.cs index 4ab67fee63..6761356331 100644 --- a/osu.Game/Rulesets/Edit/SnapType.cs +++ b/osu.Game/Rulesets/Edit/SnapType.cs @@ -8,8 +8,9 @@ namespace osu.Game.Rulesets.Edit [Flags] public enum SnapType { - NearbyObjects = 0, - Grids = 1, + None = 0, + NearbyObjects = 1 << 0, + Grids = 1 << 1, All = NearbyObjects | Grids, } } From 363e7a6f537b7feda0f8825192391d64e791a749 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 19 May 2022 16:42:43 +0900 Subject: [PATCH 080/395] Simplify toolbar hiding logic in `FirstRunSetupOverlay` Rather than fiddling around with the activation modes, this seems like a much cleaner way to make things work. Closes https://github.com/ppy/osu/issues/18277. --- osu.Game/Overlays/FirstRunSetupOverlay.cs | 22 +++------------------- osu.Game/Screens/Menu/MainMenu.cs | 2 ++ 2 files changed, 5 insertions(+), 19 deletions(-) diff --git a/osu.Game/Overlays/FirstRunSetupOverlay.cs b/osu.Game/Overlays/FirstRunSetupOverlay.cs index 890f17b013..7b0de4affe 100644 --- a/osu.Game/Overlays/FirstRunSetupOverlay.cs +++ b/osu.Game/Overlays/FirstRunSetupOverlay.cs @@ -62,8 +62,6 @@ namespace osu.Game.Overlays private Container screenContent = null!; - private Bindable? overlayActivationMode; - private Container content = null!; private LoadingSpinner loading = null!; @@ -225,16 +223,9 @@ namespace osu.Game.Overlays // if we are valid for display, only do so after reaching the main menu. performer.PerformFromScreen(screen => { - MainMenu menu = (MainMenu)screen; - - // Eventually I'd like to replace this with a better method that doesn't access the screen. - // Either this dialog would be converted to its own screen, or at very least be "hosted" by a screen pushed to the main menu. - // Alternatively, another method of disabling notifications could be added to `INotificationOverlay`. - if (menu != null) - { - overlayActivationMode = menu.OverlayActivationMode.GetBoundCopy(); - overlayActivationMode.Value = OverlayActivation.UserTriggered; - } + // Hides the toolbar for us. + if (screen is MainMenu menu) + menu.ReturnToOsuLogo(); base.Show(); }, new[] { typeof(MainMenu) }); @@ -257,13 +248,6 @@ namespace osu.Game.Overlays content.ScaleTo(0.99f, 400, Easing.OutQuint); - if (overlayActivationMode != null) - { - // If this is non-null we are guaranteed to have come from the main menu. - overlayActivationMode.Value = OverlayActivation.All; - overlayActivationMode = null; - } - if (currentStepIndex != null) { notificationOverlay.Post(new SimpleNotification diff --git a/osu.Game/Screens/Menu/MainMenu.cs b/osu.Game/Screens/Menu/MainMenu.cs index 4401ee93ec..6fc8039413 100644 --- a/osu.Game/Screens/Menu/MainMenu.cs +++ b/osu.Game/Screens/Menu/MainMenu.cs @@ -150,6 +150,8 @@ namespace osu.Game.Screens.Menu [Resolved(canBeNull: true)] private IPerformFromScreenRunner performer { get; set; } + public void ReturnToOsuLogo() => Buttons.State = ButtonSystemState.Initial; + private void confirmAndExit() { if (exitConfirmed) return; From 63c97763658c490de30239d0b1e41799dbed9d2e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 19 May 2022 19:24:18 +0900 Subject: [PATCH 081/395] Fix oversight in bindable logic in new overlay class --- osu.Game/Overlays/ChatOverlayV2.cs | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/osu.Game/Overlays/ChatOverlayV2.cs b/osu.Game/Overlays/ChatOverlayV2.cs index 0307dcfdf3..42c928014a 100644 --- a/osu.Game/Overlays/ChatOverlayV2.cs +++ b/osu.Game/Overlays/ChatOverlayV2.cs @@ -62,6 +62,9 @@ namespace osu.Game.Overlays [Cached] private readonly Bindable currentChannel = new Bindable(); + private readonly IBindableList availableChannels = new BindableList(); + private readonly IBindableList joinedChannels = new BindableList(); + public ChatOverlayV2() { Height = default_chat_height; @@ -147,9 +150,13 @@ namespace osu.Game.Overlays chatHeight.BindValueChanged(height => { Height = height.NewValue; }, true); currentChannel.BindTo(channelManager.CurrentChannel); - channelManager.CurrentChannel.BindValueChanged(currentChannelChanged, true); - channelManager.JoinedChannels.BindCollectionChanged(joinedChannelsChanged, true); - channelManager.AvailableChannels.BindCollectionChanged(availableChannelsChanged, true); + currentChannel.BindValueChanged(currentChannelChanged, true); + + joinedChannels.BindTo(channelManager.JoinedChannels); + joinedChannels.BindCollectionChanged(joinedChannelsChanged, true); + + availableChannels.BindTo(channelManager.AvailableChannels); + availableChannels.BindCollectionChanged(availableChannelsChanged, true); channelList.OnRequestSelect += channel => channelManager.CurrentChannel.Value = channel; channelList.OnRequestLeave += channel => channelManager.LeaveChannel(channel); @@ -263,8 +270,8 @@ namespace osu.Game.Overlays switch (args.Action) { case NotifyCollectionChangedAction.Add: - IEnumerable joinedChannels = filterChannels(args.NewItems); - foreach (var channel in joinedChannels) + IEnumerable newChannels = filterChannels(args.NewItems); + foreach (var channel in newChannels) channelList.AddChannel(channel); break; From 136ecb45e241b6d0dd4358107ddb2d9b4299f3bf Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 19 May 2022 19:26:14 +0900 Subject: [PATCH 082/395] Rename dummy channel and move to a nested class inside the `ChannelListing` itself --- osu.Game.Tests/Visual/Online/TestSceneChannelList.cs | 3 ++- .../Visual/Online/TestSceneChatOverlayV2.cs | 2 +- osu.Game/Online/Chat/ChannelManager.cs | 4 ++-- osu.Game/Overlays/Chat/ChannelList/ChannelList.cs | 5 +++-- osu.Game/Overlays/Chat/ChannelList/ChannelListItem.cs | 3 ++- osu.Game/Overlays/Chat/Listing/ChannelListing.cs | 9 +++++++++ osu.Game/Overlays/ChatOverlayV2.cs | 11 +---------- 7 files changed, 20 insertions(+), 17 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneChannelList.cs b/osu.Game.Tests/Visual/Online/TestSceneChannelList.cs index 53a48fcc58..e4bc5645b6 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneChannelList.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneChannelList.cs @@ -13,6 +13,7 @@ using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Chat; using osu.Game.Overlays; using osu.Game.Overlays.Chat.ChannelList; +using osu.Game.Overlays.Chat.Listing; namespace osu.Game.Tests.Visual.Online { @@ -147,7 +148,7 @@ namespace osu.Game.Tests.Visual.Online }); } - private bool validItem => selected.Value != null && !(selected.Value is DummySelectorChannel); + private bool validItem => selected.Value != null && !(selected.Value is ChannelListing.ChannelListingChannel); private Channel createRandomPublicChannel() { diff --git a/osu.Game.Tests/Visual/Online/TestSceneChatOverlayV2.cs b/osu.Game.Tests/Visual/Online/TestSceneChatOverlayV2.cs index 48557a4ddb..3b8849f7c9 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneChatOverlayV2.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneChatOverlayV2.cs @@ -408,7 +408,7 @@ namespace osu.Game.Tests.Visual.Online chatOverlay.ChildrenOfType().Single(); private ChannelListItem channelSelectorButton => - chatOverlay.ChildrenOfType().Single(item => item.Channel is DummySelectorChannel); + chatOverlay.ChildrenOfType().Single(item => item.Channel is ChannelListing.ChannelListingChannel); private void clickDrawable(Drawable d) { diff --git a/osu.Game/Online/Chat/ChannelManager.cs b/osu.Game/Online/Chat/ChannelManager.cs index 9e85067f15..b7d67de04d 100644 --- a/osu.Game/Online/Chat/ChannelManager.cs +++ b/osu.Game/Online/Chat/ChannelManager.cs @@ -14,7 +14,7 @@ using osu.Game.Input; using osu.Game.Online.API; using osu.Game.Online.API.Requests; using osu.Game.Online.API.Requests.Responses; -using osu.Game.Overlays; +using osu.Game.Overlays.Chat.Listing; using osu.Game.Overlays.Chat.Tabs; namespace osu.Game.Online.Chat @@ -134,7 +134,7 @@ namespace osu.Game.Online.Chat private void currentChannelChanged(ValueChangedEvent e) { - bool isSelectorChannel = e.NewValue is ChannelSelectorTabItem.ChannelSelectorTabChannel || e.NewValue is DummySelectorChannel; + bool isSelectorChannel = e.NewValue is ChannelSelectorTabItem.ChannelSelectorTabChannel || e.NewValue is ChannelListing.ChannelListingChannel; if (!isSelectorChannel) JoinChannel(e.NewValue); diff --git a/osu.Game/Overlays/Chat/ChannelList/ChannelList.cs b/osu.Game/Overlays/Chat/ChannelList/ChannelList.cs index c9f3a4d380..555b54155c 100644 --- a/osu.Game/Overlays/Chat/ChannelList/ChannelList.cs +++ b/osu.Game/Overlays/Chat/ChannelList/ChannelList.cs @@ -13,6 +13,7 @@ using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osu.Game.Online.Chat; +using osu.Game.Overlays.Chat.Listing; namespace osu.Game.Overlays.Chat.ChannelList { @@ -23,7 +24,7 @@ namespace osu.Game.Overlays.Chat.ChannelList private readonly Dictionary channelMap = new Dictionary(); - private readonly DummySelectorChannel dummySelectorChannel = new DummySelectorChannel(); + private readonly ChannelListing.ChannelListingChannel channelListingChannel = new ChannelListing.ChannelListingChannel(); private ChannelListItemFlow publicChannelFlow = null!; private ChannelListItemFlow privateChannelFlow = null!; @@ -53,7 +54,7 @@ namespace osu.Game.Overlays.Chat.ChannelList Children = new Drawable[] { publicChannelFlow = new ChannelListItemFlow("CHANNELS"), - selector = new ChannelListItem(dummySelectorChannel) + selector = new ChannelListItem(channelListingChannel) { Margin = new MarginPadding { Bottom = 10 }, }, diff --git a/osu.Game/Overlays/Chat/ChannelList/ChannelListItem.cs b/osu.Game/Overlays/Chat/ChannelList/ChannelListItem.cs index eb2bdfdf8a..2967d9df11 100644 --- a/osu.Game/Overlays/Chat/ChannelList/ChannelListItem.cs +++ b/osu.Game/Overlays/Chat/ChannelList/ChannelListItem.cs @@ -15,6 +15,7 @@ using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osu.Game.Online.Chat; +using osu.Game.Overlays.Chat.Listing; using osu.Game.Users.Drawables; using osuTK; @@ -191,6 +192,6 @@ namespace osu.Game.Overlays.Chat.ChannelList text.FadeColour(colourProvider.Light3, 200, Easing.OutQuint); } - private bool isSelector => Channel is DummySelectorChannel; + private bool isSelector => Channel is ChannelListing.ChannelListingChannel; } } diff --git a/osu.Game/Overlays/Chat/Listing/ChannelListing.cs b/osu.Game/Overlays/Chat/Listing/ChannelListing.cs index 732c78de15..8a5bc18cbf 100644 --- a/osu.Game/Overlays/Chat/Listing/ChannelListing.cs +++ b/osu.Game/Overlays/Chat/Listing/ChannelListing.cs @@ -75,5 +75,14 @@ namespace osu.Game.Overlays.Chat.Listing protected override void PopIn() => this.FadeIn(); protected override void PopOut() => this.FadeOut(); + + public class ChannelListingChannel : Channel + { + public ChannelListingChannel() + { + Name = "Add more channels"; + Type = ChannelType.System; + } + } } } diff --git a/osu.Game/Overlays/ChatOverlayV2.cs b/osu.Game/Overlays/ChatOverlayV2.cs index 42c928014a..202d70d99b 100644 --- a/osu.Game/Overlays/ChatOverlayV2.cs +++ b/osu.Game/Overlays/ChatOverlayV2.cs @@ -244,7 +244,7 @@ namespace osu.Game.Overlays { Channel? newChannel = channel.NewValue; - if (newChannel == null || newChannel is DummySelectorChannel) + if (newChannel == null || newChannel is ChannelListing.ChannelListingChannel) { // null channel denotes that we should be showing the listing. channelListing.State.Value = Visibility.Visible; @@ -300,13 +300,4 @@ namespace osu.Game.Overlays channelManager.PostMessage(message); } } - - public class DummySelectorChannel : Channel - { - public DummySelectorChannel() - { - Name = "Add more channels"; - Type = ChannelType.System; - } - } } From c37d1e4fae4fe827e7ae36b485407d280fd29ba5 Mon Sep 17 00:00:00 2001 From: Jai Sharma Date: Thu, 19 May 2022 11:45:39 +0100 Subject: [PATCH 083/395] Ensure current channel is set to the `ChannelListingChannel` when it becomes `null` --- osu.Game/Overlays/Chat/ChannelList/ChannelList.cs | 6 +++--- osu.Game/Overlays/Chat/ChannelList/ChannelListItem.cs | 2 +- osu.Game/Overlays/ChatOverlayV2.cs | 10 ++++++++-- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/osu.Game/Overlays/Chat/ChannelList/ChannelList.cs b/osu.Game/Overlays/Chat/ChannelList/ChannelList.cs index 555b54155c..d1ceae604c 100644 --- a/osu.Game/Overlays/Chat/ChannelList/ChannelList.cs +++ b/osu.Game/Overlays/Chat/ChannelList/ChannelList.cs @@ -22,9 +22,9 @@ namespace osu.Game.Overlays.Chat.ChannelList public Action? OnRequestSelect; public Action? OnRequestLeave; - private readonly Dictionary channelMap = new Dictionary(); + public readonly ChannelListing.ChannelListingChannel ChannelListingChannel = new ChannelListing.ChannelListingChannel(); - private readonly ChannelListing.ChannelListingChannel channelListingChannel = new ChannelListing.ChannelListingChannel(); + private readonly Dictionary channelMap = new Dictionary(); private ChannelListItemFlow publicChannelFlow = null!; private ChannelListItemFlow privateChannelFlow = null!; @@ -54,7 +54,7 @@ namespace osu.Game.Overlays.Chat.ChannelList Children = new Drawable[] { publicChannelFlow = new ChannelListItemFlow("CHANNELS"), - selector = new ChannelListItem(channelListingChannel) + selector = new ChannelListItem(ChannelListingChannel) { Margin = new MarginPadding { Bottom = 10 }, }, diff --git a/osu.Game/Overlays/Chat/ChannelList/ChannelListItem.cs b/osu.Game/Overlays/Chat/ChannelList/ChannelListItem.cs index 2967d9df11..9ab0c2792a 100644 --- a/osu.Game/Overlays/Chat/ChannelList/ChannelListItem.cs +++ b/osu.Game/Overlays/Chat/ChannelList/ChannelListItem.cs @@ -179,7 +179,7 @@ namespace osu.Game.Overlays.Chat.ChannelList private void updateState() { - bool selected = selectedChannel.Value == Channel || (isSelector && selectedChannel.Value == null); + bool selected = selectedChannel.Value == Channel; if (selected) selectBox.FadeIn(300, Easing.OutQuint); diff --git a/osu.Game/Overlays/ChatOverlayV2.cs b/osu.Game/Overlays/ChatOverlayV2.cs index 202d70d99b..fe3f8ba8cb 100644 --- a/osu.Game/Overlays/ChatOverlayV2.cs +++ b/osu.Game/Overlays/ChatOverlayV2.cs @@ -244,9 +244,15 @@ namespace osu.Game.Overlays { Channel? newChannel = channel.NewValue; - if (newChannel == null || newChannel is ChannelListing.ChannelListingChannel) + // null channel denotes that we should be showing the listing. + if (newChannel == null) + { + currentChannel.Value = channelList.ChannelListingChannel; + return; + } + + if (newChannel is ChannelListing.ChannelListingChannel) { - // null channel denotes that we should be showing the listing. channelListing.State.Value = Visibility.Visible; textBar.ShowSearch.Value = true; } From 3fdff7bbcb5a6f17a736f8e71e59a1fdacf8b3a7 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 17 May 2022 15:11:22 +0900 Subject: [PATCH 084/395] Make Meh judgements not drain HP --- osu.Game/Rulesets/Judgements/Judgement.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Judgements/Judgement.cs b/osu.Game/Rulesets/Judgements/Judgement.cs index fd576e9b9f..4fe34e15fd 100644 --- a/osu.Game/Rulesets/Judgements/Judgement.cs +++ b/osu.Game/Rulesets/Judgements/Judgement.cs @@ -103,7 +103,7 @@ namespace osu.Game.Rulesets.Judgements return -DEFAULT_MAX_HEALTH_INCREASE; case HitResult.Meh: - return -DEFAULT_MAX_HEALTH_INCREASE * 0.05; + return DEFAULT_MAX_HEALTH_INCREASE * 0.05; case HitResult.Ok: return DEFAULT_MAX_HEALTH_INCREASE * 0.5; From ce7be940e2dd4f220583318bdf623a63d0f6e4f9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 20 May 2022 14:34:33 +0900 Subject: [PATCH 085/395] Setup basics for control and test --- .../Editing/TestSceneTapTimingControl.cs | 94 +++++++++ .../UserInterfaceV2/LabelledDrawable.cs | 3 +- .../Screens/Edit/Timing/TapTimingControl.cs | 189 ++++++++++++++++++ osu.Game/Screens/Edit/Timing/TimingSection.cs | 2 + 4 files changed, 287 insertions(+), 1 deletion(-) create mode 100644 osu.Game.Tests/Visual/Editing/TestSceneTapTimingControl.cs create mode 100644 osu.Game/Screens/Edit/Timing/TapTimingControl.cs diff --git a/osu.Game.Tests/Visual/Editing/TestSceneTapTimingControl.cs b/osu.Game.Tests/Visual/Editing/TestSceneTapTimingControl.cs new file mode 100644 index 0000000000..c2889b2549 --- /dev/null +++ b/osu.Game.Tests/Visual/Editing/TestSceneTapTimingControl.cs @@ -0,0 +1,94 @@ +// 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 NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Testing; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Graphics.UserInterfaceV2; +using osu.Game.Overlays; +using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Osu; +using osu.Game.Screens.Edit; +using osu.Game.Screens.Edit.Timing; +using osuTK; + +namespace osu.Game.Tests.Visual.Editing +{ + [TestFixture] + public class TestSceneTapTimingControl : EditorClockTestScene + { + [Cached(typeof(EditorBeatmap))] + [Cached(typeof(IBeatSnapProvider))] + private readonly EditorBeatmap editorBeatmap; + + [Cached] + private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Blue); + + [Cached] + private Bindable selectedGroup = new Bindable(); + + private TapTimingControl control; + + public TestSceneTapTimingControl() + { + editorBeatmap = new EditorBeatmap(CreateBeatmap(new OsuRuleset().RulesetInfo)); + selectedGroup.Value = editorBeatmap.ControlPointInfo.Groups.First(); + } + + [Test] + public void TestTapThenReset() + { + AddStep("click tap button", () => + { + control.ChildrenOfType() + .First(b => b.Text == "Tap to beat") + .TriggerClick(); + }); + + AddUntilStep("wait for track playing", () => Clock.IsRunning); + + AddStep("click reset button", () => + { + control.ChildrenOfType() + .First(b => b.Text == "Reset") + .TriggerClick(); + }); + + AddUntilStep("wait for track stopped", () => !Clock.IsRunning); + } + + [Test] + public void TestBasic() + { + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + Beatmap.Value = CreateWorkingBeatmap(editorBeatmap.PlayableBeatmap); + Beatmap.Disabled = true; + + Child = new Container + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + AutoSizeAxes = Axes.Y, + Width = 400, + Scale = new Vector2(1.5f), + Child = control = new TapTimingControl(), + }; + } + + protected override void Dispose(bool isDisposing) + { + Beatmap.Disabled = false; + base.Dispose(isDisposing); + } + } +} diff --git a/osu.Game/Graphics/UserInterfaceV2/LabelledDrawable.cs b/osu.Game/Graphics/UserInterfaceV2/LabelledDrawable.cs index 1e6032c1d0..066e1a7978 100644 --- a/osu.Game/Graphics/UserInterfaceV2/LabelledDrawable.cs +++ b/osu.Game/Graphics/UserInterfaceV2/LabelledDrawable.cs @@ -41,7 +41,8 @@ namespace osu.Game.Graphics.UserInterfaceV2 protected const float CONTENT_PADDING_VERTICAL = 10; protected const float CONTENT_PADDING_HORIZONTAL = 15; - protected const float CORNER_RADIUS = 15; + + public const float CORNER_RADIUS = 15; /// /// The component that is being displayed. diff --git a/osu.Game/Screens/Edit/Timing/TapTimingControl.cs b/osu.Game/Screens/Edit/Timing/TapTimingControl.cs new file mode 100644 index 0000000000..3867a4bcec --- /dev/null +++ b/osu.Game/Screens/Edit/Timing/TapTimingControl.cs @@ -0,0 +1,189 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics; +using osu.Game.Graphics.Containers; +using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterfaceV2; +using osu.Game.Overlays; +using osuTK; + +namespace osu.Game.Screens.Edit.Timing +{ + public class TapTimingControl : CompositeDrawable + { + [Resolved] + private EditorClock editorClock { get; set; } + + [BackgroundDependencyLoader] + private void load(OverlayColourProvider colourProvider, OsuColour colours) + { + Height = 200; + RelativeSizeAxes = Axes.X; + + CornerRadius = LabelledDrawable.CORNER_RADIUS; + Masking = true; + + InternalChildren = new Drawable[] + { + new Box + { + Colour = colourProvider.Background4, + RelativeSizeAxes = Axes.Both, + }, + new GridContainer + { + RelativeSizeAxes = Axes.Both, + RowDimensions = new[] + { + new Dimension(), + new Dimension(GridSizeMode.Absolute, 60), + }, + Content = new[] + { + new Drawable[] + { + new Metronome() + }, + new Drawable[] + { + new Container + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding(10), + Children = new Drawable[] + { + new RoundedButton + { + Text = "Reset", + BackgroundColour = colours.Pink, + RelativeSizeAxes = Axes.X, + Width = 0.3f, + Action = reset, + }, + new RoundedButton + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + Text = "Tap to beat", + RelativeSizeAxes = Axes.X, + BackgroundColour = colourProvider.Background1, + Width = 0.68f, + Action = tap, + } + } + }, + } + } + }, + }; + } + + private void tap() + { + if (!editorClock.IsRunning) + { + editorClock.Seek(0); + editorClock.Start(); + } + } + + private void reset() + { + editorClock.Stop(); + } + + private class Metronome : BeatSyncedContainer + { + private Container swing; + private Box weight; + private OsuSpriteText bpm; + + [BackgroundDependencyLoader] + private void load(OverlayColourProvider overlayColourProvider) + { + Margin = new MarginPadding(10); + AutoSizeAxes = Axes.Both; + + InternalChildren = new Drawable[] + { + new Triangle + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(80, 120), + Colour = overlayColourProvider.Background1, + }, + new Circle + { + Y = -25, + Anchor = Anchor.BottomCentre, + Origin = Anchor.Centre, + Colour = overlayColourProvider.Content2, + Size = new Vector2(10) + }, + bpm = new OsuSpriteText + { + Colour = overlayColourProvider.Content1, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }, + swing = new Container + { + RelativeSizeAxes = Axes.Both, + Y = -25, + Height = 0.8f, + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + Children = new Drawable[] + { + new Box + { + Colour = overlayColourProvider.Content2, + RelativeSizeAxes = Axes.Y, + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + Width = 4, + }, + weight = new Box + { + Anchor = Anchor.TopCentre, + Origin = Anchor.Centre, + Colour = overlayColourProvider.Content2, + Size = new Vector2(15), + RelativePositionAxes = Axes.Y, + Y = -0.4f, + }, + } + }, + }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + swing + .RotateTo(20, 500, Easing.InOutQuad) + .Then() + .RotateTo(-20, 500, Easing.InOutQuad) + .Loop(); + } + + protected override void Update() + { + base.Update(); + + if (CurrentTimingPoint == null) + return; + + weight.Y = Math.Clamp((float)CurrentTimingPoint.BPM / 480, 0, 0.95f); + bpm.Text = $"{CurrentTimingPoint.BPM:F0}"; + } + } + } +} diff --git a/osu.Game/Screens/Edit/Timing/TimingSection.cs b/osu.Game/Screens/Edit/Timing/TimingSection.cs index 13af04cd4b..be719d0cb6 100644 --- a/osu.Game/Screens/Edit/Timing/TimingSection.cs +++ b/osu.Game/Screens/Edit/Timing/TimingSection.cs @@ -19,6 +19,7 @@ namespace osu.Game.Screens.Edit.Timing { Flow.AddRange(new Drawable[] { + new TapTimingControl(), bpmTextEntry = new BPMTextBox(), timeSignature = new LabelledTimeSignature { @@ -96,5 +97,6 @@ namespace osu.Game.Screens.Edit.Timing } private static double beatLengthToBpm(double beatLength) => 60000 / beatLength; + } } From 42179568f3aca7d67c574ae615dcf8c65920eae3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 20 May 2022 15:56:39 +0900 Subject: [PATCH 086/395] Initial animation pass on metronome --- .../Editing/TestSceneTapTimingControl.cs | 58 +++++++--- .../Screens/Edit/Timing/TapTimingControl.cs | 105 +++++++++++++----- osu.Game/Screens/Edit/Timing/TimingSection.cs | 1 - 3 files changed, 117 insertions(+), 47 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneTapTimingControl.cs b/osu.Game.Tests/Visual/Editing/TestSceneTapTimingControl.cs index c2889b2549..9b2618df5b 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneTapTimingControl.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneTapTimingControl.cs @@ -36,10 +36,37 @@ namespace osu.Game.Tests.Visual.Editing public TestSceneTapTimingControl() { - editorBeatmap = new EditorBeatmap(CreateBeatmap(new OsuRuleset().RulesetInfo)); + var playableBeatmap = CreateBeatmap(new OsuRuleset().RulesetInfo); + + // Ensure time doesn't end while testing + playableBeatmap.BeatmapInfo.Length = 1200000; + + editorBeatmap = new EditorBeatmap(playableBeatmap); + selectedGroup.Value = editorBeatmap.ControlPointInfo.Groups.First(); } + protected override void LoadComplete() + { + base.LoadComplete(); + + Beatmap.Value = CreateWorkingBeatmap(editorBeatmap.PlayableBeatmap); + Beatmap.Disabled = true; + + Children = new Drawable[] + { + new Container + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + AutoSizeAxes = Axes.Y, + Width = 400, + Scale = new Vector2(1.5f), + Child = control = new TapTimingControl(), + } + }; + } + [Test] public void TestTapThenReset() { @@ -65,24 +92,19 @@ namespace osu.Game.Tests.Visual.Editing [Test] public void TestBasic() { - } - - protected override void LoadComplete() - { - base.LoadComplete(); - - Beatmap.Value = CreateWorkingBeatmap(editorBeatmap.PlayableBeatmap); - Beatmap.Disabled = true; - - Child = new Container + AddStep("set low bpm", () => { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - AutoSizeAxes = Axes.Y, - Width = 400, - Scale = new Vector2(1.5f), - Child = control = new TapTimingControl(), - }; + editorBeatmap.ControlPointInfo.TimingPoints.First().BeatLength = 1000; + }); + + AddStep("click tap button", () => + { + control.ChildrenOfType() + .First(b => b.Text == "Tap to beat") + .TriggerClick(); + }); + + AddSliderStep("BPM", 30, 400, 60, bpm => editorBeatmap.ControlPointInfo.TimingPoints.First().BeatLength = 60000f / bpm); } protected override void Dispose(bool isDisposing) diff --git a/osu.Game/Screens/Edit/Timing/TapTimingControl.cs b/osu.Game/Screens/Edit/Timing/TapTimingControl.cs index 3867a4bcec..e23982ddc7 100644 --- a/osu.Game/Screens/Edit/Timing/TapTimingControl.cs +++ b/osu.Game/Screens/Edit/Timing/TapTimingControl.cs @@ -3,15 +3,19 @@ using System; using osu.Framework.Allocation; +using osu.Framework.Audio.Track; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; +using osu.Framework.Utils; +using osu.Game.Beatmaps.ControlPoints; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterfaceV2; using osu.Game.Overlays; using osuTK; +using osuTK.Graphics; namespace osu.Game.Screens.Edit.Timing { @@ -103,9 +107,13 @@ namespace osu.Game.Screens.Edit.Timing private Container swing; private Box weight; private OsuSpriteText bpm; + private Box stick; + + [Resolved] + private OverlayColourProvider overlayColourProvider { get; set; } [BackgroundDependencyLoader] - private void load(OverlayColourProvider overlayColourProvider) + private void load() { Margin = new MarginPadding(10); AutoSizeAxes = Axes.Both; @@ -117,21 +125,13 @@ namespace osu.Game.Screens.Edit.Timing Anchor = Anchor.Centre, Origin = Anchor.Centre, Size = new Vector2(80, 120), - Colour = overlayColourProvider.Background1, - }, - new Circle - { - Y = -25, - Anchor = Anchor.BottomCentre, - Origin = Anchor.Centre, - Colour = overlayColourProvider.Content2, - Size = new Vector2(10) + Colour = overlayColourProvider.Background2, }, bpm = new OsuSpriteText { Colour = overlayColourProvider.Content1, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, }, swing = new Container { @@ -142,10 +142,10 @@ namespace osu.Game.Screens.Edit.Timing Origin = Anchor.BottomCentre, Children = new Drawable[] { - new Box + stick = new Box { - Colour = overlayColourProvider.Content2, RelativeSizeAxes = Axes.Y, + Colour = overlayColourProvider.Colour2, Anchor = Anchor.BottomCentre, Origin = Anchor.BottomCentre, Width = 4, @@ -154,35 +154,84 @@ namespace osu.Game.Screens.Edit.Timing { Anchor = Anchor.TopCentre, Origin = Anchor.Centre, - Colour = overlayColourProvider.Content2, + Colour = overlayColourProvider.Colour1, Size = new Vector2(15), RelativePositionAxes = Axes.Y, - Y = -0.4f, + Y = 0.4f, }, } }, + new Circle + { + Y = -25, + Anchor = Anchor.BottomCentre, + Origin = Anchor.Centre, + Colour = overlayColourProvider.Colour0, + Size = new Vector2(10) + }, }; } - protected override void LoadComplete() - { - base.LoadComplete(); - swing - .RotateTo(20, 500, Easing.InOutQuad) - .Then() - .RotateTo(-20, 500, Easing.InOutQuad) - .Loop(); - } + private double beatLength; + + private TimingControlPoint timingPoint; + + private float bpmRatio; + private bool isSwinging; protected override void Update() { base.Update(); - if (CurrentTimingPoint == null) + timingPoint = Beatmap.Value.Beatmap.ControlPointInfo.TimingPointAt(BeatSyncClock.CurrentTime); + + if (beatLength != timingPoint.BeatLength) + { + beatLength = timingPoint.BeatLength; + bpm.Text = $"{timingPoint.BPM:F0}"; + + EarlyActivationMilliseconds = timingPoint.BeatLength / 2; + + bpmRatio = (float)Interpolation.ApplyEasing(Easing.OutQuad, Math.Clamp((timingPoint.BPM - 30) / 480, 0, 1)); + + weight.MoveToY((float)Interpolation.Lerp(0, 0.9f, bpmRatio), 600, Easing.OutQuint); + } + + if (BeatSyncClock?.IsRunning != true && isSwinging) + { + swing.ClearTransforms(true); + + using (swing.BeginDelayedSequence(350)) + { + swing.RotateTo(0, 1000, Easing.OutQuint); + stick.FadeColour(overlayColourProvider.Colour2, 1000, Easing.OutQuint); + } + + isSwinging = false; + } + } + + protected override void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, ChannelAmplitudes amplitudes) + { + base.OnNewBeat(beatIndex, timingPoint, effectPoint, amplitudes); + + float angle = (float)Interpolation.Lerp(25, 4, bpmRatio); + + if (!IsBeatSyncedWithTrack) return; - weight.Y = Math.Clamp((float)CurrentTimingPoint.BPM / 480, 0, 0.95f); - bpm.Text = $"{CurrentTimingPoint.BPM:F0}"; + isSwinging = true; + + float currentAngle = swing.Rotation; + float targetAngle = currentAngle > 0 ? -angle : angle; + + swing.RotateTo(targetAngle, beatLength, Easing.InOutQuad); + + if (currentAngle != 0 && Math.Abs(currentAngle - targetAngle) > angle * 1.8f && isSwinging) + { + using (stick.BeginDelayedSequence(beatLength / 2)) + stick.FlashColour(overlayColourProvider.Content1, beatLength, Easing.OutQuint); + } } } } diff --git a/osu.Game/Screens/Edit/Timing/TimingSection.cs b/osu.Game/Screens/Edit/Timing/TimingSection.cs index be719d0cb6..a5abd96d72 100644 --- a/osu.Game/Screens/Edit/Timing/TimingSection.cs +++ b/osu.Game/Screens/Edit/Timing/TimingSection.cs @@ -97,6 +97,5 @@ namespace osu.Game.Screens.Edit.Timing } private static double beatLengthToBpm(double beatLength) => 60000 / beatLength; - } } From cf1ef28f72235a4f5d742e3d404ccfd94d2dcab1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 20 May 2022 16:22:16 +0900 Subject: [PATCH 087/395] Add taper and more correctly shaped weight --- .../Screens/Edit/Timing/TapTimingControl.cs | 91 ++++++++++++++----- 1 file changed, 70 insertions(+), 21 deletions(-) diff --git a/osu.Game/Screens/Edit/Timing/TapTimingControl.cs b/osu.Game/Screens/Edit/Timing/TapTimingControl.cs index e23982ddc7..7507a7dd35 100644 --- a/osu.Game/Screens/Edit/Timing/TapTimingControl.cs +++ b/osu.Game/Screens/Edit/Timing/TapTimingControl.cs @@ -15,7 +15,6 @@ using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterfaceV2; using osu.Game.Overlays; using osuTK; -using osuTK.Graphics; namespace osu.Game.Screens.Edit.Timing { @@ -105,9 +104,11 @@ namespace osu.Game.Screens.Edit.Timing private class Metronome : BeatSyncedContainer { private Container swing; - private Box weight; + private OsuSpriteText bpm; - private Box stick; + + private Drawable weight; + private Drawable stick; [Resolved] private OverlayColourProvider overlayColourProvider { get; set; } @@ -116,33 +117,41 @@ namespace osu.Game.Screens.Edit.Timing private void load() { Margin = new MarginPadding(10); + + const float taper = 10; + + var triangleSize = new Vector2(80, 120); + AutoSizeAxes = Axes.Both; + const float stick_vertical_offset = -23; + InternalChildren = new Drawable[] { - new Triangle + new Container { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Size = new Vector2(80, 120), - Colour = overlayColourProvider.Background2, - }, - bpm = new OsuSpriteText - { - Colour = overlayColourProvider.Content1, + Masking = true, Anchor = Anchor.BottomCentre, Origin = Anchor.BottomCentre, + Size = new Vector2(triangleSize.X * 1.2f, triangleSize.Y - taper), + Child = new Triangle + { + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + Size = triangleSize, + Colour = overlayColourProvider.Background3, + }, }, swing = new Container { RelativeSizeAxes = Axes.Both, - Y = -25, + Y = stick_vertical_offset, Height = 0.8f, Anchor = Anchor.BottomCentre, Origin = Anchor.BottomCentre, - Children = new Drawable[] + Children = new[] { - stick = new Box + stick = new Circle { RelativeSizeAxes = Axes.Y, Colour = overlayColourProvider.Colour2, @@ -150,24 +159,64 @@ namespace osu.Game.Screens.Edit.Timing Origin = Anchor.BottomCentre, Width = 4, }, - weight = new Box + weight = new Container { Anchor = Anchor.TopCentre, Origin = Anchor.Centre, Colour = overlayColourProvider.Colour1, - Size = new Vector2(15), + Size = new Vector2(10), + Rotation = 180, RelativePositionAxes = Axes.Y, Y = 0.4f, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Shear = new Vector2(0.2f, 0), + }, + new Box + { + RelativeSizeAxes = Axes.Both, + Shear = new Vector2(-0.2f, 0), + }, + } }, } }, new Circle { - Y = -25, + Y = stick_vertical_offset, Anchor = Anchor.BottomCentre, Origin = Anchor.Centre, Colour = overlayColourProvider.Colour0, - Size = new Vector2(10) + Size = new Vector2(8) + }, + new Container + { + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + RelativeSizeAxes = Axes.Both, + Masking = true, + Height = 0.3f, + Children = new Drawable[] + { + new Triangle + { + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + Size = triangleSize, + Colour = overlayColourProvider.Background2, + Alpha = 0.8f + }, + } + }, + bpm = new OsuSpriteText + { + Colour = overlayColourProvider.Content1, + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + Y = -3, }, }; } @@ -194,7 +243,7 @@ namespace osu.Game.Screens.Edit.Timing bpmRatio = (float)Interpolation.ApplyEasing(Easing.OutQuad, Math.Clamp((timingPoint.BPM - 30) / 480, 0, 1)); - weight.MoveToY((float)Interpolation.Lerp(0, 0.9f, bpmRatio), 600, Easing.OutQuint); + weight.MoveToY((float)Interpolation.Lerp(0.07f, 0.9f, bpmRatio), 600, Easing.OutQuint); } if (BeatSyncClock?.IsRunning != true && isSwinging) @@ -215,7 +264,7 @@ namespace osu.Game.Screens.Edit.Timing { base.OnNewBeat(beatIndex, timingPoint, effectPoint, amplitudes); - float angle = (float)Interpolation.Lerp(25, 4, bpmRatio); + float angle = (float)Interpolation.Lerp(30, 5, bpmRatio); if (!IsBeatSyncedWithTrack) return; From 79878a4ec21cab922d4ed10295fa0f9eac8ecda2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 20 May 2022 16:25:24 +0900 Subject: [PATCH 088/395] Metrics adjust and labelling --- .../Screens/Edit/Timing/TapTimingControl.cs | 25 ++++++++++++------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/osu.Game/Screens/Edit/Timing/TapTimingControl.cs b/osu.Game/Screens/Edit/Timing/TapTimingControl.cs index 7507a7dd35..3aff74d359 100644 --- a/osu.Game/Screens/Edit/Timing/TapTimingControl.cs +++ b/osu.Game/Screens/Edit/Timing/TapTimingControl.cs @@ -116,26 +116,27 @@ namespace osu.Game.Screens.Edit.Timing [BackgroundDependencyLoader] private void load() { + const float taper = 30; + const float swing_vertical_offset = -23; + + var triangleSize = new Vector2(90, 120 + taper); + Margin = new MarginPadding(10); - const float taper = 10; - - var triangleSize = new Vector2(80, 120); - AutoSizeAxes = Axes.Both; - const float stick_vertical_offset = -23; - InternalChildren = new Drawable[] { new Container { + Name = @"Taper adjust", Masking = true, Anchor = Anchor.BottomCentre, Origin = Anchor.BottomCentre, Size = new Vector2(triangleSize.X * 1.2f, triangleSize.Y - taper), Child = new Triangle { + Name = @"Main body", Anchor = Anchor.BottomCentre, Origin = Anchor.BottomCentre, Size = triangleSize, @@ -144,15 +145,17 @@ namespace osu.Game.Screens.Edit.Timing }, swing = new Container { + Name = @"Swing", RelativeSizeAxes = Axes.Both, - Y = stick_vertical_offset, - Height = 0.8f, + Y = swing_vertical_offset, + Height = 0.80f, Anchor = Anchor.BottomCentre, Origin = Anchor.BottomCentre, Children = new[] { stick = new Circle { + Name = @"Stick", RelativeSizeAxes = Axes.Y, Colour = overlayColourProvider.Colour2, Anchor = Anchor.BottomCentre, @@ -161,6 +164,7 @@ namespace osu.Game.Screens.Edit.Timing }, weight = new Container { + Name = @"Weight", Anchor = Anchor.TopCentre, Origin = Anchor.Centre, Colour = overlayColourProvider.Colour1, @@ -186,7 +190,8 @@ namespace osu.Game.Screens.Edit.Timing }, new Circle { - Y = stick_vertical_offset, + Name = @"Swing connection point", + Y = swing_vertical_offset, Anchor = Anchor.BottomCentre, Origin = Anchor.Centre, Colour = overlayColourProvider.Colour0, @@ -194,6 +199,7 @@ namespace osu.Game.Screens.Edit.Timing }, new Container { + Name = @"Lower cover", Anchor = Anchor.BottomCentre, Origin = Anchor.BottomCentre, RelativeSizeAxes = Axes.Both, @@ -213,6 +219,7 @@ namespace osu.Game.Screens.Edit.Timing }, bpm = new OsuSpriteText { + Name = @"BPM display", Colour = overlayColourProvider.Content1, Anchor = Anchor.BottomCentre, Origin = Anchor.BottomCentre, From 21072a26ef30547bbbee7b802bcd918caac4296c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 20 May 2022 16:33:27 +0900 Subject: [PATCH 089/395] Add locking wedge --- .../Screens/Edit/Timing/TapTimingControl.cs | 36 +++++++++++++++---- 1 file changed, 29 insertions(+), 7 deletions(-) diff --git a/osu.Game/Screens/Edit/Timing/TapTimingControl.cs b/osu.Game/Screens/Edit/Timing/TapTimingControl.cs index 3aff74d359..323561f0a8 100644 --- a/osu.Game/Screens/Edit/Timing/TapTimingControl.cs +++ b/osu.Game/Screens/Edit/Timing/TapTimingControl.cs @@ -133,14 +133,17 @@ namespace osu.Game.Screens.Edit.Timing Masking = true, Anchor = Anchor.BottomCentre, Origin = Anchor.BottomCentre, - Size = new Vector2(triangleSize.X * 1.2f, triangleSize.Y - taper), - Child = new Triangle + Size = new Vector2(triangleSize.X, triangleSize.Y - taper), + Children = new Drawable[] { - Name = @"Main body", - Anchor = Anchor.BottomCentre, - Origin = Anchor.BottomCentre, - Size = triangleSize, - Colour = overlayColourProvider.Background3, + new Triangle + { + Name = @"Main body", + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + Size = triangleSize, + Colour = overlayColourProvider.Background3, + }, }, }, swing = new Container @@ -188,6 +191,25 @@ namespace osu.Game.Screens.Edit.Timing }, } }, + new Container + { + Name = @"Taper adjust", + Masking = true, + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + Size = new Vector2(triangleSize.X, triangleSize.Y - taper), + Children = new Drawable[] + { + new Circle + { + Name = @"Locking wedge", + Anchor = Anchor.TopCentre, + Origin = Anchor.Centre, + Colour = overlayColourProvider.Background1, + Size = new Vector2(8), + } + }, + }, new Circle { Name = @"Swing connection point", From 8487d2c48a41010245ba0b1a527ab821935520ac Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 20 May 2022 16:45:01 +0900 Subject: [PATCH 090/395] Interpolate bpm label and fix incorrect angle logic --- .../Screens/Edit/Timing/TapTimingControl.cs | 27 ++++++++++++------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/osu.Game/Screens/Edit/Timing/TapTimingControl.cs b/osu.Game/Screens/Edit/Timing/TapTimingControl.cs index 323561f0a8..4ab731c897 100644 --- a/osu.Game/Screens/Edit/Timing/TapTimingControl.cs +++ b/osu.Game/Screens/Edit/Timing/TapTimingControl.cs @@ -4,6 +4,7 @@ using System; using osu.Framework.Allocation; using osu.Framework.Audio.Track; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; @@ -105,7 +106,7 @@ namespace osu.Game.Screens.Edit.Timing { private Container swing; - private OsuSpriteText bpm; + private OsuSpriteText bpmText; private Drawable weight; private Drawable stick; @@ -116,7 +117,7 @@ namespace osu.Game.Screens.Edit.Timing [BackgroundDependencyLoader] private void load() { - const float taper = 30; + const float taper = 25; const float swing_vertical_offset = -23; var triangleSize = new Vector2(90, 120 + taper); @@ -226,7 +227,7 @@ namespace osu.Game.Screens.Edit.Timing Origin = Anchor.BottomCentre, RelativeSizeAxes = Axes.Both, Masking = true, - Height = 0.3f, + Height = 0.28f, Children = new Drawable[] { new Triangle @@ -239,7 +240,7 @@ namespace osu.Game.Screens.Edit.Timing }, } }, - bpm = new OsuSpriteText + bpmText = new OsuSpriteText { Name = @"BPM display", Colour = overlayColourProvider.Content1, @@ -254,9 +255,17 @@ namespace osu.Game.Screens.Edit.Timing private TimingControlPoint timingPoint; - private float bpmRatio; private bool isSwinging; + private readonly BindableInt interpolatedBpm = new BindableInt(); + + protected override void LoadComplete() + { + base.LoadComplete(); + + interpolatedBpm.BindValueChanged(bpm => bpmText.Text = bpm.NewValue.ToString()); + } + protected override void Update() { base.Update(); @@ -266,13 +275,13 @@ namespace osu.Game.Screens.Edit.Timing if (beatLength != timingPoint.BeatLength) { beatLength = timingPoint.BeatLength; - bpm.Text = $"{timingPoint.BPM:F0}"; EarlyActivationMilliseconds = timingPoint.BeatLength / 2; - bpmRatio = (float)Interpolation.ApplyEasing(Easing.OutQuad, Math.Clamp((timingPoint.BPM - 30) / 480, 0, 1)); + float bpmRatio = (float)Interpolation.ApplyEasing(Easing.OutQuad, Math.Clamp((timingPoint.BPM - 30) / 480, 0, 1)); - weight.MoveToY((float)Interpolation.Lerp(0.07f, 0.9f, bpmRatio), 600, Easing.OutQuint); + weight.MoveToY((float)Interpolation.Lerp(0.1f, 0.83f, bpmRatio), 600, Easing.OutQuint); + this.TransformBindableTo(interpolatedBpm, (int)timingPoint.BPM, 600, Easing.OutQuint); } if (BeatSyncClock?.IsRunning != true && isSwinging) @@ -293,7 +302,7 @@ namespace osu.Game.Screens.Edit.Timing { base.OnNewBeat(beatIndex, timingPoint, effectPoint, amplitudes); - float angle = (float)Interpolation.Lerp(30, 5, bpmRatio); + const float angle = 27.5f; if (!IsBeatSyncedWithTrack) return; From 4712e512d71810e0be81428275382372eca91fac Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 20 May 2022 16:48:07 +0900 Subject: [PATCH 091/395] Apply edge smoothness and make weight more accented --- osu.Game/Screens/Edit/Timing/TapTimingControl.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Timing/TapTimingControl.cs b/osu.Game/Screens/Edit/Timing/TapTimingControl.cs index 4ab731c897..bdab427c58 100644 --- a/osu.Game/Screens/Edit/Timing/TapTimingControl.cs +++ b/osu.Game/Screens/Edit/Timing/TapTimingControl.cs @@ -140,6 +140,7 @@ namespace osu.Game.Screens.Edit.Timing new Triangle { Name = @"Main body", + EdgeSmoothness = new Vector2(1), Anchor = Anchor.BottomCentre, Origin = Anchor.BottomCentre, Size = triangleSize, @@ -171,7 +172,7 @@ namespace osu.Game.Screens.Edit.Timing Name = @"Weight", Anchor = Anchor.TopCentre, Origin = Anchor.Centre, - Colour = overlayColourProvider.Colour1, + Colour = overlayColourProvider.Colour0, Size = new Vector2(10), Rotation = 180, RelativePositionAxes = Axes.Y, @@ -182,11 +183,13 @@ namespace osu.Game.Screens.Edit.Timing { RelativeSizeAxes = Axes.Both, Shear = new Vector2(0.2f, 0), + EdgeSmoothness = new Vector2(1), }, new Box { RelativeSizeAxes = Axes.Both, Shear = new Vector2(-0.2f, 0), + EdgeSmoothness = new Vector2(1), }, } }, @@ -236,6 +239,7 @@ namespace osu.Game.Screens.Edit.Timing Origin = Anchor.BottomCentre, Size = triangleSize, Colour = overlayColourProvider.Background2, + EdgeSmoothness = new Vector2(1), Alpha = 0.8f }, } From cf97f4e409aa57e7883b0a36d037ae588c4683b7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 20 May 2022 16:55:07 +0900 Subject: [PATCH 092/395] Add centre marker --- osu.Game/Screens/Edit/Timing/TapTimingControl.cs | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Edit/Timing/TapTimingControl.cs b/osu.Game/Screens/Edit/Timing/TapTimingControl.cs index bdab427c58..a99468fbff 100644 --- a/osu.Game/Screens/Edit/Timing/TapTimingControl.cs +++ b/osu.Game/Screens/Edit/Timing/TapTimingControl.cs @@ -119,6 +119,7 @@ namespace osu.Game.Screens.Edit.Timing { const float taper = 25; const float swing_vertical_offset = -23; + const float lower_cover_height = 32; var triangleSize = new Vector2(90, 120 + taper); @@ -148,6 +149,17 @@ namespace osu.Game.Screens.Edit.Timing }, }, }, + new Circle + { + Name = "Centre marker", + Colour = overlayColourProvider.Background5, + RelativeSizeAxes = Axes.Y, + Width = 2, + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + Y = -(lower_cover_height + 3), + Height = 0.65f, + }, swing = new Container { Name = @"Swing", @@ -228,9 +240,9 @@ namespace osu.Game.Screens.Edit.Timing Name = @"Lower cover", Anchor = Anchor.BottomCentre, Origin = Anchor.BottomCentre, - RelativeSizeAxes = Axes.Both, + RelativeSizeAxes = Axes.X, Masking = true, - Height = 0.28f, + Height = lower_cover_height, Children = new Drawable[] { new Triangle From 2e21d75b101d4aabb3a2dba05ef2e7306c6976ce Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 20 May 2022 17:12:35 +0900 Subject: [PATCH 093/395] Move metronome into own class and rename to avoid conflict with mod sounds --- .../Mods/TestSceneOsuModMuted.cs | 2 +- osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs | 2 +- .../Editing/TestSceneTapTimingControl.cs | 6 +- .../Mods/{Metronome.cs => MetronomeBeat.cs} | 4 +- osu.Game/Rulesets/Mods/ModMuted.cs | 6 +- .../Screens/Edit/Timing/MetronomeDisplay.cs | 255 +++++++++++++++++ .../Screens/Edit/Timing/TapTimingControl.cs | 261 +----------------- 7 files changed, 277 insertions(+), 259 deletions(-) rename osu.Game/Rulesets/Mods/{Metronome.cs => MetronomeBeat.cs} (95%) create mode 100644 osu.Game/Screens/Edit/Timing/MetronomeDisplay.cs diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModMuted.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModMuted.cs index c14dc78f38..e08d66fa31 100644 --- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModMuted.cs +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModMuted.cs @@ -22,7 +22,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods MuteComboCount = { Value = 0 }, }, PassCondition = () => Beatmap.Value.Track.AggregateVolume.Value == 0.0 && - Player.ChildrenOfType().SingleOrDefault()?.AggregateVolume.Value == 1.0, + Player.ChildrenOfType().SingleOrDefault()?.AggregateVolume.Value == 1.0, }); /// diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs b/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs index 5b121f4673..c58c624f5c 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs @@ -339,7 +339,7 @@ namespace osu.Game.Rulesets.Osu.Mods public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) { - drawableRuleset.Overlays.Add(new Metronome(drawableRuleset.Beatmap.HitObjects.First().StartTime)); + drawableRuleset.Overlays.Add(new MetronomeBeat(drawableRuleset.Beatmap.HitObjects.First().StartTime)); } #endregion diff --git a/osu.Game.Tests/Visual/Editing/TestSceneTapTimingControl.cs b/osu.Game.Tests/Visual/Editing/TestSceneTapTimingControl.cs index 9b2618df5b..de441995b5 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneTapTimingControl.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneTapTimingControl.cs @@ -73,7 +73,7 @@ namespace osu.Game.Tests.Visual.Editing AddStep("click tap button", () => { control.ChildrenOfType() - .First(b => b.Text == "Tap to beat") + .Last() .TriggerClick(); }); @@ -82,7 +82,7 @@ namespace osu.Game.Tests.Visual.Editing AddStep("click reset button", () => { control.ChildrenOfType() - .First(b => b.Text == "Reset") + .First() .TriggerClick(); }); @@ -100,7 +100,7 @@ namespace osu.Game.Tests.Visual.Editing AddStep("click tap button", () => { control.ChildrenOfType() - .First(b => b.Text == "Tap to beat") + .Last() .TriggerClick(); }); diff --git a/osu.Game/Rulesets/Mods/Metronome.cs b/osu.Game/Rulesets/Mods/MetronomeBeat.cs similarity index 95% rename from osu.Game/Rulesets/Mods/Metronome.cs rename to osu.Game/Rulesets/Mods/MetronomeBeat.cs index b85a341577..c7a8b02130 100644 --- a/osu.Game/Rulesets/Mods/Metronome.cs +++ b/osu.Game/Rulesets/Mods/MetronomeBeat.cs @@ -11,14 +11,14 @@ using osu.Game.Skinning; namespace osu.Game.Rulesets.Mods { - public class Metronome : BeatSyncedContainer, IAdjustableAudioComponent + public class MetronomeBeat : BeatSyncedContainer, IAdjustableAudioComponent { private readonly double firstHitTime; private readonly PausableSkinnableSound sample; /// Start time of the first hit object, used for providing a count down. - public Metronome(double firstHitTime) + public MetronomeBeat(double firstHitTime) { this.firstHitTime = firstHitTime; AllowMistimedEventFiring = false; diff --git a/osu.Game/Rulesets/Mods/ModMuted.cs b/osu.Game/Rulesets/Mods/ModMuted.cs index a7d3114f2b..84341faab7 100644 --- a/osu.Game/Rulesets/Mods/ModMuted.cs +++ b/osu.Game/Rulesets/Mods/ModMuted.cs @@ -79,11 +79,11 @@ namespace osu.Game.Rulesets.Mods { if (EnableMetronome.Value) { - Metronome metronome; + MetronomeBeat metronomeBeat; - drawableRuleset.Overlays.Add(metronome = new Metronome(drawableRuleset.Beatmap.HitObjects.First().StartTime)); + drawableRuleset.Overlays.Add(metronomeBeat = new MetronomeBeat(drawableRuleset.Beatmap.HitObjects.First().StartTime)); - metronome.AddAdjustment(AdjustableProperty.Volume, metronomeVolumeAdjust); + metronomeBeat.AddAdjustment(AdjustableProperty.Volume, metronomeVolumeAdjust); } if (AffectsHitSounds.Value) diff --git a/osu.Game/Screens/Edit/Timing/MetronomeDisplay.cs b/osu.Game/Screens/Edit/Timing/MetronomeDisplay.cs new file mode 100644 index 0000000000..fdd5bd1e4e --- /dev/null +++ b/osu.Game/Screens/Edit/Timing/MetronomeDisplay.cs @@ -0,0 +1,255 @@ +// 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.Audio.Track; +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.Beatmaps.ControlPoints; +using osu.Game.Graphics.Containers; +using osu.Game.Graphics.Sprites; +using osu.Game.Overlays; +using osuTK; + +namespace osu.Game.Screens.Edit.Timing +{ + public class MetronomeDisplay : BeatSyncedContainer + { + private Container swing; + + private OsuSpriteText bpmText; + + private Drawable weight; + private Drawable stick; + + [Resolved] + private OverlayColourProvider overlayColourProvider { get; set; } + + [BackgroundDependencyLoader] + private void load() + { + const float taper = 25; + const float swing_vertical_offset = -23; + const float lower_cover_height = 32; + + var triangleSize = new Vector2(90, 120 + taper); + + Margin = new MarginPadding(10); + + AutoSizeAxes = Axes.Both; + + InternalChildren = new Drawable[] + { + new Container + { + Name = @"Taper adjust", + Masking = true, + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + Size = new Vector2(triangleSize.X, triangleSize.Y - taper), + Children = new Drawable[] + { + new Triangle + { + Name = @"Main body", + EdgeSmoothness = new Vector2(1), + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + Size = triangleSize, + Colour = overlayColourProvider.Background3, + }, + }, + }, + new Circle + { + Name = "Centre marker", + Colour = overlayColourProvider.Background5, + RelativeSizeAxes = Axes.Y, + Width = 2, + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + Y = -(lower_cover_height + 3), + Height = 0.65f, + }, + swing = new Container + { + Name = @"Swing", + RelativeSizeAxes = Axes.Both, + Y = swing_vertical_offset, + Height = 0.80f, + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + Children = new[] + { + stick = new Circle + { + Name = @"Stick", + RelativeSizeAxes = Axes.Y, + Colour = overlayColourProvider.Colour2, + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + Width = 4, + }, + weight = new Container + { + Name = @"Weight", + Anchor = Anchor.TopCentre, + Origin = Anchor.Centre, + Colour = overlayColourProvider.Colour0, + Size = new Vector2(10), + Rotation = 180, + RelativePositionAxes = Axes.Y, + Y = 0.4f, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Shear = new Vector2(0.2f, 0), + EdgeSmoothness = new Vector2(1), + }, + new Box + { + RelativeSizeAxes = Axes.Both, + Shear = new Vector2(-0.2f, 0), + EdgeSmoothness = new Vector2(1), + }, + } + }, + } + }, + new Container + { + Name = @"Taper adjust", + Masking = true, + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + Size = new Vector2(triangleSize.X, triangleSize.Y - taper), + Children = new Drawable[] + { + new Circle + { + Name = @"Locking wedge", + Anchor = Anchor.TopCentre, + Origin = Anchor.Centre, + Colour = overlayColourProvider.Background1, + Size = new Vector2(8), + } + }, + }, + new Circle + { + Name = @"Swing connection point", + Y = swing_vertical_offset, + Anchor = Anchor.BottomCentre, + Origin = Anchor.Centre, + Colour = overlayColourProvider.Colour0, + Size = new Vector2(8) + }, + new Container + { + Name = @"Lower cover", + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + RelativeSizeAxes = Axes.X, + Masking = true, + Height = lower_cover_height, + Children = new Drawable[] + { + new Triangle + { + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + Size = triangleSize, + Colour = overlayColourProvider.Background2, + EdgeSmoothness = new Vector2(1), + Alpha = 0.8f + }, + } + }, + bpmText = new OsuSpriteText + { + Name = @"BPM display", + Colour = overlayColourProvider.Content1, + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + Y = -3, + }, + }; + } + + private double beatLength; + + private TimingControlPoint timingPoint; + + private bool isSwinging; + + private readonly BindableInt interpolatedBpm = new BindableInt(); + + protected override void LoadComplete() + { + base.LoadComplete(); + + interpolatedBpm.BindValueChanged(bpm => bpmText.Text = bpm.NewValue.ToString()); + } + + protected override void Update() + { + base.Update(); + + timingPoint = Beatmap.Value.Beatmap.ControlPointInfo.TimingPointAt(BeatSyncClock.CurrentTime); + + if (beatLength != timingPoint.BeatLength) + { + beatLength = timingPoint.BeatLength; + + EarlyActivationMilliseconds = timingPoint.BeatLength / 2; + + float bpmRatio = (float)Interpolation.ApplyEasing(Easing.OutQuad, Math.Clamp((timingPoint.BPM - 30) / 480, 0, 1)); + + weight.MoveToY((float)Interpolation.Lerp(0.1f, 0.83f, bpmRatio), 600, Easing.OutQuint); + this.TransformBindableTo(interpolatedBpm, (int)timingPoint.BPM, 600, Easing.OutQuint); + } + + if (BeatSyncClock?.IsRunning != true && isSwinging) + { + swing.ClearTransforms(true); + + using (swing.BeginDelayedSequence(350)) + { + swing.RotateTo(0, 1000, Easing.OutQuint); + stick.FadeColour(overlayColourProvider.Colour2, 1000, Easing.OutQuint); + } + + isSwinging = false; + } + } + + protected override void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, ChannelAmplitudes amplitudes) + { + base.OnNewBeat(beatIndex, timingPoint, effectPoint, amplitudes); + + const float angle = 27.5f; + + if (!IsBeatSyncedWithTrack) + return; + + isSwinging = true; + + float currentAngle = swing.Rotation; + float targetAngle = currentAngle > 0 ? -angle : angle; + + swing.RotateTo(targetAngle, beatLength, Easing.InOutQuad); + + if (currentAngle != 0 && Math.Abs(currentAngle - targetAngle) > angle * 1.8f && isSwinging) + { + using (stick.BeginDelayedSequence(beatLength / 2)) + stick.FlashColour(overlayColourProvider.Content1, beatLength, Easing.OutQuint); + } + } + } +} diff --git a/osu.Game/Screens/Edit/Timing/TapTimingControl.cs b/osu.Game/Screens/Edit/Timing/TapTimingControl.cs index a99468fbff..1b0f0a3f5e 100644 --- a/osu.Game/Screens/Edit/Timing/TapTimingControl.cs +++ b/osu.Game/Screens/Edit/Timing/TapTimingControl.cs @@ -1,21 +1,15 @@ // 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.Audio.Track; 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.Beatmaps.ControlPoints; using osu.Game.Graphics; -using osu.Game.Graphics.Containers; -using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterfaceV2; using osu.Game.Overlays; -using osuTK; namespace osu.Game.Screens.Edit.Timing { @@ -24,6 +18,9 @@ namespace osu.Game.Screens.Edit.Timing [Resolved] private EditorClock editorClock { get; set; } + [Resolved] + private Bindable selectedGroup { get; set; } + [BackgroundDependencyLoader] private void load(OverlayColourProvider colourProvider, OsuColour colours) { @@ -52,7 +49,11 @@ namespace osu.Game.Screens.Edit.Timing { new Drawable[] { - new Metronome() + new MetronomeDisplay + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + } }, new Drawable[] { @@ -74,7 +75,7 @@ namespace osu.Game.Screens.Edit.Timing { Anchor = Anchor.TopRight, Origin = Anchor.TopRight, - Text = "Tap to beat", + Text = "Play from start", RelativeSizeAxes = Axes.X, BackgroundColour = colourProvider.Background1, Width = 0.68f, @@ -90,252 +91,14 @@ namespace osu.Game.Screens.Edit.Timing private void tap() { - if (!editorClock.IsRunning) - { - editorClock.Seek(0); - editorClock.Start(); - } + editorClock.Seek(selectedGroup.Value.Time); + editorClock.Start(); } private void reset() { editorClock.Stop(); - } - - private class Metronome : BeatSyncedContainer - { - private Container swing; - - private OsuSpriteText bpmText; - - private Drawable weight; - private Drawable stick; - - [Resolved] - private OverlayColourProvider overlayColourProvider { get; set; } - - [BackgroundDependencyLoader] - private void load() - { - const float taper = 25; - const float swing_vertical_offset = -23; - const float lower_cover_height = 32; - - var triangleSize = new Vector2(90, 120 + taper); - - Margin = new MarginPadding(10); - - AutoSizeAxes = Axes.Both; - - InternalChildren = new Drawable[] - { - new Container - { - Name = @"Taper adjust", - Masking = true, - Anchor = Anchor.BottomCentre, - Origin = Anchor.BottomCentre, - Size = new Vector2(triangleSize.X, triangleSize.Y - taper), - Children = new Drawable[] - { - new Triangle - { - Name = @"Main body", - EdgeSmoothness = new Vector2(1), - Anchor = Anchor.BottomCentre, - Origin = Anchor.BottomCentre, - Size = triangleSize, - Colour = overlayColourProvider.Background3, - }, - }, - }, - new Circle - { - Name = "Centre marker", - Colour = overlayColourProvider.Background5, - RelativeSizeAxes = Axes.Y, - Width = 2, - Anchor = Anchor.BottomCentre, - Origin = Anchor.BottomCentre, - Y = -(lower_cover_height + 3), - Height = 0.65f, - }, - swing = new Container - { - Name = @"Swing", - RelativeSizeAxes = Axes.Both, - Y = swing_vertical_offset, - Height = 0.80f, - Anchor = Anchor.BottomCentre, - Origin = Anchor.BottomCentre, - Children = new[] - { - stick = new Circle - { - Name = @"Stick", - RelativeSizeAxes = Axes.Y, - Colour = overlayColourProvider.Colour2, - Anchor = Anchor.BottomCentre, - Origin = Anchor.BottomCentre, - Width = 4, - }, - weight = new Container - { - Name = @"Weight", - Anchor = Anchor.TopCentre, - Origin = Anchor.Centre, - Colour = overlayColourProvider.Colour0, - Size = new Vector2(10), - Rotation = 180, - RelativePositionAxes = Axes.Y, - Y = 0.4f, - Children = new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Shear = new Vector2(0.2f, 0), - EdgeSmoothness = new Vector2(1), - }, - new Box - { - RelativeSizeAxes = Axes.Both, - Shear = new Vector2(-0.2f, 0), - EdgeSmoothness = new Vector2(1), - }, - } - }, - } - }, - new Container - { - Name = @"Taper adjust", - Masking = true, - Anchor = Anchor.BottomCentre, - Origin = Anchor.BottomCentre, - Size = new Vector2(triangleSize.X, triangleSize.Y - taper), - Children = new Drawable[] - { - new Circle - { - Name = @"Locking wedge", - Anchor = Anchor.TopCentre, - Origin = Anchor.Centre, - Colour = overlayColourProvider.Background1, - Size = new Vector2(8), - } - }, - }, - new Circle - { - Name = @"Swing connection point", - Y = swing_vertical_offset, - Anchor = Anchor.BottomCentre, - Origin = Anchor.Centre, - Colour = overlayColourProvider.Colour0, - Size = new Vector2(8) - }, - new Container - { - Name = @"Lower cover", - Anchor = Anchor.BottomCentre, - Origin = Anchor.BottomCentre, - RelativeSizeAxes = Axes.X, - Masking = true, - Height = lower_cover_height, - Children = new Drawable[] - { - new Triangle - { - Anchor = Anchor.BottomCentre, - Origin = Anchor.BottomCentre, - Size = triangleSize, - Colour = overlayColourProvider.Background2, - EdgeSmoothness = new Vector2(1), - Alpha = 0.8f - }, - } - }, - bpmText = new OsuSpriteText - { - Name = @"BPM display", - Colour = overlayColourProvider.Content1, - Anchor = Anchor.BottomCentre, - Origin = Anchor.BottomCentre, - Y = -3, - }, - }; - } - - private double beatLength; - - private TimingControlPoint timingPoint; - - private bool isSwinging; - - private readonly BindableInt interpolatedBpm = new BindableInt(); - - protected override void LoadComplete() - { - base.LoadComplete(); - - interpolatedBpm.BindValueChanged(bpm => bpmText.Text = bpm.NewValue.ToString()); - } - - protected override void Update() - { - base.Update(); - - timingPoint = Beatmap.Value.Beatmap.ControlPointInfo.TimingPointAt(BeatSyncClock.CurrentTime); - - if (beatLength != timingPoint.BeatLength) - { - beatLength = timingPoint.BeatLength; - - EarlyActivationMilliseconds = timingPoint.BeatLength / 2; - - float bpmRatio = (float)Interpolation.ApplyEasing(Easing.OutQuad, Math.Clamp((timingPoint.BPM - 30) / 480, 0, 1)); - - weight.MoveToY((float)Interpolation.Lerp(0.1f, 0.83f, bpmRatio), 600, Easing.OutQuint); - this.TransformBindableTo(interpolatedBpm, (int)timingPoint.BPM, 600, Easing.OutQuint); - } - - if (BeatSyncClock?.IsRunning != true && isSwinging) - { - swing.ClearTransforms(true); - - using (swing.BeginDelayedSequence(350)) - { - swing.RotateTo(0, 1000, Easing.OutQuint); - stick.FadeColour(overlayColourProvider.Colour2, 1000, Easing.OutQuint); - } - - isSwinging = false; - } - } - - protected override void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, ChannelAmplitudes amplitudes) - { - base.OnNewBeat(beatIndex, timingPoint, effectPoint, amplitudes); - - const float angle = 27.5f; - - if (!IsBeatSyncedWithTrack) - return; - - isSwinging = true; - - float currentAngle = swing.Rotation; - float targetAngle = currentAngle > 0 ? -angle : angle; - - swing.RotateTo(targetAngle, beatLength, Easing.InOutQuad); - - if (currentAngle != 0 && Math.Abs(currentAngle - targetAngle) > angle * 1.8f && isSwinging) - { - using (stick.BeginDelayedSequence(beatLength / 2)) - stick.FlashColour(overlayColourProvider.Content1, beatLength, Easing.OutQuint); - } - } + editorClock.Seek(selectedGroup.Value.Time); } } } From 82eb5fd2c9007945d7432e22433ebeded94b7066 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 20 May 2022 17:21:56 +0900 Subject: [PATCH 094/395] Add centre line on weight --- osu.Game/Screens/Edit/Timing/MetronomeDisplay.cs | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Timing/MetronomeDisplay.cs b/osu.Game/Screens/Edit/Timing/MetronomeDisplay.cs index fdd5bd1e4e..5429165fe0 100644 --- a/osu.Game/Screens/Edit/Timing/MetronomeDisplay.cs +++ b/osu.Game/Screens/Edit/Timing/MetronomeDisplay.cs @@ -6,6 +6,7 @@ using osu.Framework.Allocation; using osu.Framework.Audio.Track; using osu.Framework.Bindables; using osu.Framework.Graphics; +using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Utils; @@ -99,7 +100,6 @@ namespace osu.Game.Screens.Edit.Timing Name = @"Weight", Anchor = Anchor.TopCentre, Origin = Anchor.Centre, - Colour = overlayColourProvider.Colour0, Size = new Vector2(10), Rotation = 180, RelativePositionAxes = Axes.Y, @@ -110,14 +110,25 @@ namespace osu.Game.Screens.Edit.Timing { RelativeSizeAxes = Axes.Both, Shear = new Vector2(0.2f, 0), + Colour = overlayColourProvider.Colour1, EdgeSmoothness = new Vector2(1), }, new Box { RelativeSizeAxes = Axes.Both, Shear = new Vector2(-0.2f, 0), + Colour = overlayColourProvider.Colour1, EdgeSmoothness = new Vector2(1), }, + new Circle + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Colour = ColourInfo.GradientVertical(overlayColourProvider.Colour1, overlayColourProvider.Colour0), + RelativeSizeAxes = Axes.Y, + Width = 1, + Height = 0.9f + }, } }, } From b29172ea45fea63d8e48b85aee346d9d607f0dfa Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 19 May 2022 19:55:51 +0900 Subject: [PATCH 095/395] Increase HP lost for misses --- osu.Game/Rulesets/Judgements/Judgement.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Judgements/Judgement.cs b/osu.Game/Rulesets/Judgements/Judgement.cs index 4fe34e15fd..99dce82ec2 100644 --- a/osu.Game/Rulesets/Judgements/Judgement.cs +++ b/osu.Game/Rulesets/Judgements/Judgement.cs @@ -100,7 +100,7 @@ namespace osu.Game.Rulesets.Judgements return -DEFAULT_MAX_HEALTH_INCREASE; case HitResult.Miss: - return -DEFAULT_MAX_HEALTH_INCREASE; + return -DEFAULT_MAX_HEALTH_INCREASE * 2; case HitResult.Meh: return DEFAULT_MAX_HEALTH_INCREASE * 0.05; From c78d90ccbdc92bb090c9840934ce70986c409a08 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 20 May 2022 14:43:07 +0300 Subject: [PATCH 096/395] Refactor track transferring logic for ability to override and disallow --- osu.Game/Beatmaps/WorkingBeatmap.cs | 16 ++++++++++++---- osu.Game/Overlays/MusicController.cs | 9 +-------- 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/osu.Game/Beatmaps/WorkingBeatmap.cs b/osu.Game/Beatmaps/WorkingBeatmap.cs index bb64ec796c..a1a8306b76 100644 --- a/osu.Game/Beatmaps/WorkingBeatmap.cs +++ b/osu.Game/Beatmaps/WorkingBeatmap.cs @@ -126,11 +126,19 @@ namespace osu.Game.Beatmaps } /// - /// Transfer a valid audio track into this working beatmap. Used as an optimisation to avoid reload / track swap - /// across difficulties in the same beatmap set. + /// Attempts to transfer the audio track to a target working beatmap, if valid for transferring. + /// Used as an optimisation to avoid reload / track swap across difficulties in the same beatmap set. /// - /// The track to transfer. - public void TransferTrack([NotNull] Track track) => this.track = track ?? throw new ArgumentNullException(nameof(track)); + /// The target working beatmap to transfer this track to. + /// Whether the track is valid and has been transferred to this working beatmap. + public virtual bool TryTransferTrack([NotNull] WorkingBeatmap target) + { + if (BeatmapInfo?.AudioEquals(target.BeatmapInfo) != true) + return false; + + target.track = track; + return true; + } /// /// Get the loaded audio track instance. must have first been called. diff --git a/osu.Game/Overlays/MusicController.cs b/osu.Game/Overlays/MusicController.cs index 65b06eb864..509da4302d 100644 --- a/osu.Game/Overlays/MusicController.cs +++ b/osu.Game/Overlays/MusicController.cs @@ -290,15 +290,8 @@ namespace osu.Game.Overlays current = newWorking; - if (!audioEquals || CurrentTrack.IsDummyDevice) - { + if (lastWorking == null || !lastWorking.TryTransferTrack(current)) changeTrack(); - } - else - { - // transfer still valid track to new working beatmap - current.TransferTrack(lastWorking.Track); - } TrackChanged?.Invoke(current, direction); From cef99fd0205ba17f515a95530adb2ad0a6296bed Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 20 May 2022 14:43:50 +0300 Subject: [PATCH 097/395] Disallow transferring track from test `WorkingBeatmap`s which have local stores --- osu.Game.Tests/WaveformTestBeatmap.cs | 7 +++++++ osu.Game/Tests/Visual/OsuTestScene.cs | 7 +++++++ 2 files changed, 14 insertions(+) diff --git a/osu.Game.Tests/WaveformTestBeatmap.cs b/osu.Game.Tests/WaveformTestBeatmap.cs index 9c85fa0c9c..ab7bf7fb73 100644 --- a/osu.Game.Tests/WaveformTestBeatmap.cs +++ b/osu.Game.Tests/WaveformTestBeatmap.cs @@ -59,6 +59,13 @@ namespace osu.Game.Tests protected override Track GetBeatmapTrack() => trackStore.Get(firstAudioFile); + public override bool TryTransferTrack(WorkingBeatmap target) + { + // Our track comes from a local track store that's disposed on finalizer, + // therefore it's unsafe to transfer it to another working beatmap. + return false; + } + private string firstAudioFile { get diff --git a/osu.Game/Tests/Visual/OsuTestScene.cs b/osu.Game/Tests/Visual/OsuTestScene.cs index f2d280417e..1582bdfca4 100644 --- a/osu.Game/Tests/Visual/OsuTestScene.cs +++ b/osu.Game/Tests/Visual/OsuTestScene.cs @@ -373,6 +373,13 @@ namespace osu.Game.Tests.Visual protected override Track GetBeatmapTrack() => track; + public override bool TryTransferTrack(WorkingBeatmap target) + { + // Our track comes from a local track store that's disposed on finalizer, + // therefore it's unsafe to transfer it to another working beatmap. + return false; + } + public class TrackVirtualStore : AudioCollectionManager, ITrackStore { private readonly IFrameBasedClock referenceClock; From abf9039109389dd8b869fed1c2778dedd0c76b5c Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 20 May 2022 14:44:07 +0300 Subject: [PATCH 098/395] Use `==` instead of `??` --- osu.Game/Overlays/MusicController.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/MusicController.cs b/osu.Game/Overlays/MusicController.cs index 509da4302d..aa09ff6b97 100644 --- a/osu.Game/Overlays/MusicController.cs +++ b/osu.Game/Overlays/MusicController.cs @@ -267,7 +267,7 @@ namespace osu.Game.Overlays TrackChangeDirection direction = TrackChangeDirection.None; - bool audioEquals = newWorking?.BeatmapInfo?.AudioEquals(current?.BeatmapInfo) ?? false; + bool audioEquals = newWorking?.BeatmapInfo?.AudioEquals(current?.BeatmapInfo) == true; if (current != null) { From c3bfbe888003f733137617e8629725bdadd1c506 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 20 May 2022 23:23:51 +0900 Subject: [PATCH 099/395] Allow `BeatSyncedContainer` to prefer `EditorBeatmap` when available --- .../TestSceneBeatSyncedContainer.cs | 19 +------------ .../Containers/BeatSyncedContainer.cs | 28 +++++++++++++------ 2 files changed, 20 insertions(+), 27 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneBeatSyncedContainer.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneBeatSyncedContainer.cs index ede89c6096..1881f6d718 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneBeatSyncedContainer.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneBeatSyncedContainer.cs @@ -258,24 +258,7 @@ namespace osu.Game.Tests.Visual.UserInterface }; } - protected override void LoadComplete() - { - base.LoadComplete(); - - Beatmap.BindValueChanged(_ => - { - timingPointCount.Value = 0; - currentTimingPoint.Value = 0; - beatCount.Value = 0; - currentBeat.Value = 0; - beatsPerMinute.Value = 0; - adjustedBeatLength.Value = 0; - timeUntilNextBeat.Value = 0; - timeSinceLastBeat.Value = 0; - }, true); - } - - private List timingPoints => Beatmap.Value.Beatmap.ControlPointInfo.TimingPoints.ToList(); + private List timingPoints => Beatmap.ControlPointInfo.TimingPoints.ToList(); private TimingControlPoint getNextTimingPoint(TimingControlPoint current) { diff --git a/osu.Game/Graphics/Containers/BeatSyncedContainer.cs b/osu.Game/Graphics/Containers/BeatSyncedContainer.cs index 2024d18570..48936f7aa9 100644 --- a/osu.Game/Graphics/Containers/BeatSyncedContainer.cs +++ b/osu.Game/Graphics/Containers/BeatSyncedContainer.cs @@ -10,6 +10,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Timing; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Screens.Edit; using osu.Game.Screens.Play; namespace osu.Game.Graphics.Containers @@ -79,20 +80,31 @@ namespace osu.Game.Graphics.Containers } [Resolved] - protected IBindable Beatmap { get; private set; } + private IBindable beatmap { get; set; } [Resolved(canBeNull: true)] protected GameplayClock GameplayClock { get; private set; } + [Resolved(canBeNull: true)] + protected EditorBeatmap EditorBeatmap { get; private set; } + + [Resolved(canBeNull: true)] + protected EditorClock EditorClock { get; private set; } + + protected IBeatmap Beatmap => EditorBeatmap ?? beatmap?.Value.Beatmap; + protected IClock BeatSyncClock { get { + if (EditorClock != null) + return EditorClock; + if (GameplayClock != null) return GameplayClock; - if (Beatmap.Value.TrackLoaded) - return Beatmap.Value.Track; + if (beatmap.Value.TrackLoaded) + return beatmap.Value.Track; return null; } @@ -101,7 +113,6 @@ namespace osu.Game.Graphics.Containers protected override void Update() { ITrack track = null; - IBeatmap beatmap = null; TimingControlPoint timingPoint; EffectControlPoint effectPoint; @@ -113,10 +124,9 @@ namespace osu.Game.Graphics.Containers double currentTrackTime = clock.CurrentTime + EarlyActivationMilliseconds; - if (Beatmap.Value.TrackLoaded && Beatmap.Value.BeatmapLoaded) + if (this.beatmap.Value.TrackLoaded && this.beatmap.Value.BeatmapLoaded) { - track = Beatmap.Value.Track; - beatmap = Beatmap.Value.Beatmap; + track = this.beatmap.Value.Track; } IsBeatSyncedWithTrack = beatmap != null && clock.IsRunning && track?.Length > 0; @@ -125,8 +135,8 @@ namespace osu.Game.Graphics.Containers { Debug.Assert(beatmap != null); - timingPoint = beatmap.ControlPointInfo.TimingPointAt(currentTrackTime); - effectPoint = beatmap.ControlPointInfo.EffectPointAt(currentTrackTime); + timingPoint = Beatmap.ControlPointInfo.TimingPointAt(currentTrackTime); + effectPoint = Beatmap.ControlPointInfo.EffectPointAt(currentTrackTime); } else { From 8db80b92bb04f642e58e95de70c6309e2be44876 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 20 May 2022 23:24:21 +0900 Subject: [PATCH 100/395] Fix metronome not using `EditorBeatmap` causing editor updates to not propagate immediately --- osu.Game/Screens/Edit/Timing/MetronomeDisplay.cs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Edit/Timing/MetronomeDisplay.cs b/osu.Game/Screens/Edit/Timing/MetronomeDisplay.cs index 5429165fe0..d51d7b9c58 100644 --- a/osu.Game/Screens/Edit/Timing/MetronomeDisplay.cs +++ b/osu.Game/Screens/Edit/Timing/MetronomeDisplay.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Globalization; using osu.Framework.Allocation; using osu.Framework.Audio.Track; using osu.Framework.Bindables; @@ -30,6 +31,9 @@ namespace osu.Game.Screens.Edit.Timing [Resolved] private OverlayColourProvider overlayColourProvider { get; set; } + [Resolved] + private EditorBeatmap editorBeatmap { get; set; } + [BackgroundDependencyLoader] private void load() { @@ -205,14 +209,14 @@ namespace osu.Game.Screens.Edit.Timing { base.LoadComplete(); - interpolatedBpm.BindValueChanged(bpm => bpmText.Text = bpm.NewValue.ToString()); + interpolatedBpm.BindValueChanged(bpm => bpmText.Text = bpm.NewValue.ToString(CultureInfo.CurrentCulture)); } protected override void Update() { base.Update(); - timingPoint = Beatmap.Value.Beatmap.ControlPointInfo.TimingPointAt(BeatSyncClock.CurrentTime); + timingPoint = editorBeatmap.ControlPointInfo.TimingPointAt(BeatSyncClock.CurrentTime); if (beatLength != timingPoint.BeatLength) { @@ -223,7 +227,7 @@ namespace osu.Game.Screens.Edit.Timing float bpmRatio = (float)Interpolation.ApplyEasing(Easing.OutQuad, Math.Clamp((timingPoint.BPM - 30) / 480, 0, 1)); weight.MoveToY((float)Interpolation.Lerp(0.1f, 0.83f, bpmRatio), 600, Easing.OutQuint); - this.TransformBindableTo(interpolatedBpm, (int)timingPoint.BPM, 600, Easing.OutQuint); + this.TransformBindableTo(interpolatedBpm, (int)Math.Round(timingPoint.BPM), 600, Easing.OutQuint); } if (BeatSyncClock?.IsRunning != true && isSwinging) From a2950d9d4e096ca6d09506e0ab01736651e9100f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 20 May 2022 23:49:03 +0900 Subject: [PATCH 101/395] Update framework --- osu.Android.props | 2 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index b984f528fe..27fcdd4f6e 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -52,7 +52,7 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 7f149b4e35..53a4753223 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -35,7 +35,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/osu.iOS.props b/osu.iOS.props index 932421a705..2e861faa1f 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -61,7 +61,7 @@ - + @@ -84,7 +84,7 @@ - + From de8aedf3484229b27920873abd4bc02a50bc81cd Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 20 May 2022 17:29:37 +0300 Subject: [PATCH 102/395] Add failing test case --- .../SongSelect/TestSceneBeatmapInfoWedge.cs | 40 +++++++++++++++---- 1 file changed, 32 insertions(+), 8 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedge.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedge.cs index 644a333fcf..995274e722 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedge.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedge.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; -using System.Globalization; using System.Linq; using JetBrains.Annotations; using NUnit.Framework; @@ -127,6 +126,12 @@ namespace osu.Game.Tests.Visual.SongSelect AddAssert("check info labels count", () => infoWedge.Info.ChildrenOfType().Count() == expectedCount); } + [SetUpSteps] + public void SetUpSteps() + { + AddStep("reset mods", () => SelectedMods.SetDefault()); + } + [Test] public void TestNullBeatmap() { @@ -147,24 +152,43 @@ namespace osu.Game.Tests.Visual.SongSelect [Test] public void TestBPMUpdates() { - const float bpm = 120; + const double bpm = 120; IBeatmap beatmap = createTestBeatmap(new OsuRuleset().RulesetInfo); beatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = 60 * 1000 / bpm }); OsuModDoubleTime doubleTime = null; selectBeatmap(beatmap); - checkDisplayedBPM(bpm); + checkDisplayedBPM($"{bpm}"); AddStep("select DT", () => SelectedMods.Value = new[] { doubleTime = new OsuModDoubleTime() }); - checkDisplayedBPM(bpm * 1.5f); + checkDisplayedBPM($"{bpm * 1.5f}"); AddStep("change DT rate", () => doubleTime.SpeedChange.Value = 2); - checkDisplayedBPM(bpm * 2); + checkDisplayedBPM($"{bpm * 2}"); + } - void checkDisplayedBPM(float target) => - AddUntilStep($"displayed bpm is {target}", () => this.ChildrenOfType().Any( - label => label.Statistic.Name == "BPM" && label.Statistic.Content == target.ToString(CultureInfo.InvariantCulture))); + [TestCase(120, 125, "120-125 (mostly 120)")] + [TestCase(120, 120.6, "120-121 (mostly 120)")] + [TestCase(120, 120.4, "120")] + public void TestVaryingBPM(double commonBpm, double otherBpm, string expectedDisplay) + { + IBeatmap beatmap = createTestBeatmap(new OsuRuleset().RulesetInfo); + beatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = 60 * 1000 / commonBpm }); + beatmap.ControlPointInfo.Add(100, new TimingControlPoint { BeatLength = 60 * 1000 / otherBpm }); + beatmap.ControlPointInfo.Add(200, new TimingControlPoint { BeatLength = 60 * 1000 / commonBpm }); + + selectBeatmap(beatmap); + checkDisplayedBPM(expectedDisplay); + } + + private void checkDisplayedBPM(string target) + { + AddUntilStep($"displayed bpm is {target}", () => + { + var label = this.ChildrenOfType().Single(l => l.Statistic.Name == "BPM"); + return label.Statistic.Content == target; + }); } private void setRuleset(RulesetInfo rulesetInfo) From 596853da8feb59a5f2c7252e1f0de11763979ad0 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 20 May 2022 17:43:40 +0300 Subject: [PATCH 103/395] Fix song select potentially displaying BPM range with equal min/max values --- osu.Game/Screens/Select/BeatmapInfoWedge.cs | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapInfoWedge.cs b/osu.Game/Screens/Select/BeatmapInfoWedge.cs index 7db1016f62..55ade6fa33 100644 --- a/osu.Game/Screens/Select/BeatmapInfoWedge.cs +++ b/osu.Game/Screens/Select/BeatmapInfoWedge.cs @@ -12,7 +12,6 @@ using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Utils; using osu.Game.Beatmaps; using osu.Game.Beatmaps.Drawables; using osu.Game.Graphics; @@ -416,13 +415,13 @@ namespace osu.Game.Screens.Select foreach (var mod in mods.Value.OfType()) rate = mod.ApplyToRate(0, rate); - double bpmMax = beatmap.ControlPointInfo.BPMMaximum * rate; - double bpmMin = beatmap.ControlPointInfo.BPMMinimum * rate; - double mostCommonBPM = 60000 / beatmap.GetMostCommonBeatLength() * rate; + int bpmMax = (int)Math.Round(beatmap.ControlPointInfo.BPMMaximum * rate); + int bpmMin = (int)Math.Round(beatmap.ControlPointInfo.BPMMinimum * rate); + int mostCommonBPM = (int)Math.Round(60000 / beatmap.GetMostCommonBeatLength() * rate); - string labelText = Precision.AlmostEquals(bpmMin, bpmMax) - ? $"{bpmMin:0}" - : $"{bpmMin:0}-{bpmMax:0} (mostly {mostCommonBPM:0})"; + string labelText = bpmMin == bpmMax + ? $"{bpmMin}" + : $"{bpmMin}-{bpmMax} (mostly {mostCommonBPM})"; bpmLabelContainer.Child = new InfoLabel(new BeatmapStatistic { From 84a3cee452364173ed73d405e3a6caade62263b7 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 20 May 2022 17:47:45 +0300 Subject: [PATCH 104/395] Apply rate multiplier outside BPM rounding --- .../Visual/SongSelect/TestSceneBeatmapInfoWedge.cs | 13 +++++++++---- osu.Game/Screens/Select/BeatmapInfoWedge.cs | 6 +++--- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedge.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedge.cs index 995274e722..030055903f 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedge.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedge.cs @@ -168,16 +168,21 @@ namespace osu.Game.Tests.Visual.SongSelect checkDisplayedBPM($"{bpm * 2}"); } - [TestCase(120, 125, "120-125 (mostly 120)")] - [TestCase(120, 120.6, "120-121 (mostly 120)")] - [TestCase(120, 120.4, "120")] - public void TestVaryingBPM(double commonBpm, double otherBpm, string expectedDisplay) + [TestCase(120, 125, null, "120-125 (mostly 120)")] + [TestCase(120, 120.6, null, "120-121 (mostly 120)")] + [TestCase(120, 120.4, null, "120")] + [TestCase(120, 120.6, "DT", "180-182 (mostly 180)")] + [TestCase(120, 120.4, "DT", "180")] + public void TestVaryingBPM(double commonBpm, double otherBpm, string mod, string expectedDisplay) { IBeatmap beatmap = createTestBeatmap(new OsuRuleset().RulesetInfo); beatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = 60 * 1000 / commonBpm }); beatmap.ControlPointInfo.Add(100, new TimingControlPoint { BeatLength = 60 * 1000 / otherBpm }); beatmap.ControlPointInfo.Add(200, new TimingControlPoint { BeatLength = 60 * 1000 / commonBpm }); + if (mod != null) + AddStep($"select {mod}", () => SelectedMods.Value = new[] { Ruleset.Value.CreateInstance().CreateModFromAcronym(mod) }); + selectBeatmap(beatmap); checkDisplayedBPM(expectedDisplay); } diff --git a/osu.Game/Screens/Select/BeatmapInfoWedge.cs b/osu.Game/Screens/Select/BeatmapInfoWedge.cs index 55ade6fa33..d98238f518 100644 --- a/osu.Game/Screens/Select/BeatmapInfoWedge.cs +++ b/osu.Game/Screens/Select/BeatmapInfoWedge.cs @@ -415,9 +415,9 @@ namespace osu.Game.Screens.Select foreach (var mod in mods.Value.OfType()) rate = mod.ApplyToRate(0, rate); - int bpmMax = (int)Math.Round(beatmap.ControlPointInfo.BPMMaximum * rate); - int bpmMin = (int)Math.Round(beatmap.ControlPointInfo.BPMMinimum * rate); - int mostCommonBPM = (int)Math.Round(60000 / beatmap.GetMostCommonBeatLength() * rate); + int bpmMax = (int)Math.Round(Math.Round(beatmap.ControlPointInfo.BPMMaximum) * rate); + int bpmMin = (int)Math.Round(Math.Round(beatmap.ControlPointInfo.BPMMinimum) * rate); + int mostCommonBPM = (int)Math.Round(Math.Round(60000 / beatmap.GetMostCommonBeatLength()) * rate); string labelText = bpmMin == bpmMax ? $"{bpmMin}" From 7dcb88759afcf00ce46e930ce117729fe9d7416f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 21 May 2022 00:28:17 +0900 Subject: [PATCH 105/395] Remove unnecessary using statement --- osu.Game/Graphics/ScreenshotManager.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Graphics/ScreenshotManager.cs b/osu.Game/Graphics/ScreenshotManager.cs index 1fc2633143..53f7d5791b 100644 --- a/osu.Game/Graphics/ScreenshotManager.cs +++ b/osu.Game/Graphics/ScreenshotManager.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.IO; using System.Threading; using System.Threading.Tasks; using osu.Framework.Allocation; From 1f17652c1d55d0f42a9394af259f668961982077 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 20 May 2022 19:08:41 +0300 Subject: [PATCH 106/395] Fix test failure due to async-loading of content --- osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedge.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedge.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedge.cs index 030055903f..ef04baefa2 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedge.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedge.cs @@ -191,7 +191,7 @@ namespace osu.Game.Tests.Visual.SongSelect { AddUntilStep($"displayed bpm is {target}", () => { - var label = this.ChildrenOfType().Single(l => l.Statistic.Name == "BPM"); + var label = infoWedge.DisplayedContent.ChildrenOfType().Single(l => l.Statistic.Name == "BPM"); return label.Statistic.Content == target; }); } From d3f115bfe2b321a9309be155995d44e6fc0423a4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 21 May 2022 13:53:58 +0900 Subject: [PATCH 107/395] Remove redundant qualifiers --- osu.Game/Graphics/Containers/BeatSyncedContainer.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Graphics/Containers/BeatSyncedContainer.cs b/osu.Game/Graphics/Containers/BeatSyncedContainer.cs index 48936f7aa9..bd46a20434 100644 --- a/osu.Game/Graphics/Containers/BeatSyncedContainer.cs +++ b/osu.Game/Graphics/Containers/BeatSyncedContainer.cs @@ -124,9 +124,9 @@ namespace osu.Game.Graphics.Containers double currentTrackTime = clock.CurrentTime + EarlyActivationMilliseconds; - if (this.beatmap.Value.TrackLoaded && this.beatmap.Value.BeatmapLoaded) + if (beatmap.Value.TrackLoaded && beatmap.Value.BeatmapLoaded) { - track = this.beatmap.Value.Track; + track = beatmap.Value.Track; } IsBeatSyncedWithTrack = beatmap != null && clock.IsRunning && track?.Length > 0; From 5bf17dde9fd244c76a61960f6689741128ed6a60 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 21 May 2022 14:35:07 +0900 Subject: [PATCH 108/395] Fix missing `MutatePath` calls --- osu.Game/IO/WrappedStorage.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/IO/WrappedStorage.cs b/osu.Game/IO/WrappedStorage.cs index d4d9d531fa..a465c215e2 100644 --- a/osu.Game/IO/WrappedStorage.cs +++ b/osu.Game/IO/WrappedStorage.cs @@ -67,12 +67,12 @@ namespace osu.Game.IO public override IEnumerable GetFiles(string path, string pattern = "*") => ToLocalRelative(UnderlyingStorage.GetFiles(MutatePath(path), pattern)); - public override Stream CreateFileSafely(string path) => UnderlyingStorage.CreateFileSafely(path); + public override Stream CreateFileSafely(string path) => UnderlyingStorage.CreateFileSafely(MutatePath(path)); public override Stream GetStream(string path, FileAccess access = FileAccess.Read, FileMode mode = FileMode.OpenOrCreate) => UnderlyingStorage.GetStream(MutatePath(path), access, mode); - public override void Move(string from, string to) => UnderlyingStorage.Move(from, to); + public override void Move(string from, string to) => UnderlyingStorage.Move(MutatePath(from), MutatePath(to)); public override bool OpenFileExternally(string filename) => UnderlyingStorage.OpenFileExternally(MutatePath(filename)); From 72386d9377412eb2ba5adef5e06ade4b389cb2ec Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 21 May 2022 15:15:52 +0900 Subject: [PATCH 109/395] Fix incorrect storage being used in `CreateFileSafely` implementation --- osu.Game/IO/WrappedStorage.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/osu.Game/IO/WrappedStorage.cs b/osu.Game/IO/WrappedStorage.cs index a465c215e2..5887704768 100644 --- a/osu.Game/IO/WrappedStorage.cs +++ b/osu.Game/IO/WrappedStorage.cs @@ -11,7 +11,7 @@ namespace osu.Game.IO /// /// A storage which wraps another storage and delegates implementation, potentially mutating the lookup path. /// - public class WrappedStorage : Storage + public class WrappedStorage : NativeStorage // TODO: Revert to Storage inheritance after https://github.com/ppy/osu-framework/pull/5189 { protected Storage UnderlyingStorage { get; private set; } @@ -67,8 +67,6 @@ namespace osu.Game.IO public override IEnumerable GetFiles(string path, string pattern = "*") => ToLocalRelative(UnderlyingStorage.GetFiles(MutatePath(path), pattern)); - public override Stream CreateFileSafely(string path) => UnderlyingStorage.CreateFileSafely(MutatePath(path)); - public override Stream GetStream(string path, FileAccess access = FileAccess.Read, FileMode mode = FileMode.OpenOrCreate) => UnderlyingStorage.GetStream(MutatePath(path), access, mode); From ba21556d4e9271b7f7e9185eb19b5ccec2b2901c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 21 May 2022 15:54:43 +0900 Subject: [PATCH 110/395] Update framework --- osu.Android.props | 2 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index 7910100b1f..aaea784852 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -52,7 +52,7 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 17bc056b59..5bc65ca507 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -35,7 +35,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/osu.iOS.props b/osu.iOS.props index 191d1bd5ca..3597e7e5c0 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -61,7 +61,7 @@ - + @@ -84,7 +84,7 @@ - + From c587d10cc3dcef9be569b2b6d453bb2b1c79b1cd Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 21 May 2022 15:55:07 +0900 Subject: [PATCH 111/395] Revert storage workaround --- osu.Game/IO/WrappedStorage.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/IO/WrappedStorage.cs b/osu.Game/IO/WrappedStorage.cs index 5887704768..7205ea3adb 100644 --- a/osu.Game/IO/WrappedStorage.cs +++ b/osu.Game/IO/WrappedStorage.cs @@ -11,7 +11,7 @@ namespace osu.Game.IO /// /// A storage which wraps another storage and delegates implementation, potentially mutating the lookup path. /// - public class WrappedStorage : NativeStorage // TODO: Revert to Storage inheritance after https://github.com/ppy/osu-framework/pull/5189 + public class WrappedStorage : Storage { protected Storage UnderlyingStorage { get; private set; } From a7aa36a825cc9589ee6f3cf5bf2f5792d8df09cd Mon Sep 17 00:00:00 2001 From: maromalo <54760464+maromalo@users.noreply.github.com> Date: Sat, 21 May 2022 04:55:42 -0300 Subject: [PATCH 112/395] Add OnPressed to button --- .../Screens/OnlinePlay/Match/RoomSubScreen.cs | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs index 13b2c37ded..61798b5413 100644 --- a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs @@ -15,9 +15,12 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Shapes; +using osu.Framework.Input.Bindings; +using osu.Framework.Input.Events; using osu.Framework.Screens; using osu.Game.Audio; using osu.Game.Beatmaps; +using osu.Game.Input.Bindings; using osu.Game.Online.Rooms; using osu.Game.Overlays; using osu.Game.Overlays.Mods; @@ -473,8 +476,19 @@ namespace osu.Game.Screens.OnlinePlay.Match /// The room to change the settings of. protected abstract RoomSettingsOverlay CreateRoomSettingsOverlay(Room room); - public class UserModSelectButton : PurpleTriangleButton + public class UserModSelectButton : PurpleTriangleButton, IKeyBindingHandler { + public virtual bool OnPressed(KeyBindingPressEvent e) + { + if (e.Action == GlobalAction.ToggleModSelection && !e.Repeat) + { + TriggerClick(); + return true; + } + return false; + } + + public virtual void OnReleased(KeyBindingReleaseEvent e) { } } protected override void Dispose(bool isDisposing) From 2bd4c126d39223990260535b2f4874ef8229e0b8 Mon Sep 17 00:00:00 2001 From: maromalo <54760464+maromalo@users.noreply.github.com> Date: Sat, 21 May 2022 05:07:24 -0300 Subject: [PATCH 113/395] Blank line Forgot to save. --- osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs index 61798b5413..0dc6e52770 100644 --- a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs @@ -485,6 +485,7 @@ namespace osu.Game.Screens.OnlinePlay.Match TriggerClick(); return true; } + return false; } From 376549ce09a2570811ff8b7ea1e6ce3b1636b812 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 21 May 2022 21:48:51 +0900 Subject: [PATCH 114/395] Use `ToLocalisableString` instead of `CurrentCulture` --- osu.Game/Screens/Edit/Timing/MetronomeDisplay.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Timing/MetronomeDisplay.cs b/osu.Game/Screens/Edit/Timing/MetronomeDisplay.cs index d51d7b9c58..98ce9f0f46 100644 --- a/osu.Game/Screens/Edit/Timing/MetronomeDisplay.cs +++ b/osu.Game/Screens/Edit/Timing/MetronomeDisplay.cs @@ -6,6 +6,7 @@ using System.Globalization; using osu.Framework.Allocation; using osu.Framework.Audio.Track; using osu.Framework.Bindables; +using osu.Framework.Extensions.LocalisationExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; @@ -209,7 +210,7 @@ namespace osu.Game.Screens.Edit.Timing { base.LoadComplete(); - interpolatedBpm.BindValueChanged(bpm => bpmText.Text = bpm.NewValue.ToString(CultureInfo.CurrentCulture)); + interpolatedBpm.BindValueChanged(bpm => bpmText.Text = bpm.NewValue.ToLocalisableString()); } protected override void Update() From 9a780bcad3b6e7e4b14fe11f3bc1f2c23c13c6d0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 21 May 2022 21:56:05 +0900 Subject: [PATCH 115/395] Remove unused using --- osu.Game/Screens/Edit/Timing/MetronomeDisplay.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Timing/MetronomeDisplay.cs b/osu.Game/Screens/Edit/Timing/MetronomeDisplay.cs index 98ce9f0f46..b59865ac1a 100644 --- a/osu.Game/Screens/Edit/Timing/MetronomeDisplay.cs +++ b/osu.Game/Screens/Edit/Timing/MetronomeDisplay.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Globalization; using osu.Framework.Allocation; using osu.Framework.Audio.Track; using osu.Framework.Bindables; From 466ed3c791507796151fb504ad353902a71c1f32 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 21 May 2022 16:43:31 +0300 Subject: [PATCH 116/395] Fix wrong return xmldoc MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bartłomiej Dach --- osu.Game/Beatmaps/WorkingBeatmap.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/WorkingBeatmap.cs b/osu.Game/Beatmaps/WorkingBeatmap.cs index a1a8306b76..5551e0b3e5 100644 --- a/osu.Game/Beatmaps/WorkingBeatmap.cs +++ b/osu.Game/Beatmaps/WorkingBeatmap.cs @@ -130,7 +130,7 @@ namespace osu.Game.Beatmaps /// Used as an optimisation to avoid reload / track swap across difficulties in the same beatmap set. /// /// The target working beatmap to transfer this track to. - /// Whether the track is valid and has been transferred to this working beatmap. + /// Whether the track has been transferred to the . public virtual bool TryTransferTrack([NotNull] WorkingBeatmap target) { if (BeatmapInfo?.AudioEquals(target.BeatmapInfo) != true) From a42f5ea34ec5c88b002037692029422931a4dcb8 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 21 May 2022 16:50:40 +0300 Subject: [PATCH 117/395] Bring back virtual track condition given its cheapness Will still keep the override in `ClockBackedTestWorkingBeatmap` because it still relies on a local track store and will fail the moment it uses a non-virtual track. --- osu.Game/Beatmaps/WorkingBeatmap.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/WorkingBeatmap.cs b/osu.Game/Beatmaps/WorkingBeatmap.cs index 5551e0b3e5..5fc579b47c 100644 --- a/osu.Game/Beatmaps/WorkingBeatmap.cs +++ b/osu.Game/Beatmaps/WorkingBeatmap.cs @@ -133,7 +133,7 @@ namespace osu.Game.Beatmaps /// Whether the track has been transferred to the . public virtual bool TryTransferTrack([NotNull] WorkingBeatmap target) { - if (BeatmapInfo?.AudioEquals(target.BeatmapInfo) != true) + if (BeatmapInfo?.AudioEquals(target.BeatmapInfo) != true || track.IsDummyDevice) return false; target.track = track; From a17eed64f96c2557bbf632d894694fd57bc1d00c Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 21 May 2022 16:51:04 +0300 Subject: [PATCH 118/395] Use `Track` to ensure its loaded before transferring --- osu.Game/Beatmaps/WorkingBeatmap.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Beatmaps/WorkingBeatmap.cs b/osu.Game/Beatmaps/WorkingBeatmap.cs index 5fc579b47c..09072ec897 100644 --- a/osu.Game/Beatmaps/WorkingBeatmap.cs +++ b/osu.Game/Beatmaps/WorkingBeatmap.cs @@ -133,10 +133,10 @@ namespace osu.Game.Beatmaps /// Whether the track has been transferred to the . public virtual bool TryTransferTrack([NotNull] WorkingBeatmap target) { - if (BeatmapInfo?.AudioEquals(target.BeatmapInfo) != true || track.IsDummyDevice) + if (BeatmapInfo?.AudioEquals(target.BeatmapInfo) != true || Track.IsDummyDevice) return false; - target.track = track; + target.track = Track; return true; } From d73afcaf484185ac1d1102b8b2c4df4a118c98b5 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 12 May 2022 17:29:28 +0300 Subject: [PATCH 119/395] Fix existing test coverage false-passing Also improves general test coverage to test more realisticly, in order to produce accurate results. --- .../TestSceneMultiSpectatorScreen.cs | 46 ++++++++++--------- 1 file changed, 24 insertions(+), 22 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs index 703b526e8c..c048722804 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.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.Collections.Generic; using System.Linq; using NUnit.Framework; @@ -15,13 +16,14 @@ using osu.Game.Configuration; using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Multiplayer; using osu.Game.Online.Multiplayer.MatchTypes.TeamVersus; -using osu.Game.Online.Rooms; using osu.Game.Rulesets.UI; using osu.Game.Screens.OnlinePlay.Multiplayer.Spectate; using osu.Game.Screens.Play; using osu.Game.Screens.Play.HUD; using osu.Game.Screens.Play.PlayerSettings; +using osu.Game.Storyboards; using osu.Game.Tests.Beatmaps.IO; +using osuTK; using osuTK.Graphics; namespace osu.Game.Tests.Visual.Multiplayer @@ -349,15 +351,27 @@ namespace osu.Game.Tests.Visual.Multiplayer } /// - /// Tests spectating with a gameplay start time set to a negative value. - /// Simulating beatmaps with high or negative time storyboard elements. + /// Tests spectating with a beatmap that has a high value. /// [Test] - public void TestNegativeGameplayStartTime() + public void TestAudioLeadIn() => testLeadIn(b => b.BeatmapInfo.AudioLeadIn = 2000); + + /// + /// Tests spectating with a beatmap that has a storyboard element with a negative start time (i.e. intro storyboard element). + /// + [Test] + public void TestIntroStoryboardElement() => testLeadIn(b => + { + var sprite = new StoryboardSprite("unknown", Anchor.TopLeft, Vector2.Zero); + sprite.TimelineGroup.Alpha.Add(Easing.None, -2000, 0, 0, 1); + b.Storyboard.GetLayer("Background").Add(sprite); + }); + + private void testLeadIn(Action applyToBeatmap = null) { start(PLAYER_1_ID); - loadSpectateScreen(false, -500); + loadSpectateScreen(false, applyToBeatmap); // to ensure negative gameplay start time does not affect spectator, send frames exactly after StartGameplay(). // (similar to real spectating sessions in which the first frames get sent between StartGameplay() and player load complete) @@ -371,14 +385,16 @@ namespace osu.Game.Tests.Visual.Multiplayer assertRunning(PLAYER_1_ID); } - private void loadSpectateScreen(bool waitForPlayerLoad = true, double? gameplayStartTime = null) + private void loadSpectateScreen(bool waitForPlayerLoad = true, Action applyToBeatmap = null) { - AddStep(!gameplayStartTime.HasValue ? "load screen" : $"load screen (start = {gameplayStartTime}ms)", () => + AddStep("load screen", () => { Beatmap.Value = beatmapManager.GetWorkingBeatmap(importedBeatmap); Ruleset.Value = importedBeatmap.Ruleset; - LoadScreen(spectatorScreen = new TestMultiSpectatorScreen(SelectedRoom.Value, playingUsers.ToArray(), gameplayStartTime)); + applyToBeatmap?.Invoke(Beatmap.Value); + + LoadScreen(spectatorScreen = new MultiSpectatorScreen(SelectedRoom.Value, playingUsers.ToArray())); }); AddUntilStep("wait for screen load", () => spectatorScreen.LoadState == LoadState.Loaded && (!waitForPlayerLoad || spectatorScreen.AllPlayersLoaded)); @@ -461,19 +477,5 @@ namespace osu.Game.Tests.Visual.Multiplayer private GameplayLeaderboardScore getLeaderboardScore(int userId) => spectatorScreen.ChildrenOfType().Single(s => s.User?.Id == userId); private int[] getPlayerIds(int count) => Enumerable.Range(PLAYER_1_ID, count).ToArray(); - - private class TestMultiSpectatorScreen : MultiSpectatorScreen - { - private readonly double? startTime; - - public TestMultiSpectatorScreen(Room room, MultiplayerRoomUser[] users, double? startTime = null) - : base(room, users) - { - this.startTime = startTime; - } - - protected override MasterGameplayClockContainer CreateMasterGameplayClockContainer(WorkingBeatmap beatmap) - => new MasterGameplayClockContainer(beatmap, 0) { StartTime = startTime ?? 0 }; - } } } From 27da293b4053d9fce0ca0cd161f32c1e7127d535 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 13 May 2022 00:07:11 +0300 Subject: [PATCH 120/395] Make catch-up spectator clocks running state immutable externally --- .../Spectate/CatchUpSpectatorPlayerClock.cs | 10 ++++++++++ .../Multiplayer/Spectate/CatchUpSyncManager.cs | 2 ++ .../Multiplayer/Spectate/ISpectatorPlayerClock.cs | 10 ++++++++++ 3 files changed, 22 insertions(+) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/CatchUpSpectatorPlayerClock.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/CatchUpSpectatorPlayerClock.cs index 20d12d62a3..48d0b063ff 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/CatchUpSpectatorPlayerClock.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/CatchUpSpectatorPlayerClock.cs @@ -34,6 +34,16 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate public void Stop() => IsRunning = false; + void IAdjustableClock.Start() + { + // Our running state should only be managed by an ISyncManager, ignore calls from external sources. + } + + void IAdjustableClock.Stop() + { + // Our running state should only be managed by an ISyncManager, ignore calls from external sources. + } + public bool Seek(double position) { CurrentTime = position; diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/CatchUpSyncManager.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/CatchUpSyncManager.cs index b8f47c16ff..b0fdeddd56 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/CatchUpSyncManager.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/CatchUpSyncManager.cs @@ -144,6 +144,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate // Make sure the player clock is running if it can. if (!clock.WaitingOnFrames.Value) clock.Start(); + else + clock.Stop(); if (clock.IsCatchingUp) { diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/ISpectatorPlayerClock.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/ISpectatorPlayerClock.cs index de23b4fef7..b2ecb105c2 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/ISpectatorPlayerClock.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/ISpectatorPlayerClock.cs @@ -11,6 +11,16 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate /// public interface ISpectatorPlayerClock : IFrameBasedClock, IAdjustableClock { + /// + /// Starts this . + /// + new void Start(); + + /// + /// Stops this . + /// + new void Stop(); + /// /// Whether this clock is waiting on frames to continue playback. /// From efae934e0199713368a4e9cf563cb77a6202f6a6 Mon Sep 17 00:00:00 2001 From: Jai Sharma Date: Sat, 21 May 2022 17:35:31 +0100 Subject: [PATCH 121/395] Fix slow loading channel test --- osu.Game.Tests/Visual/Online/TestSceneChatOverlayV2.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game.Tests/Visual/Online/TestSceneChatOverlayV2.cs b/osu.Game.Tests/Visual/Online/TestSceneChatOverlayV2.cs index 4f916eec18..e27db00003 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneChatOverlayV2.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneChatOverlayV2.cs @@ -397,6 +397,7 @@ namespace osu.Game.Tests.Visual.Online chatOverlay.SlowLoading = true; }); AddStep("Join channel 1", () => channelManager.JoinChannel(testChannel1)); + AddStep("Select channel 1", () => clickDrawable(getChannelListItem(testChannel1))); AddAssert("Channel 1 loading", () => !channelIsVisible && chatOverlay.GetSlowLoadingChannel(testChannel1).LoadState == LoadState.Loading); AddStep("Join channel 2", () => channelManager.JoinChannel(testChannel2)); From 21e1576b2fccaf00eaf980265c8b6590103d4767 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 21 May 2022 20:49:29 +0200 Subject: [PATCH 122/395] Fix appearance of sheared button borders after click The border would previously get brighter after click, but then dim instantly when the flash layer has fully faded out. The underlying issue there is https://github.com/ppy/osu-framework/issues/5191, but `ShearedButton` was placing the flashing layer incorrectly anyway, as the intent was that it should also apply to the border. --- .../Graphics/UserInterface/ShearedButton.cs | 37 +++++++++++++------ 1 file changed, 25 insertions(+), 12 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/ShearedButton.cs b/osu.Game/Graphics/UserInterface/ShearedButton.cs index dea44e6d99..66c6eedd0c 100644 --- a/osu.Game/Graphics/UserInterface/ShearedButton.cs +++ b/osu.Game/Graphics/UserInterface/ShearedButton.cs @@ -5,6 +5,7 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; +using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Events; using osu.Framework.Localisation; @@ -68,6 +69,7 @@ namespace osu.Game.Graphics.UserInterface private Colour4? lighterColour; private Colour4? textColour; + private readonly Container backgroundLayer; private readonly Box flashLayer; /// @@ -85,24 +87,35 @@ namespace osu.Game.Graphics.UserInterface Height = 50; Padding = new MarginPadding { Horizontal = shear * 50 }; - Content.CornerRadius = 7; + const float corner_radius = 7; + + Content.CornerRadius = corner_radius; Content.Shear = new Vector2(shear, 0); Content.Masking = true; - Content.BorderThickness = 2; Content.Anchor = Content.Origin = Anchor.Centre; Children = new Drawable[] { - background = new Box + backgroundLayer = new Container { - RelativeSizeAxes = Axes.Both - }, - text = new OsuSpriteText - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Font = OsuFont.TorusAlternate.With(size: 17), - Shear = new Vector2(-shear, 0) + RelativeSizeAxes = Axes.Both, + CornerRadius = corner_radius, + Masking = true, + BorderThickness = 2, + Children = new Drawable[] + { + background = new Box + { + RelativeSizeAxes = Axes.Both + }, + text = new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Font = OsuFont.TorusAlternate.With(size: 17), + Shear = new Vector2(-shear, 0) + }, + } }, flashLayer = new Box { @@ -186,7 +199,7 @@ namespace osu.Game.Graphics.UserInterface } background.FadeColour(colourDark, 150, Easing.OutQuint); - Content.TransformTo(nameof(BorderColour), ColourInfo.GradientVertical(colourDark, colourLight), 150, Easing.OutQuint); + backgroundLayer.TransformTo(nameof(BorderColour), ColourInfo.GradientVertical(colourDark, colourLight), 150, Easing.OutQuint); if (!Enabled.Value) colourText = colourText.Opacity(0.6f); From fc25d248ad18d4af683347fe0a3b444504e4161c Mon Sep 17 00:00:00 2001 From: maromalo <54760464+maromalo@users.noreply.github.com> Date: Sat, 21 May 2022 18:16:29 -0300 Subject: [PATCH 123/395] Test coverage + no virtual --- .../TestSceneMultiplayerMatchSubScreen.cs | 42 +++++++++++++++++++ .../Screens/OnlinePlay/Match/RoomSubScreen.cs | 4 +- 2 files changed, 44 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs index ca79fa9cb8..dd281e7738 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs @@ -178,6 +178,48 @@ namespace osu.Game.Tests.Visual.Multiplayer .SingleOrDefault(panel => !panel.Filtered.Value)?.Mod is OsuModDoubleTime); } + [Test] + public void TestModSelectKeyWithAllowedMods() + { + AddStep("add playlist item with allowed mod", () => + { + SelectedRoom.Value.Playlist.Add(new PlaylistItem(new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo) + { + RulesetID = new OsuRuleset().RulesetInfo.OnlineID, + AllowedMods = new[] { new APIMod(new OsuModDoubleTime()) } + }); + }); + + ClickButtonWhenEnabled(); + + AddUntilStep("wait for join", () => RoomJoined); + + AddStep("press toggle mod select key", () => InputManager.Key(osuTK.Input.Key.F1)); + + AddUntilStep("mod select contents loaded", + () => this.ChildrenOfType().Any() && this.ChildrenOfType().All(col => col.IsLoaded && col.ItemsLoaded)); + } + + [Test] + public void TestModSelectKeyWithNoAllowedMods() + { + AddStep("add playlist item with no allowed mods", () => + { + SelectedRoom.Value.Playlist.Add(new PlaylistItem(new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo) + { + RulesetID = new OsuRuleset().RulesetInfo.OnlineID, + }); + }); + ClickButtonWhenEnabled(); + + AddUntilStep("wait for join", () => RoomJoined); + + AddStep("press toggle mod select key", () => InputManager.Key(osuTK.Input.Key.F1)); + + AddAssert("mod select contents not loaded", + () => !(this.ChildrenOfType().Any() && this.ChildrenOfType().All(col => col.IsLoaded && col.ItemsLoaded))); + } + [Test] public void TestNextPlaylistItemSelectedAfterCompletion() { diff --git a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs index 0dc6e52770..a612b6a0e6 100644 --- a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs @@ -478,7 +478,7 @@ namespace osu.Game.Screens.OnlinePlay.Match public class UserModSelectButton : PurpleTriangleButton, IKeyBindingHandler { - public virtual bool OnPressed(KeyBindingPressEvent e) + public bool OnPressed(KeyBindingPressEvent e) { if (e.Action == GlobalAction.ToggleModSelection && !e.Repeat) { @@ -489,7 +489,7 @@ namespace osu.Game.Screens.OnlinePlay.Match return false; } - public virtual void OnReleased(KeyBindingReleaseEvent e) { } + public void OnReleased(KeyBindingReleaseEvent e) { } } protected override void Dispose(bool isDisposing) From eabf57828276ce8ce2b2ad16305e54df271b34ed Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 22 May 2022 22:15:53 +0900 Subject: [PATCH 124/395] Use interface to convey beat sync information --- .../TestSceneBeatSyncedContainer.cs | 22 +++++-- .../Containers/BeatSyncedContainer.cs | 65 ++++--------------- .../Graphics/Containers/IBeatSyncProvider.cs | 26 ++++++++ osu.Game/OsuGameBase.cs | 10 ++- osu.Game/Rulesets/Mods/MetronomeBeat.cs | 2 +- osu.Game/Screens/Edit/Editor.cs | 10 ++- .../Screens/Edit/Timing/MetronomeDisplay.cs | 12 ++-- .../Play/MasterGameplayClockContainer.cs | 8 ++- osu.Game/Screens/Play/Player.cs | 9 ++- 9 files changed, 96 insertions(+), 68 deletions(-) create mode 100644 osu.Game/Graphics/Containers/IBeatSyncProvider.cs diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneBeatSyncedContainer.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneBeatSyncedContainer.cs index 1881f6d718..3cbb7daf51 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneBeatSyncedContainer.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneBeatSyncedContainer.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using NUnit.Framework; using osu.Framework.Audio.Track; @@ -82,11 +83,15 @@ namespace osu.Game.Tests.Visual.UserInterface if (!allowMistimed) { - AddAssert("trigger is near beat length", () => lastActuationTime != null && lastBeatIndex != null && Precision.AlmostEquals(lastTimingPoint.Time + lastBeatIndex.Value * lastTimingPoint.BeatLength, lastActuationTime.Value, BeatSyncedContainer.MISTIMED_ALLOWANCE)); + AddAssert("trigger is near beat length", + () => lastActuationTime != null && lastBeatIndex != null && Precision.AlmostEquals(lastTimingPoint.Time + lastBeatIndex.Value * lastTimingPoint.BeatLength, lastActuationTime.Value, + BeatSyncedContainer.MISTIMED_ALLOWANCE)); } else { - AddAssert("trigger is not near beat length", () => lastActuationTime != null && lastBeatIndex != null && !Precision.AlmostEquals(lastTimingPoint.Time + lastBeatIndex.Value * lastTimingPoint.BeatLength, lastActuationTime.Value, BeatSyncedContainer.MISTIMED_ALLOWANCE)); + AddAssert("trigger is not near beat length", + () => lastActuationTime != null && lastBeatIndex != null && !Precision.AlmostEquals(lastTimingPoint.Time + lastBeatIndex.Value * lastTimingPoint.BeatLength, + lastActuationTime.Value, BeatSyncedContainer.MISTIMED_ALLOWANCE)); } } @@ -258,7 +263,7 @@ namespace osu.Game.Tests.Visual.UserInterface }; } - private List timingPoints => Beatmap.ControlPointInfo.TimingPoints.ToList(); + private List timingPoints => BeatSyncSource.ControlPoints?.TimingPoints.ToList(); private TimingControlPoint getNextTimingPoint(TimingControlPoint current) { @@ -275,7 +280,11 @@ namespace osu.Game.Tests.Visual.UserInterface if (timingPoints.Count == 0) return 0; if (timingPoints[^1] == current) - return (int)Math.Ceiling((BeatSyncClock.CurrentTime - current.Time) / current.BeatLength); + { + Debug.Assert(BeatSyncSource.Clock != null); + + return (int)Math.Ceiling((BeatSyncSource.Clock.CurrentTime - current.Time) / current.BeatLength); + } return (int)Math.Ceiling((getNextTimingPoint(current).Time - current.Time) / current.BeatLength); } @@ -283,9 +292,12 @@ namespace osu.Game.Tests.Visual.UserInterface protected override void Update() { base.Update(); + + Debug.Assert(BeatSyncSource.Clock != null); + timeUntilNextBeat.Value = TimeUntilNextBeat; timeSinceLastBeat.Value = TimeSinceLastBeat; - currentTime.Value = BeatSyncClock.CurrentTime; + currentTime.Value = BeatSyncSource.Clock.CurrentTime; } public Action NewBeat; diff --git a/osu.Game/Graphics/Containers/BeatSyncedContainer.cs b/osu.Game/Graphics/Containers/BeatSyncedContainer.cs index bd46a20434..953731244d 100644 --- a/osu.Game/Graphics/Containers/BeatSyncedContainer.cs +++ b/osu.Game/Graphics/Containers/BeatSyncedContainer.cs @@ -5,12 +5,9 @@ using System; using System.Diagnostics; using osu.Framework.Allocation; using osu.Framework.Audio.Track; -using osu.Framework.Bindables; using osu.Framework.Graphics.Containers; -using osu.Framework.Timing; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; -using osu.Game.Screens.Edit; using osu.Game.Screens.Play; namespace osu.Game.Graphics.Containers @@ -75,74 +72,38 @@ namespace osu.Game.Graphics.Containers /// protected bool IsBeatSyncedWithTrack { get; private set; } + [Resolved] + protected IBeatSyncProvider BeatSyncSource { get; private set; } + protected virtual void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, ChannelAmplitudes amplitudes) { } - [Resolved] - private IBindable beatmap { get; set; } - - [Resolved(canBeNull: true)] - protected GameplayClock GameplayClock { get; private set; } - - [Resolved(canBeNull: true)] - protected EditorBeatmap EditorBeatmap { get; private set; } - - [Resolved(canBeNull: true)] - protected EditorClock EditorClock { get; private set; } - - protected IBeatmap Beatmap => EditorBeatmap ?? beatmap?.Value.Beatmap; - - protected IClock BeatSyncClock - { - get - { - if (EditorClock != null) - return EditorClock; - - if (GameplayClock != null) - return GameplayClock; - - if (beatmap.Value.TrackLoaded) - return beatmap.Value.Track; - - return null; - } - } - protected override void Update() { - ITrack track = null; - TimingControlPoint timingPoint; EffectControlPoint effectPoint; - IClock clock = BeatSyncClock; + IsBeatSyncedWithTrack = BeatSyncSource.Clock?.IsRunning == true; - if (clock == null) - return; - - double currentTrackTime = clock.CurrentTime + EarlyActivationMilliseconds; - - if (beatmap.Value.TrackLoaded && beatmap.Value.BeatmapLoaded) - { - track = beatmap.Value.Track; - } - - IsBeatSyncedWithTrack = beatmap != null && clock.IsRunning && track?.Length > 0; + double currentTrackTime; if (IsBeatSyncedWithTrack) { - Debug.Assert(beatmap != null); + Debug.Assert(BeatSyncSource.ControlPoints != null); + Debug.Assert(BeatSyncSource.Clock != null); - timingPoint = Beatmap.ControlPointInfo.TimingPointAt(currentTrackTime); - effectPoint = Beatmap.ControlPointInfo.EffectPointAt(currentTrackTime); + currentTrackTime = BeatSyncSource.Clock.CurrentTime + EarlyActivationMilliseconds; + + timingPoint = BeatSyncSource.ControlPoints.TimingPointAt(currentTrackTime); + effectPoint = BeatSyncSource.ControlPoints.EffectPointAt(currentTrackTime); } else { // this may be the case where the beat syncing clock has been paused. // we still want to show an idle animation, so use this container's time instead. currentTrackTime = Clock.CurrentTime + EarlyActivationMilliseconds; + timingPoint = TimingControlPoint.DEFAULT; effectPoint = EffectControlPoint.DEFAULT; } @@ -172,7 +133,7 @@ namespace osu.Game.Graphics.Containers if (AllowMistimedEventFiring || Math.Abs(TimeSinceLastBeat) < MISTIMED_ALLOWANCE) { using (BeginDelayedSequence(-TimeSinceLastBeat)) - OnNewBeat(beatIndex, timingPoint, effectPoint, track?.CurrentAmplitudes ?? ChannelAmplitudes.Empty); + OnNewBeat(beatIndex, timingPoint, effectPoint, BeatSyncSource.Amplitudes ?? ChannelAmplitudes.Empty); } lastBeat = beatIndex; diff --git a/osu.Game/Graphics/Containers/IBeatSyncProvider.cs b/osu.Game/Graphics/Containers/IBeatSyncProvider.cs new file mode 100644 index 0000000000..d5be50297c --- /dev/null +++ b/osu.Game/Graphics/Containers/IBeatSyncProvider.cs @@ -0,0 +1,26 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +#nullable enable + +using osu.Framework.Allocation; +using osu.Framework.Audio.Track; +using osu.Framework.Timing; +using osu.Game.Beatmaps.ControlPoints; + +namespace osu.Game.Graphics.Containers +{ + /// + /// Provides various data sources which allow for synchronising visuals to a known beat. + /// Primarily intended for use with . + /// + [Cached(typeof(IBeatSyncProvider))] + public interface IBeatSyncProvider + { + ControlPointInfo? ControlPoints { get; } + + IClock? Clock { get; } + + ChannelAmplitudes? Amplitudes { get; } + } +} diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 2e4758a134..52052efd5d 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -10,6 +10,7 @@ using System.Threading; using System.Threading.Tasks; using osu.Framework.Allocation; using osu.Framework.Audio; +using osu.Framework.Audio.Track; using osu.Framework.Bindables; using osu.Framework.Development; using osu.Framework.Extensions; @@ -21,12 +22,15 @@ using osu.Framework.Input; using osu.Framework.IO.Stores; using osu.Framework.Logging; using osu.Framework.Platform; +using osu.Framework.Timing; using osu.Game.Audio; using osu.Game.Beatmaps; +using osu.Game.Beatmaps.ControlPoints; using osu.Game.Beatmaps.Formats; using osu.Game.Configuration; using osu.Game.Database; using osu.Game.Graphics; +using osu.Game.Graphics.Containers; using osu.Game.Graphics.Cursor; using osu.Game.Input; using osu.Game.Input.Bindings; @@ -52,7 +56,7 @@ namespace osu.Game /// Unlike , this class will not load any kind of UI, allowing it to be used /// for provide dependencies to test cases without interfering with them. /// - public partial class OsuGameBase : Framework.Game, ICanAcceptFiles + public partial class OsuGameBase : Framework.Game, ICanAcceptFiles, IBeatSyncProvider { public const string OSU_PROTOCOL = "osu://"; @@ -552,5 +556,9 @@ namespace osu.Game if (Host != null) Host.ExceptionThrown -= onExceptionThrown; } + + ControlPointInfo IBeatSyncProvider.ControlPoints => Beatmap.Value.Beatmap.ControlPointInfo; + IClock IBeatSyncProvider.Clock => Beatmap.Value.TrackLoaded ? Beatmap.Value.Track : (IClock)null; + ChannelAmplitudes? IBeatSyncProvider.Amplitudes => Beatmap.Value.TrackLoaded ? Beatmap.Value.Track.CurrentAmplitudes : (ChannelAmplitudes?)null; } } diff --git a/osu.Game/Rulesets/Mods/MetronomeBeat.cs b/osu.Game/Rulesets/Mods/MetronomeBeat.cs index c7a8b02130..149af1e30a 100644 --- a/osu.Game/Rulesets/Mods/MetronomeBeat.cs +++ b/osu.Game/Rulesets/Mods/MetronomeBeat.cs @@ -36,7 +36,7 @@ namespace osu.Game.Rulesets.Mods int timeSignature = timingPoint.TimeSignature.Numerator; // play metronome from one measure before the first object. - if (BeatSyncClock.CurrentTime < firstHitTime - timingPoint.BeatLength * timeSignature) + if (BeatSyncSource.Clock?.CurrentTime < firstHitTime - timingPoint.BeatLength * timeSignature) return; sample.Frequency.Value = beatIndex % timeSignature == 0 ? 1 : 0.5f; diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 3a2b195eed..a50a70374b 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -8,6 +8,7 @@ using System.Linq; using JetBrains.Annotations; using osu.Framework; using osu.Framework.Allocation; +using osu.Framework.Audio.Track; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -19,11 +20,14 @@ using osu.Framework.Input.Events; using osu.Framework.Logging; using osu.Framework.Platform; using osu.Framework.Screens; +using osu.Framework.Timing; using osu.Game.Audio; using osu.Game.Beatmaps; +using osu.Game.Beatmaps.ControlPoints; using osu.Game.Configuration; using osu.Game.Database; using osu.Game.Graphics; +using osu.Game.Graphics.Containers; using osu.Game.Graphics.Cursor; using osu.Game.Graphics.UserInterface; using osu.Game.Input.Bindings; @@ -53,7 +57,7 @@ namespace osu.Game.Screens.Edit { [Cached(typeof(IBeatSnapProvider))] [Cached] - public class Editor : ScreenWithBeatmapBackground, IKeyBindingHandler, IKeyBindingHandler, IBeatSnapProvider, ISamplePlaybackDisabler + public class Editor : ScreenWithBeatmapBackground, IKeyBindingHandler, IKeyBindingHandler, IBeatSnapProvider, ISamplePlaybackDisabler, IBeatSyncProvider { public override float BackgroundParallaxAmount => 0.1f; @@ -954,5 +958,9 @@ namespace osu.Game.Screens.Edit public double GetBeatLengthAtTime(double referenceTime) => editorBeatmap.GetBeatLengthAtTime(referenceTime); public int BeatDivisor => beatDivisor.Value; + + ControlPointInfo IBeatSyncProvider.ControlPoints => editorBeatmap.ControlPointInfo; + IClock IBeatSyncProvider.Clock => clock; + ChannelAmplitudes? IBeatSyncProvider.Amplitudes => Beatmap.Value.TrackLoaded ? Beatmap.Value.Track.CurrentAmplitudes : (ChannelAmplitudes?)null; } } diff --git a/osu.Game/Screens/Edit/Timing/MetronomeDisplay.cs b/osu.Game/Screens/Edit/Timing/MetronomeDisplay.cs index b59865ac1a..7b0b8440e6 100644 --- a/osu.Game/Screens/Edit/Timing/MetronomeDisplay.cs +++ b/osu.Game/Screens/Edit/Timing/MetronomeDisplay.cs @@ -31,9 +31,6 @@ namespace osu.Game.Screens.Edit.Timing [Resolved] private OverlayColourProvider overlayColourProvider { get; set; } - [Resolved] - private EditorBeatmap editorBeatmap { get; set; } - [BackgroundDependencyLoader] private void load() { @@ -216,7 +213,10 @@ namespace osu.Game.Screens.Edit.Timing { base.Update(); - timingPoint = editorBeatmap.ControlPointInfo.TimingPointAt(BeatSyncClock.CurrentTime); + if (BeatSyncSource.ControlPoints == null || BeatSyncSource.Clock == null) + return; + + timingPoint = BeatSyncSource.ControlPoints.TimingPointAt(BeatSyncSource.Clock.CurrentTime); if (beatLength != timingPoint.BeatLength) { @@ -230,7 +230,7 @@ namespace osu.Game.Screens.Edit.Timing this.TransformBindableTo(interpolatedBpm, (int)Math.Round(timingPoint.BPM), 600, Easing.OutQuint); } - if (BeatSyncClock?.IsRunning != true && isSwinging) + if (BeatSyncSource.Clock?.IsRunning != true && isSwinging) { swing.ClearTransforms(true); @@ -258,7 +258,7 @@ namespace osu.Game.Screens.Edit.Timing float currentAngle = swing.Rotation; float targetAngle = currentAngle > 0 ? -angle : angle; - swing.RotateTo(targetAngle, beatLength, Easing.InOutQuad); + swing.RotateTo(targetAngle, beatLength, Easing.InOutSine); if (currentAngle != 0 && Math.Abs(currentAngle - targetAngle) > angle * 1.8f && isSwinging) { diff --git a/osu.Game/Screens/Play/MasterGameplayClockContainer.cs b/osu.Game/Screens/Play/MasterGameplayClockContainer.cs index ea43fb1546..4ca5541362 100644 --- a/osu.Game/Screens/Play/MasterGameplayClockContainer.cs +++ b/osu.Game/Screens/Play/MasterGameplayClockContainer.cs @@ -12,8 +12,10 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Timing; using osu.Game.Beatmaps; +using osu.Game.Beatmaps.ControlPoints; using osu.Game.Configuration; using osu.Game.Database; +using osu.Game.Graphics.Containers; namespace osu.Game.Screens.Play { @@ -27,7 +29,7 @@ namespace osu.Game.Screens.Play /// /// This is intended to be used as a single controller for gameplay, or as a reference source for other s. /// - public class MasterGameplayClockContainer : GameplayClockContainer + public class MasterGameplayClockContainer : GameplayClockContainer, IBeatSyncProvider { /// /// Duration before gameplay start time required before skip button displays. @@ -250,6 +252,10 @@ namespace osu.Game.Screens.Play removeSourceClockAdjustments(); } + ControlPointInfo IBeatSyncProvider.ControlPoints => beatmap.Beatmap.ControlPointInfo; + IClock IBeatSyncProvider.Clock => GameplayClock; + ChannelAmplitudes? IBeatSyncProvider.Amplitudes => beatmap.TrackLoaded ? beatmap.Track.CurrentAmplitudes : (ChannelAmplitudes?)null; + private class HardwareCorrectionOffsetClock : FramedOffsetClock { private readonly BindableDouble pauseRateAdjust; diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 51c1e6b43b..cbd9b03c32 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -8,6 +8,7 @@ using System.Threading.Tasks; using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Audio.Sample; +using osu.Framework.Audio.Track; using osu.Framework.Bindables; using osu.Framework.Extensions; using osu.Framework.Graphics; @@ -16,8 +17,10 @@ using osu.Framework.Input.Events; using osu.Framework.Logging; using osu.Framework.Screens; using osu.Framework.Threading; +using osu.Framework.Timing; using osu.Game.Audio; using osu.Game.Beatmaps; +using osu.Game.Beatmaps.ControlPoints; using osu.Game.Configuration; using osu.Game.Graphics.Containers; using osu.Game.IO.Archives; @@ -38,7 +41,7 @@ using osuTK.Graphics; namespace osu.Game.Screens.Play { [Cached] - public abstract class Player : ScreenWithBeatmapBackground, ISamplePlaybackDisabler, ILocalUserPlayInfo + public abstract class Player : ScreenWithBeatmapBackground, ISamplePlaybackDisabler, ILocalUserPlayInfo, IBeatSyncProvider { /// /// The delay upon completion of the beatmap before displaying the results screen. @@ -1108,5 +1111,9 @@ namespace osu.Game.Screens.Play IBindable ISamplePlaybackDisabler.SamplePlaybackDisabled => samplePlaybackDisabled; IBindable ILocalUserPlayInfo.IsPlaying => LocalUserPlaying; + + ControlPointInfo IBeatSyncProvider.ControlPoints => Beatmap.Value.Beatmap.ControlPointInfo; + IClock IBeatSyncProvider.Clock => GameplayClockContainer.GameplayClock; + ChannelAmplitudes? IBeatSyncProvider.Amplitudes => Beatmap.Value.TrackLoaded ? Beatmap.Value.Track.CurrentAmplitudes : (ChannelAmplitudes?)null; } } From 3a7233bd6ed5e2a3784de1825ac58ea1ec7ae840 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 22 May 2022 22:49:24 +0900 Subject: [PATCH 125/395] Move interface to more appropriate namespace --- .../{Graphics/Containers => Beatmaps}/IBeatSyncProvider.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) rename osu.Game/{Graphics/Containers => Beatmaps}/IBeatSyncProvider.cs (91%) diff --git a/osu.Game/Graphics/Containers/IBeatSyncProvider.cs b/osu.Game/Beatmaps/IBeatSyncProvider.cs similarity index 91% rename from osu.Game/Graphics/Containers/IBeatSyncProvider.cs rename to osu.Game/Beatmaps/IBeatSyncProvider.cs index d5be50297c..573f06ef9f 100644 --- a/osu.Game/Graphics/Containers/IBeatSyncProvider.cs +++ b/osu.Game/Beatmaps/IBeatSyncProvider.cs @@ -7,8 +7,9 @@ using osu.Framework.Allocation; using osu.Framework.Audio.Track; using osu.Framework.Timing; using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Graphics.Containers; -namespace osu.Game.Graphics.Containers +namespace osu.Game.Beatmaps { /// /// Provides various data sources which allow for synchronising visuals to a known beat. From 007582afb9082782b1c18a61401a673198fe3370 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 22 May 2022 23:55:33 +0900 Subject: [PATCH 126/395] Remove unused usings resulting from namespace move --- osu.Game/OsuGameBase.cs | 1 - osu.Game/Screens/Edit/Editor.cs | 1 - osu.Game/Screens/Play/MasterGameplayClockContainer.cs | 1 - 3 files changed, 3 deletions(-) diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 52052efd5d..ed2bfbc6e3 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -30,7 +30,6 @@ using osu.Game.Beatmaps.Formats; using osu.Game.Configuration; using osu.Game.Database; using osu.Game.Graphics; -using osu.Game.Graphics.Containers; using osu.Game.Graphics.Cursor; using osu.Game.Input; using osu.Game.Input.Bindings; diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index a50a70374b..0bb3f51903 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -27,7 +27,6 @@ using osu.Game.Beatmaps.ControlPoints; using osu.Game.Configuration; using osu.Game.Database; using osu.Game.Graphics; -using osu.Game.Graphics.Containers; using osu.Game.Graphics.Cursor; using osu.Game.Graphics.UserInterface; using osu.Game.Input.Bindings; diff --git a/osu.Game/Screens/Play/MasterGameplayClockContainer.cs b/osu.Game/Screens/Play/MasterGameplayClockContainer.cs index 4ca5541362..d87d57ec49 100644 --- a/osu.Game/Screens/Play/MasterGameplayClockContainer.cs +++ b/osu.Game/Screens/Play/MasterGameplayClockContainer.cs @@ -15,7 +15,6 @@ using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Configuration; using osu.Game.Database; -using osu.Game.Graphics.Containers; namespace osu.Game.Screens.Play { From c9ea87e6be1885fdb18bcc4e93bf21b5d0ad11e1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 23 May 2022 00:03:42 +0900 Subject: [PATCH 127/395] Revert easing for now --- osu.Game/Screens/Edit/Timing/MetronomeDisplay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Timing/MetronomeDisplay.cs b/osu.Game/Screens/Edit/Timing/MetronomeDisplay.cs index 7b0b8440e6..4dd7a75d4a 100644 --- a/osu.Game/Screens/Edit/Timing/MetronomeDisplay.cs +++ b/osu.Game/Screens/Edit/Timing/MetronomeDisplay.cs @@ -258,7 +258,7 @@ namespace osu.Game.Screens.Edit.Timing float currentAngle = swing.Rotation; float targetAngle = currentAngle > 0 ? -angle : angle; - swing.RotateTo(targetAngle, beatLength, Easing.InOutSine); + swing.RotateTo(targetAngle, beatLength, Easing.InOutQuad); if (currentAngle != 0 && Math.Abs(currentAngle - targetAngle) > angle * 1.8f && isSwinging) { From 267bef959fd7049dcf80557196cd211e3f81686f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 23 May 2022 02:00:04 +0900 Subject: [PATCH 128/395] Remove unnecessary cache type specification MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bartłomiej Dach --- osu.Game/Beatmaps/IBeatSyncProvider.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/IBeatSyncProvider.cs b/osu.Game/Beatmaps/IBeatSyncProvider.cs index 573f06ef9f..cc1cfc3cb5 100644 --- a/osu.Game/Beatmaps/IBeatSyncProvider.cs +++ b/osu.Game/Beatmaps/IBeatSyncProvider.cs @@ -15,7 +15,7 @@ namespace osu.Game.Beatmaps /// Provides various data sources which allow for synchronising visuals to a known beat. /// Primarily intended for use with . /// - [Cached(typeof(IBeatSyncProvider))] + [Cached] public interface IBeatSyncProvider { ControlPointInfo? ControlPoints { get; } From 9c3d0dafbdba852857197bf13dbc2879328dceb3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 23 May 2022 02:00:20 +0900 Subject: [PATCH 129/395] Remove implementation at `Player` level Turns out this isn't required in the end due to implementation at `MasterGameplayClockContainer`. --- osu.Game/Screens/Play/Player.cs | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index cbd9b03c32..51c1e6b43b 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -8,7 +8,6 @@ using System.Threading.Tasks; using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Audio.Sample; -using osu.Framework.Audio.Track; using osu.Framework.Bindables; using osu.Framework.Extensions; using osu.Framework.Graphics; @@ -17,10 +16,8 @@ using osu.Framework.Input.Events; using osu.Framework.Logging; using osu.Framework.Screens; using osu.Framework.Threading; -using osu.Framework.Timing; using osu.Game.Audio; using osu.Game.Beatmaps; -using osu.Game.Beatmaps.ControlPoints; using osu.Game.Configuration; using osu.Game.Graphics.Containers; using osu.Game.IO.Archives; @@ -41,7 +38,7 @@ using osuTK.Graphics; namespace osu.Game.Screens.Play { [Cached] - public abstract class Player : ScreenWithBeatmapBackground, ISamplePlaybackDisabler, ILocalUserPlayInfo, IBeatSyncProvider + public abstract class Player : ScreenWithBeatmapBackground, ISamplePlaybackDisabler, ILocalUserPlayInfo { /// /// The delay upon completion of the beatmap before displaying the results screen. @@ -1111,9 +1108,5 @@ namespace osu.Game.Screens.Play IBindable ISamplePlaybackDisabler.SamplePlaybackDisabled => samplePlaybackDisabled; IBindable ILocalUserPlayInfo.IsPlaying => LocalUserPlaying; - - ControlPointInfo IBeatSyncProvider.ControlPoints => Beatmap.Value.Beatmap.ControlPointInfo; - IClock IBeatSyncProvider.Clock => GameplayClockContainer.GameplayClock; - ChannelAmplitudes? IBeatSyncProvider.Amplitudes => Beatmap.Value.TrackLoaded ? Beatmap.Value.Track.CurrentAmplitudes : (ChannelAmplitudes?)null; } } From 0bef2ca7526295767db063b0b9e92035411cda83 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 22 May 2022 19:27:20 +0200 Subject: [PATCH 130/395] Adjust test slightly * Import `osuTK.Input` instead of using full qualified name * Use some more straightforward assertions --- .../TestSceneMultiplayerMatchSubScreen.cs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs index dd281e7738..7ae81c9800 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs @@ -6,6 +6,7 @@ using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Extensions; +using osu.Framework.Graphics.Containers; using osu.Framework.Platform; using osu.Framework.Screens; using osu.Framework.Testing; @@ -28,6 +29,7 @@ using osu.Game.Screens.OnlinePlay.Multiplayer.Match; using osu.Game.Screens.OnlinePlay.Multiplayer.Participants; using osu.Game.Tests.Beatmaps; using osu.Game.Tests.Resources; +using osuTK.Input; namespace osu.Game.Tests.Visual.Multiplayer { @@ -194,10 +196,9 @@ namespace osu.Game.Tests.Visual.Multiplayer AddUntilStep("wait for join", () => RoomJoined); - AddStep("press toggle mod select key", () => InputManager.Key(osuTK.Input.Key.F1)); + AddStep("press toggle mod select key", () => InputManager.Key(Key.F1)); - AddUntilStep("mod select contents loaded", - () => this.ChildrenOfType().Any() && this.ChildrenOfType().All(col => col.IsLoaded && col.ItemsLoaded)); + AddUntilStep("mod select shown", () => this.ChildrenOfType().Single().State.Value == Visibility.Visible); } [Test] @@ -214,10 +215,10 @@ namespace osu.Game.Tests.Visual.Multiplayer AddUntilStep("wait for join", () => RoomJoined); - AddStep("press toggle mod select key", () => InputManager.Key(osuTK.Input.Key.F1)); + AddStep("press toggle mod select key", () => InputManager.Key(Key.F1)); - AddAssert("mod select contents not loaded", - () => !(this.ChildrenOfType().Any() && this.ChildrenOfType().All(col => col.IsLoaded && col.ItemsLoaded))); + AddWaitStep("wait some", 3); + AddAssert("mod select not shown", () => this.ChildrenOfType().Single().State.Value == Visibility.Hidden); } [Test] From afbb1fa750e8560481fbf43509ae166e822d87ff Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 23 May 2022 14:06:00 +0900 Subject: [PATCH 131/395] Fix tournament user stat population potentially using wrong ruleset `OsuGameBase.Ruleset` is bound [late](https://github.com/ppy/osu/blob/7d0470794bcd454d3a93954ec4fe3415933627ef/osu.Game.Tournament/TournamentGameBase.cs#L169) so we must use the ladder one during user retrieval. Closes https://github.com/ppy/osu/issues/18363. --- osu.Game.Tournament/TournamentGameBase.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tournament/TournamentGameBase.cs b/osu.Game.Tournament/TournamentGameBase.cs index 363baccb37..6ae0312cce 100644 --- a/osu.Game.Tournament/TournamentGameBase.cs +++ b/osu.Game.Tournament/TournamentGameBase.cs @@ -259,7 +259,7 @@ namespace osu.Game.Tournament public void PopulateUser(APIUser user, Action success = null, Action failure = null, bool immediate = false) { - var req = new GetUserRequest(user.Id, Ruleset.Value); + var req = new GetUserRequest(user.Id, ladder.Ruleset.Value); if (immediate) { From 63a3829768be608c125076767538910104b4fe89 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 23 May 2022 14:19:25 +0900 Subject: [PATCH 132/395] Split out SingleKeyStamina class --- .../Difficulty/Skills/SingleKeyStamina.cs | 42 +++++++++++++++++++ .../Difficulty/Skills/Stamina.cs | 34 --------------- 2 files changed, 42 insertions(+), 34 deletions(-) create mode 100644 osu.Game.Rulesets.Taiko/Difficulty/Skills/SingleKeyStamina.cs diff --git a/osu.Game.Rulesets.Taiko/Difficulty/Skills/SingleKeyStamina.cs b/osu.Game.Rulesets.Taiko/Difficulty/Skills/SingleKeyStamina.cs new file mode 100644 index 0000000000..cabfd231d8 --- /dev/null +++ b/osu.Game.Rulesets.Taiko/Difficulty/Skills/SingleKeyStamina.cs @@ -0,0 +1,42 @@ +// 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.Rulesets.Difficulty.Preprocessing; +using osu.Game.Rulesets.Difficulty.Skills; + +namespace osu.Game.Rulesets.Taiko.Difficulty.Skills +{ + /// + /// Stamina of a single key, calculated based on repetition speed. + /// + public class SingleKeyStamina + { + private double? previousHitTime; + + /// + /// Similar to + /// + public double StrainValueOf(DifficultyHitObject current) + { + if (previousHitTime == null) + { + previousHitTime = current.StartTime; + return 0; + } + + double objectStrain = 0.5; + objectStrain += speedBonus(current.StartTime - previousHitTime.Value); + previousHitTime = current.StartTime; + return objectStrain; + } + + /// + /// Applies a speed bonus dependent on the time since the last hit performed using this key. + /// + /// The duration between the current and previous note hit using the same key. + private double speedBonus(double notePairDuration) + { + return 175 / (notePairDuration + 100); + } + } +} diff --git a/osu.Game.Rulesets.Taiko/Difficulty/Skills/Stamina.cs b/osu.Game.Rulesets.Taiko/Difficulty/Skills/Stamina.cs index 744dafd57e..32d8b70485 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/Skills/Stamina.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/Skills/Stamina.cs @@ -9,40 +9,6 @@ using osu.Game.Rulesets.Taiko.Objects; namespace osu.Game.Rulesets.Taiko.Difficulty.Skills { - /// - /// Stamina of a single key, calculated based on repetition speed. - /// - public class SingleKeyStamina - { - private double? previousHitTime; - - /// - /// Similar to - /// - public double StrainValueOf(DifficultyHitObject current) - { - if (previousHitTime == null) - { - previousHitTime = current.StartTime; - return 0; - } - - double objectStrain = 0.5; - objectStrain += speedBonus(current.StartTime - previousHitTime.Value); - previousHitTime = current.StartTime; - return objectStrain; - } - - /// - /// Applies a speed bonus dependent on the time since the last hit performed using this key. - /// - /// The duration between the current and previous note hit using the same key. - private double speedBonus(double notePairDuration) - { - return 175 / (notePairDuration + 100); - } - } - /// /// Calculates the stamina coefficient of taiko difficulty. /// From 6cfe35360a6ed2e666047dce18a45570b098041a Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 23 May 2022 14:36:06 +0900 Subject: [PATCH 133/395] Refactor key indexing --- .../Difficulty/Skills/Stamina.cs | 27 ++++++++++--------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Difficulty/Skills/Stamina.cs b/osu.Game.Rulesets.Taiko/Difficulty/Skills/Stamina.cs index 32d8b70485..61bcbfa59d 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/Skills/Stamina.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/Skills/Stamina.cs @@ -20,26 +20,27 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Skills protected override double SkillMultiplier => 1; protected override double StrainDecayBase => 0.4; - /// - /// Stamina of each individual keys, calculated based on repetition speed. - /// - private readonly SingleKeyStamina[] keyStamina = + private readonly SingleKeyStamina[] centreKeyStamina = { new SingleKeyStamina(), - new SingleKeyStamina(), + new SingleKeyStamina() + }; + + private readonly SingleKeyStamina[] rimKeyStamina = + { new SingleKeyStamina(), new SingleKeyStamina() }; /// - /// Current index to for a don hit. + /// Current index into for a centre hit. /// - private int donIndex = 1; + private int centreKeyIndex; /// - /// Current index to for a kat hit. + /// Current index into for a rim hit. /// - private int katIndex = 3; + private int rimKeyIndex; /// /// Creates a skill. @@ -59,12 +60,12 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Skills // Alternate key for the same color. if (current.HitType == HitType.Centre) { - donIndex = donIndex == 0 ? 1 : 0; - return keyStamina[donIndex]; + centreKeyIndex = (centreKeyIndex + 1) % 2; + return centreKeyStamina[centreKeyIndex]; } - katIndex = katIndex == 2 ? 3 : 2; - return keyStamina[katIndex]; + rimKeyIndex = (rimKeyIndex + 1) % 2; + return rimKeyStamina[rimKeyIndex]; } protected override double StrainValueOf(DifficultyHitObject current) From 88217e0c984b63a29e0c70bdf41372067743b7ea Mon Sep 17 00:00:00 2001 From: Susko3 Date: Mon, 23 May 2022 20:22:27 +0200 Subject: [PATCH 134/395] Adjust `ScrollDelta` usages to account for normalised `IsPrecise` values --- osu.Game/Overlays/Volume/VolumeMeter.cs | 2 +- osu.Game/Rulesets/Edit/DistancedHitObjectComposer.cs | 8 +++++--- osu.Game/Screens/Edit/Editor.cs | 2 +- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/osu.Game/Overlays/Volume/VolumeMeter.cs b/osu.Game/Overlays/Volume/VolumeMeter.cs index 46b8b35da2..929c362bd8 100644 --- a/osu.Game/Overlays/Volume/VolumeMeter.cs +++ b/osu.Game/Overlays/Volume/VolumeMeter.cs @@ -329,7 +329,7 @@ namespace osu.Game.Overlays.Volume if (isPrecise) { - scrollAccumulation += delta * adjust_step * 0.1; + scrollAccumulation += delta * adjust_step; while (Precision.AlmostBigger(Math.Abs(scrollAccumulation), precision)) { diff --git a/osu.Game/Rulesets/Edit/DistancedHitObjectComposer.cs b/osu.Game/Rulesets/Edit/DistancedHitObjectComposer.cs index 5e6d9dbe34..27a3685f89 100644 --- a/osu.Game/Rulesets/Edit/DistancedHitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/DistancedHitObjectComposer.cs @@ -26,6 +26,8 @@ namespace osu.Game.Rulesets.Edit public abstract class DistancedHitObjectComposer : HitObjectComposer, IDistanceSnapProvider, IScrollBindingHandler where TObject : HitObject { + private const float adjust_step = 0.1f; + public Bindable DistanceSpacingMultiplier { get; } = new BindableDouble(1.0) { MinValue = 0.1, @@ -61,7 +63,7 @@ namespace osu.Game.Rulesets.Edit Child = distanceSpacingSlider = new ExpandableSlider> { Current = { BindTarget = DistanceSpacingMultiplier }, - KeyboardStep = 0.1f, + KeyboardStep = adjust_step, } } }); @@ -93,7 +95,7 @@ namespace osu.Game.Rulesets.Edit { case GlobalAction.EditorIncreaseDistanceSpacing: case GlobalAction.EditorDecreaseDistanceSpacing: - return adjustDistanceSpacing(e.Action, 0.1f); + return adjustDistanceSpacing(e.Action, adjust_step); } return false; @@ -109,7 +111,7 @@ namespace osu.Game.Rulesets.Edit { case GlobalAction.EditorIncreaseDistanceSpacing: case GlobalAction.EditorDecreaseDistanceSpacing: - return adjustDistanceSpacing(e.Action, e.ScrollAmount * (e.IsPrecise ? 0.01f : 0.1f)); + return adjustDistanceSpacing(e.Action, e.ScrollAmount * adjust_step); } return false; diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 0bb3f51903..eaacf74af3 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -540,7 +540,7 @@ namespace osu.Game.Screens.Edit if (scrollAccumulation != 0 && Math.Sign(scrollAccumulation) != scrollDirection) scrollAccumulation = scrollDirection * (precision - Math.Abs(scrollAccumulation)); - scrollAccumulation += scrollComponent * (e.IsPrecise ? 0.1 : 1); + scrollAccumulation += scrollComponent; // because we are doing snapped seeking, we need to add up precise scrolls until they accumulate to an arbitrary cut-off. while (Math.Abs(scrollAccumulation) >= precision) From 58d39734d07c0fdd90c2310b68186b82df4ac6c0 Mon Sep 17 00:00:00 2001 From: Jai Sharma Date: Mon, 23 May 2022 20:23:50 +0100 Subject: [PATCH 135/395] Integrate `ChatOverlayV2` into main game --- osu.Game.Tests/Visual/Navigation/TestSceneOsuGame.cs | 2 +- osu.Game.Tests/Visual/Online/TestSceneChatOverlayV2.cs | 2 +- osu.Game/Configuration/OsuConfigManager.cs | 2 +- osu.Game/OsuGame.cs | 4 ++-- osu.Game/Overlays/ChatOverlayV2.cs | 10 ++++++++-- .../Profile/Header/Components/MessageUserButton.cs | 2 +- osu.Game/Overlays/Toolbar/ToolbarChatButton.cs | 2 +- 7 files changed, 15 insertions(+), 9 deletions(-) diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneOsuGame.cs b/osu.Game.Tests/Visual/Navigation/TestSceneOsuGame.cs index e4871f611e..c8ea692bb2 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSceneOsuGame.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSceneOsuGame.cs @@ -40,7 +40,7 @@ namespace osu.Game.Tests.Visual.Navigation typeof(DashboardOverlay), typeof(NewsOverlay), typeof(ChannelManager), - typeof(ChatOverlay), + typeof(ChatOverlayV2), typeof(SettingsOverlay), typeof(UserProfileOverlay), typeof(BeatmapSetOverlay), diff --git a/osu.Game.Tests/Visual/Online/TestSceneChatOverlayV2.cs b/osu.Game.Tests/Visual/Online/TestSceneChatOverlayV2.cs index e27db00003..7268dd31f6 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneChatOverlayV2.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneChatOverlayV2.cs @@ -63,7 +63,7 @@ namespace osu.Game.Tests.Visual.Online Children = new Drawable[] { channelManager, - chatOverlay = new TestChatOverlayV2 { RelativeSizeAxes = Axes.Both }, + chatOverlay = new TestChatOverlayV2(), }, }; }); diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index 20d555c16c..69e7dee1a5 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -46,7 +46,7 @@ namespace osu.Game.Configuration SetDefault(OsuSetting.RandomSelectAlgorithm, RandomSelectAlgorithm.RandomPermutation); - SetDefault(OsuSetting.ChatDisplayHeight, ChatOverlay.DEFAULT_HEIGHT, 0.2f, 1f); + SetDefault(OsuSetting.ChatDisplayHeight, ChatOverlayV2.DEFAULT_HEIGHT, 0.2f, 1f); SetDefault(OsuSetting.BeatmapListingCardSize, BeatmapCardSize.Normal); diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 785881d97a..402bd94f31 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -75,7 +75,7 @@ namespace osu.Game public Toolbar Toolbar; - private ChatOverlay chatOverlay; + private ChatOverlayV2 chatOverlay; private ChannelManager channelManager; @@ -848,7 +848,7 @@ namespace osu.Game loadComponentSingleFile(news = new NewsOverlay(), overlayContent.Add, true); var rankingsOverlay = loadComponentSingleFile(new RankingsOverlay(), overlayContent.Add, true); loadComponentSingleFile(channelManager = new ChannelManager(), AddInternal, true); - loadComponentSingleFile(chatOverlay = new ChatOverlay(), overlayContent.Add, true); + loadComponentSingleFile(chatOverlay = new ChatOverlayV2(), overlayContent.Add, true); loadComponentSingleFile(new MessageNotifier(), AddInternal, true); loadComponentSingleFile(Settings = new SettingsOverlay(), leftFloatingOverlayContent.Add, true); loadComponentSingleFile(changelogOverlay = new ChangelogOverlay(), overlayContent.Add, true); diff --git a/osu.Game/Overlays/ChatOverlayV2.cs b/osu.Game/Overlays/ChatOverlayV2.cs index ef479ea21b..441042432a 100644 --- a/osu.Game/Overlays/ChatOverlayV2.cs +++ b/osu.Game/Overlays/ChatOverlayV2.cs @@ -47,8 +47,9 @@ namespace osu.Game.Overlays private bool isDraggingTopBar; private float dragStartChatHeight; + public const float DEFAULT_HEIGHT = 0.4f; + private const int transition_length = 500; - private const float default_chat_height = 0.4f; private const float top_bar_height = 40; private const float side_bar_width = 190; private const float chat_bar_height = 60; @@ -70,7 +71,7 @@ namespace osu.Game.Overlays public ChatOverlayV2() { - Height = default_chat_height; + Height = DEFAULT_HEIGHT; Masking = true; @@ -82,6 +83,7 @@ namespace osu.Game.Overlays Margin = new MarginPadding { Bottom = -corner_radius }; Padding = new MarginPadding { Bottom = corner_radius }; + RelativeSizeAxes = Axes.Both; Anchor = Anchor.BottomCentre; Origin = Anchor.BottomCentre; } @@ -294,6 +296,10 @@ namespace osu.Game.Overlays }); } } + + // Mark channel as read when channel switched + if (newChannel.Messages.Any()) + channelManager.MarkChannelAsRead(newChannel); } protected virtual ChatOverlayDrawableChannel CreateDrawableChannel(Channel newChannel) => new ChatOverlayDrawableChannel(newChannel); diff --git a/osu.Game/Overlays/Profile/Header/Components/MessageUserButton.cs b/osu.Game/Overlays/Profile/Header/Components/MessageUserButton.cs index e3dc5f818a..eafb453f75 100644 --- a/osu.Game/Overlays/Profile/Header/Components/MessageUserButton.cs +++ b/osu.Game/Overlays/Profile/Header/Components/MessageUserButton.cs @@ -27,7 +27,7 @@ namespace osu.Game.Overlays.Profile.Header.Components private UserProfileOverlay userOverlay { get; set; } [Resolved(CanBeNull = true)] - private ChatOverlay chatOverlay { get; set; } + private ChatOverlayV2 chatOverlay { get; set; } [Resolved] private IAPIProvider apiProvider { get; set; } diff --git a/osu.Game/Overlays/Toolbar/ToolbarChatButton.cs b/osu.Game/Overlays/Toolbar/ToolbarChatButton.cs index 2d3b33e9bc..20f405aae2 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarChatButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarChatButton.cs @@ -17,7 +17,7 @@ namespace osu.Game.Overlays.Toolbar } [BackgroundDependencyLoader(true)] - private void load(ChatOverlay chat) + private void load(ChatOverlayV2 chat) { StateContainer = chat; } From d2a49ca266087d5342fb1c9aa65b45db9204a7a1 Mon Sep 17 00:00:00 2001 From: Jai Sharma Date: Mon, 23 May 2022 20:24:17 +0100 Subject: [PATCH 136/395] Use `ChatOverlayV2` in message notifier and tests --- osu.Game.Tests/Visual/Online/TestSceneMessageNotifier.cs | 2 +- osu.Game/Online/Chat/MessageNotifier.cs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneMessageNotifier.cs b/osu.Game.Tests/Visual/Online/TestSceneMessageNotifier.cs index 79f62a16e3..46f426597a 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneMessageNotifier.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneMessageNotifier.cs @@ -208,7 +208,7 @@ namespace osu.Game.Tests.Visual.Online }; [Cached] - public ChatOverlay ChatOverlay { get; } = new ChatOverlay(); + public ChatOverlayV2 ChatOverlay { get; } = new ChatOverlayV2(); private readonly MessageNotifier messageNotifier = new MessageNotifier(); diff --git a/osu.Game/Online/Chat/MessageNotifier.cs b/osu.Game/Online/Chat/MessageNotifier.cs index ca6082e19b..cea3e321fa 100644 --- a/osu.Game/Online/Chat/MessageNotifier.cs +++ b/osu.Game/Online/Chat/MessageNotifier.cs @@ -27,7 +27,7 @@ namespace osu.Game.Online.Chat private INotificationOverlay notifications { get; set; } [Resolved] - private ChatOverlay chatOverlay { get; set; } + private ChatOverlayV2 chatOverlay { get; set; } [Resolved] private ChannelManager channelManager { get; set; } @@ -170,7 +170,7 @@ namespace osu.Game.Online.Chat public override bool IsImportant => false; [BackgroundDependencyLoader] - private void load(OsuColour colours, ChatOverlay chatOverlay, INotificationOverlay notificationOverlay) + private void load(OsuColour colours, ChatOverlayV2 chatOverlay, INotificationOverlay notificationOverlay) { IconBackground.Colour = colours.PurpleDark; From 606f3b2bd18bc2ed780441a02f6698ba4bcbe6f0 Mon Sep 17 00:00:00 2001 From: Jai Sharma Date: Mon, 23 May 2022 20:24:49 +0100 Subject: [PATCH 137/395] Use `ChatOverlayV2` in screen navigation tests --- .../Visual/Navigation/TestScenePerformFromScreen.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Navigation/TestScenePerformFromScreen.cs b/osu.Game.Tests/Visual/Navigation/TestScenePerformFromScreen.cs index 2ce914ba3d..5d0116f80e 100644 --- a/osu.Game.Tests/Visual/Navigation/TestScenePerformFromScreen.cs +++ b/osu.Game.Tests/Visual/Navigation/TestScenePerformFromScreen.cs @@ -86,9 +86,9 @@ namespace osu.Game.Tests.Visual.Navigation [Test] public void TestOverlaysAlwaysClosed() { - ChatOverlay chat = null; + ChatOverlayV2 chat = null; AddUntilStep("is at menu", () => Game.ScreenStack.CurrentScreen is MainMenu); - AddUntilStep("wait for chat load", () => (chat = Game.ChildrenOfType().SingleOrDefault()) != null); + AddUntilStep("wait for chat load", () => (chat = Game.ChildrenOfType().SingleOrDefault()) != null); AddStep("show chat", () => InputManager.Key(Key.F8)); From 60b10fca4ec969997f77861beb12d724f0f15297 Mon Sep 17 00:00:00 2001 From: Jai Sharma Date: Mon, 23 May 2022 20:25:01 +0100 Subject: [PATCH 138/395] Remove redundant caching of overlays in `ChatLink` test --- osu.Game.Tests/Visual/Online/TestSceneChatLink.cs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneChatLink.cs b/osu.Game.Tests/Visual/Online/TestSceneChatLink.cs index 6818147da4..a28de3be1e 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneChatLink.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneChatLink.cs @@ -12,7 +12,6 @@ using osu.Framework.Graphics.Containers; using osu.Game.Graphics; using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Chat; -using osu.Game.Overlays; using osu.Game.Overlays.Chat; using osuTK.Graphics; @@ -22,12 +21,10 @@ namespace osu.Game.Tests.Visual.Online public class TestSceneChatLink : OsuTestScene { private readonly TestChatLineContainer textContainer; - private readonly DialogOverlay dialogOverlay; private Color4 linkColour; public TestSceneChatLink() { - Add(dialogOverlay = new DialogOverlay { Depth = float.MinValue }); Add(textContainer = new TestChatLineContainer { Padding = new MarginPadding { Left = 20, Right = 20 }, @@ -47,9 +44,6 @@ namespace osu.Game.Tests.Visual.Online availableChannels.Add(new Channel { Name = "#english" }); availableChannels.Add(new Channel { Name = "#japanese" }); Dependencies.Cache(chatManager); - - Dependencies.Cache(new ChatOverlay()); - Dependencies.CacheAs(dialogOverlay); } [SetUp] From e990a247230d086f14a88e3976230055c5fd331a Mon Sep 17 00:00:00 2001 From: Jai Sharma Date: Mon, 23 May 2022 20:40:01 +0100 Subject: [PATCH 139/395] Remove redundant installation of `libavformat` in Linux CI runs Linux `libavformat` native libraries are now packaged with framework. --- .github/workflows/ci.yml | 9 --------- 1 file changed, 9 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1d5c565d60..320197b88e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -78,15 +78,6 @@ jobs: with: dotnet-version: "6.0.x" - # FIXME: libavformat is not included in Ubuntu. Let's fix that. - # https://github.com/ppy/osu-framework/issues/4349 - # Remove this once https://github.com/actions/virtual-environments/issues/3306 has been resolved. - - name: Install libavformat-dev - if: ${{matrix.os.fullname == 'ubuntu-latest'}} - run: | - sudo apt-get update && \ - sudo apt-get -y install libavformat-dev - - name: Compile run: dotnet build -c Debug -warnaserror osu.Desktop.slnf From 14f14b431154f9f86d14f794ad64d60e1bd9b76a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 23 May 2022 23:37:05 +0200 Subject: [PATCH 140/395] Add failing test case for back button behaviour in song select --- .../Navigation/TestSceneScreenNavigation.cs | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs index a3e0caedb9..256f4a7423 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs @@ -8,9 +8,11 @@ using osu.Framework.Allocation; using osu.Framework.Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.UserInterface; using osu.Framework.Screens; using osu.Framework.Testing; using osu.Game.Beatmaps; +using osu.Game.Collections; using osu.Game.Configuration; using osu.Game.Graphics.UserInterface; using osu.Game.Online.Leaderboards; @@ -54,6 +56,39 @@ namespace osu.Game.Tests.Visual.Navigation exitViaEscapeAndConfirm(); } + [Test] + public void TestSongSelectBackActionHandling() + { + TestPlaySongSelect songSelect = null; + + PushAndConfirm(() => songSelect = new TestPlaySongSelect()); + + AddStep("set filter", () => songSelect.ChildrenOfType().Single().Current.Value = "test"); + AddStep("press back", () => InputManager.Click(MouseButton.Button1)); + + AddAssert("still at song select", () => Game.ScreenStack.CurrentScreen == songSelect); + AddAssert("filter cleared", () => string.IsNullOrEmpty(songSelect.ChildrenOfType().Single().Current.Value)); + + AddStep("set filter again", () => songSelect.ChildrenOfType().Single().Current.Value = "test"); + AddStep("open collections dropdown", () => + { + InputManager.MoveMouseTo(songSelect.ChildrenOfType().Single()); + InputManager.Click(MouseButton.Left); + }); + + AddStep("press back once", () => InputManager.Click(MouseButton.Button1)); + AddAssert("still at song select", () => Game.ScreenStack.CurrentScreen == songSelect); + AddAssert("collections dropdown closed", () => songSelect + .ChildrenOfType().Single() + .ChildrenOfType.DropdownMenu>().Single().State == MenuState.Closed); + + AddStep("press back a second time", () => InputManager.Click(MouseButton.Button1)); + AddAssert("filter cleared", () => string.IsNullOrEmpty(songSelect.ChildrenOfType().Single().Current.Value)); + + AddStep("press back a third time", () => InputManager.Click(MouseButton.Button1)); + ConfirmAtMainMenu(); + } + /// /// This tests that the F1 key will open the mod select overlay, and not be handled / blocked by the music controller (which has the same default binding /// but should be handled *after* song select). From 3847a586f110fec99f82645582a373da98776bff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 23 May 2022 23:37:40 +0200 Subject: [PATCH 141/395] Make `GlobalAction.Back` close all opened dropdown menus --- .../Graphics/UserInterface/OsuDropdown.cs | 22 ++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/osu.Game/Graphics/UserInterface/OsuDropdown.cs b/osu.Game/Graphics/UserInterface/OsuDropdown.cs index 4e391c8221..23e05c7ccc 100644 --- a/osu.Game/Graphics/UserInterface/OsuDropdown.cs +++ b/osu.Game/Graphics/UserInterface/OsuDropdown.cs @@ -12,9 +12,12 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.UserInterface; +using osu.Framework.Input.Bindings; +using osu.Framework.Input.Events; using osu.Framework.Localisation; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; +using osu.Game.Input.Bindings; using osu.Game.Overlays; using osuTK; using osuTK.Graphics; @@ -31,7 +34,7 @@ namespace osu.Game.Graphics.UserInterface #region OsuDropdownMenu - protected class OsuDropdownMenu : DropdownMenu + protected class OsuDropdownMenu : DropdownMenu, IKeyBindingHandler { public override bool HandleNonPositionalInput => State == MenuState.Open; @@ -275,6 +278,23 @@ namespace osu.Game.Graphics.UserInterface } #endregion + + public bool OnPressed(KeyBindingPressEvent e) + { + if (e.Repeat) return false; + + if (e.Action == GlobalAction.Back) + { + State = MenuState.Closed; + return true; + } + + return false; + } + + public void OnReleased(KeyBindingReleaseEvent e) + { + } } #endregion From 4cf8df16286337e07294a7e30565a204abccdcc0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 23 May 2022 23:58:12 +0200 Subject: [PATCH 142/395] Fix test inteference from `TestOverlayClosing` --- osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs index 256f4a7423..c1f5f110d1 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs @@ -522,6 +522,9 @@ namespace osu.Game.Tests.Visual.Navigation AddStep("move cursor to background", () => InputManager.MoveMouseTo(Game.ScreenSpaceDrawQuad.BottomRight)); AddStep("click left mouse button", () => InputManager.Click(MouseButton.Left)); AddAssert("now playing is hidden", () => nowPlayingOverlay.State.Value == Visibility.Hidden); + + // move the mouse firmly inside game bounds to avoid interfering with other tests. + AddStep("center cursor", () => InputManager.MoveMouseTo(Game.ScreenSpaceDrawQuad.Centre)); } [Test] From eae23784662e89b9d1bdc4d5b0710afe77d41303 Mon Sep 17 00:00:00 2001 From: apollo-dw <83023433+apollo-dw@users.noreply.github.com> Date: Tue, 24 May 2022 01:49:07 +0100 Subject: [PATCH 143/395] Add WIP to search filters --- osu.Game/Overlays/BeatmapListing/SearchCategory.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Overlays/BeatmapListing/SearchCategory.cs b/osu.Game/Overlays/BeatmapListing/SearchCategory.cs index d6ae41aba1..b52df6234f 100644 --- a/osu.Game/Overlays/BeatmapListing/SearchCategory.cs +++ b/osu.Game/Overlays/BeatmapListing/SearchCategory.cs @@ -32,6 +32,9 @@ namespace osu.Game.Overlays.BeatmapListing [Description("Pending & WIP")] Pending, + [LocalisableDescription(typeof(BeatmapsStrings), nameof(BeatmapsStrings.StatusWip))] + Wip, + [LocalisableDescription(typeof(BeatmapsStrings), nameof(BeatmapsStrings.StatusGraveyard))] Graveyard, From ed0b841df0ea3bd95315fd79f658e83446239e60 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Mon, 23 May 2022 13:19:01 -0700 Subject: [PATCH 144/395] Fix incorrect left/right clicking area of mod panels --- osu.Game/Overlays/Mods/ModColumn.cs | 2 +- osu.Game/Overlays/Mods/ModPanel.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/Mods/ModColumn.cs b/osu.Game/Overlays/Mods/ModColumn.cs index 9bb3f8bd9e..42f9daec4d 100644 --- a/osu.Game/Overlays/Mods/ModColumn.cs +++ b/osu.Game/Overlays/Mods/ModColumn.cs @@ -267,7 +267,7 @@ namespace osu.Game.Overlays.Mods { cancellationTokenSource?.Cancel(); - var panels = availableMods.Select(mod => CreateModPanel(mod).With(panel => panel.Shear = new Vector2(-ShearedOverlayContainer.SHEAR, 0))); + var panels = availableMods.Select(mod => CreateModPanel(mod).With(panel => panel.Shear = Vector2.Zero)); Task? loadTask; diff --git a/osu.Game/Overlays/Mods/ModPanel.cs b/osu.Game/Overlays/Mods/ModPanel.cs index 7010342bd8..358bdd3202 100644 --- a/osu.Game/Overlays/Mods/ModPanel.cs +++ b/osu.Game/Overlays/Mods/ModPanel.cs @@ -70,7 +70,7 @@ namespace osu.Game.Overlays.Mods Content.Masking = true; Content.CornerRadius = CORNER_RADIUS; Content.BorderThickness = 2; - Content.Shear = new Vector2(ShearedOverlayContainer.SHEAR, 0); + Shear = new Vector2(ShearedOverlayContainer.SHEAR, 0); Children = new Drawable[] { From 1f935cacf452b3a9f212551082d4a14e4a286972 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Mon, 23 May 2022 18:45:53 -0700 Subject: [PATCH 145/395] Add spotlighted beatmaps filter to beatmap listing --- osu.Game/Overlays/BeatmapListing/SearchGeneral.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Overlays/BeatmapListing/SearchGeneral.cs b/osu.Game/Overlays/BeatmapListing/SearchGeneral.cs index 9387020bdf..34ff5b9840 100644 --- a/osu.Game/Overlays/BeatmapListing/SearchGeneral.cs +++ b/osu.Game/Overlays/BeatmapListing/SearchGeneral.cs @@ -21,6 +21,9 @@ namespace osu.Game.Overlays.BeatmapListing [Description("Subscribed mappers")] Follows, + [LocalisableDescription(typeof(BeatmapsStrings), nameof(BeatmapsStrings.GeneralSpotlights))] + Spotlights, + [LocalisableDescription(typeof(BeatmapsStrings), nameof(BeatmapsStrings.GeneralFeaturedArtists))] [Description("Featured artists")] FeaturedArtists From 9259aa94f10897d044e590ad61e0aa6e5506bf7a Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 24 May 2022 11:47:42 +0900 Subject: [PATCH 146/395] Fix Catmull slider parsing with duplicate initial point --- .../Formats/LegacyBeatmapDecoderTest.cs | 19 +++++++++++++++++++ ...catmull-duplicate-initial-controlpoint.osu | 2 ++ .../Objects/Legacy/ConvertHitObjectParser.cs | 6 ++++-- 3 files changed, 25 insertions(+), 2 deletions(-) create mode 100644 osu.Game.Tests/Resources/catmull-duplicate-initial-controlpoint.osu diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs index 89baaf228d..e2d9910b82 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs @@ -898,5 +898,24 @@ namespace osu.Game.Tests.Beatmaps.Formats Assert.That(controlPoints[3].Type, Is.Null); } } + + [Test] + public void TestLegacyDuplicateInitialCatmullPointIsMerged() + { + var decoder = new LegacyBeatmapDecoder { ApplyOffsets = false }; + + using (var resStream = TestResources.OpenResource("catmull-duplicate-initial-controlpoint.osu")) + using (var stream = new LineBufferedReader(resStream)) + { + var decoded = decoder.Decode(stream); + var controlPoints = ((IHasPath)decoded.HitObjects[0]).Path.ControlPoints; + + Assert.That(controlPoints.Count, Is.EqualTo(4)); + Assert.That(controlPoints[0].Type, Is.EqualTo(PathType.Catmull)); + Assert.That(controlPoints[0].Position, Is.EqualTo(Vector2.Zero)); + Assert.That(controlPoints[1].Type, Is.Null); + Assert.That(controlPoints[1].Position, Is.Not.EqualTo(Vector2.Zero)); + } + } } } diff --git a/osu.Game.Tests/Resources/catmull-duplicate-initial-controlpoint.osu b/osu.Game.Tests/Resources/catmull-duplicate-initial-controlpoint.osu new file mode 100644 index 0000000000..7062229eed --- /dev/null +++ b/osu.Game.Tests/Resources/catmull-duplicate-initial-controlpoint.osu @@ -0,0 +1,2 @@ +[HitObjects] +200,304,23875,6,0,C|200:304|288:304|288:208|352:208,1,260,8|0 \ No newline at end of file diff --git a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs index 7cf68a2df7..d3d1196eae 100644 --- a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs @@ -340,8 +340,10 @@ namespace osu.Game.Rulesets.Objects.Legacy if (vertices[endIndex].Position != vertices[endIndex - 1].Position) continue; - // Adjacent legacy Catmull segments should be treated as a single segment. - if (FormatVersion < LegacyBeatmapEncoder.FIRST_LAZER_VERSION && type == PathType.Catmull) + // Legacy Catmull sliders don't support multiple segments, so adjacent Catmull segments should be treated as a single one. + // Importantly, this is not applied to the first control point, which may duplicate the slider path's position + // resulting in a duplicate (0,0) control point in the resultant list. + if (type == PathType.Catmull && endIndex > 1 && FormatVersion < LegacyBeatmapEncoder.FIRST_LAZER_VERSION) continue; // The last control point of each segment is not allowed to start a new implicit segment. From 4bcf15f63257eb741775623ce5605bc5fb31220c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 23 May 2022 17:56:32 +0900 Subject: [PATCH 147/395] Refactor test to allow waveform testing --- .../Editing/TestSceneTapTimingControl.cs | 76 +++++++++++-------- 1 file changed, 46 insertions(+), 30 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneTapTimingControl.cs b/osu.Game.Tests/Visual/Editing/TestSceneTapTimingControl.cs index de441995b5..46b45979ea 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneTapTimingControl.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneTapTimingControl.cs @@ -4,15 +4,15 @@ using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; +using osu.Framework.Audio; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Testing; using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterfaceV2; using osu.Game.Overlays; -using osu.Game.Rulesets.Edit; -using osu.Game.Rulesets.Osu; using osu.Game.Screens.Edit; using osu.Game.Screens.Edit.Timing; using osuTK; @@ -22,9 +22,9 @@ namespace osu.Game.Tests.Visual.Editing [TestFixture] public class TestSceneTapTimingControl : EditorClockTestScene { - [Cached(typeof(EditorBeatmap))] - [Cached(typeof(IBeatSnapProvider))] - private readonly EditorBeatmap editorBeatmap; + private EditorBeatmap editorBeatmap => editorBeatmapContainer?.EditorBeatmap; + + private TestSceneHitObjectComposer.EditorBeatmapContainer editorBeatmapContainer; [Cached] private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Blue); @@ -33,38 +33,48 @@ namespace osu.Game.Tests.Visual.Editing private Bindable selectedGroup = new Bindable(); private TapTimingControl control; + private OsuSpriteText timingInfo; - public TestSceneTapTimingControl() + [Resolved] + private AudioManager audio { get; set; } + + [SetUpSteps] + public void SetUpSteps() { - var playableBeatmap = CreateBeatmap(new OsuRuleset().RulesetInfo); + AddStep("create beatmap", () => + { + Beatmap.Value = new WaveformTestBeatmap(audio); + }); - // Ensure time doesn't end while testing - playableBeatmap.BeatmapInfo.Length = 1200000; + AddStep("Create component", () => + { + Child = editorBeatmapContainer = new TestSceneHitObjectComposer.EditorBeatmapContainer(Beatmap.Value) + { + Children = new Drawable[] + { + new Container + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + AutoSizeAxes = Axes.Y, + Width = 400, + Scale = new Vector2(1.5f), + Child = control = new TapTimingControl(), + }, + timingInfo = new OsuSpriteText(), + } + }; - editorBeatmap = new EditorBeatmap(playableBeatmap); - - selectedGroup.Value = editorBeatmap.ControlPointInfo.Groups.First(); + selectedGroup.Value = editorBeatmap.ControlPointInfo.Groups.First(); + }); } - protected override void LoadComplete() + protected override void Update() { - base.LoadComplete(); + base.Update(); - Beatmap.Value = CreateWorkingBeatmap(editorBeatmap.PlayableBeatmap); - Beatmap.Disabled = true; - - Children = new Drawable[] - { - new Container - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - AutoSizeAxes = Axes.Y, - Width = 400, - Scale = new Vector2(1.5f), - Child = control = new TapTimingControl(), - } - }; + if (selectedGroup.Value != null) + timingInfo.Text = $"offset: {selectedGroup.Value.Time:N2} bpm: {selectedGroup.Value.ControlPoints.OfType().First().BPM:N2}"; } [Test] @@ -104,7 +114,13 @@ namespace osu.Game.Tests.Visual.Editing .TriggerClick(); }); - AddSliderStep("BPM", 30, 400, 60, bpm => editorBeatmap.ControlPointInfo.TimingPoints.First().BeatLength = 60000f / bpm); + AddSliderStep("BPM", 30, 400, 128, bpm => + { + if (editorBeatmap == null) + return; + + editorBeatmap.ControlPointInfo.TimingPoints.First().BeatLength = 60000f / bpm; + }); } protected override void Dispose(bool isDisposing) From 588c5d158320bf5ad26e85337a46084b92d5bda8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 23 May 2022 19:52:01 +0900 Subject: [PATCH 148/395] Add initial waveform comparison display --- .../ControlPoints/TimingControlPoint.cs | 2 + .../Screens/Edit/Timing/TapTimingControl.cs | 36 ++++- .../Edit/Timing/WaveformComparisonDisplay.cs | 152 ++++++++++++++++++ 3 files changed, 184 insertions(+), 6 deletions(-) create mode 100644 osu.Game/Screens/Edit/Timing/WaveformComparisonDisplay.cs diff --git a/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs index 922439fcb8..3a7c8b2ec0 100644 --- a/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs +++ b/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs @@ -1,6 +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 enable + using osu.Framework.Bindables; using osu.Game.Beatmaps.Timing; using osu.Game.Graphics; diff --git a/osu.Game/Screens/Edit/Timing/TapTimingControl.cs b/osu.Game/Screens/Edit/Timing/TapTimingControl.cs index 1b0f0a3f5e..d0ab4d1f98 100644 --- a/osu.Game/Screens/Edit/Timing/TapTimingControl.cs +++ b/osu.Game/Screens/Edit/Timing/TapTimingControl.cs @@ -24,8 +24,8 @@ namespace osu.Game.Screens.Edit.Timing [BackgroundDependencyLoader] private void load(OverlayColourProvider colourProvider, OsuColour colours) { - Height = 200; RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; CornerRadius = LabelledDrawable.CORNER_RADIUS; Masking = true; @@ -39,20 +39,44 @@ namespace osu.Game.Screens.Edit.Timing }, new GridContainer { - RelativeSizeAxes = Axes.Both, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, RowDimensions = new[] { - new Dimension(), + new Dimension(GridSizeMode.Absolute, 200), new Dimension(GridSizeMode.Absolute, 60), }, Content = new[] { new Drawable[] { - new MetronomeDisplay + new Container { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + new GridContainer + { + RelativeSizeAxes = Axes.Both, + ColumnDimensions = new[] + { + new Dimension(GridSizeMode.AutoSize), + new Dimension() + }, + Content = new[] + { + new Drawable[] + { + new MetronomeDisplay + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + }, + new WaveformComparisonDisplay(), + } + }, + } + } } }, new Drawable[] diff --git a/osu.Game/Screens/Edit/Timing/WaveformComparisonDisplay.cs b/osu.Game/Screens/Edit/Timing/WaveformComparisonDisplay.cs new file mode 100644 index 0000000000..67a1d30ebe --- /dev/null +++ b/osu.Game/Screens/Edit/Timing/WaveformComparisonDisplay.cs @@ -0,0 +1,152 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +#nullable enable + +using System; +using System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Audio; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Input.Events; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterfaceV2; +using osu.Game.Overlays; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Screens.Edit.Timing +{ + internal class WaveformComparisonDisplay : CompositeDrawable + { + private const int total_waveforms = 8; + + private OsuSpriteText beatIndexText = null!; + + private readonly BindableNumber beatLength = new BindableDouble(); + + [Resolved] + private IBindable beatmap { get; set; } = null!; + + [Resolved] + private Bindable selectedGroup { get; set; } = null!; + + [Resolved] + private EditorClock editorClock { get; set; } = null!; + + [Resolved] + private OverlayColourProvider colourProvider { get; set; } = null!; + + private TimingControlPoint timingPoint = TimingControlPoint.DEFAULT; + + private int lastDisplayedBeatIndex; + + public WaveformComparisonDisplay() + { + RelativeSizeAxes = Axes.Both; + + CornerRadius = LabelledDrawable.CORNER_RADIUS; + Masking = true; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + for (int i = 0; i < total_waveforms; i++) + { + AddInternal(new WaveformGraph + { + RelativeSizeAxes = Axes.Both, + BaseColour = colourProvider.Colour0, + LowColour = colourProvider.Colour1, + MidColour = colourProvider.Colour2, + HighColour = colourProvider.Colour4, + Waveform = beatmap.Value.Waveform, + Resolution = 1, + RelativePositionAxes = Axes.Both, + Height = 1f / total_waveforms, + Y = (float)i / total_waveforms, + }); + } + + AddInternal(new Circle + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Colour = Color4.White, + RelativeSizeAxes = Axes.Y, + Width = 3, + }); + + AddInternal(beatIndexText = new OsuSpriteText + { + Margin = new MarginPadding(5), + }); + + selectedGroup.BindValueChanged(selectedGroupChanged, true); + beatLength.BindValueChanged(_ => showFrom(lastDisplayedBeatIndex), true); + } + + private void selectedGroupChanged(ValueChangedEvent group) + { + timingPoint = selectedGroup.Value?.ControlPoints.OfType().FirstOrDefault() + ?? TimingControlPoint.DEFAULT; + + beatLength.UnbindBindings(); + beatLength.BindTo(timingPoint.BeatLengthBindable); + } + + protected override bool OnHover(HoverEvent e) => true; + + protected override bool OnMouseMove(MouseMoveEvent e) + { + float trackLength = (float)beatmap.Value.Track.Length; + int totalBeatsAvailable = (int)(trackLength / timingPoint.BeatLength); + + Scheduler.AddOnce(showFrom, (int)(e.MousePosition.X / DrawWidth * totalBeatsAvailable)); + + return base.OnMouseMove(e); + } + + protected override void Update() + { + base.Update(); + + if (!IsHovered) + { + int beatOffset = (int)Math.Max(0, ((editorClock.CurrentTime - selectedGroup.Value.Time) / timingPoint.BeatLength)); + + showFrom(beatOffset); + } + } + + private void showFrom(int beatIndex) + { + const float visible_width = 300; + + float trackLength = (float)beatmap.Value.Track.Length; + float scale = trackLength / visible_width; + + beatIndexText.Text = beatIndex.ToString(); + + foreach (var waveform in InternalChildren.OfType()) + { + // offset to the required beat index. + float offset = (float)(selectedGroup.Value.Time + (beatIndex * timingPoint.BeatLength - (visible_width / 2))) / trackLength * scale; + + waveform.X = -offset; + waveform.Scale = new Vector2(scale, 1); + + beatIndex++; + } + + lastDisplayedBeatIndex = beatIndex; + } + } +} From 7040dec013d1c050d7d1d068af770f337f2be5f7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 23 May 2022 20:01:25 +0900 Subject: [PATCH 149/395] Fix disabled bindable woes by not using `TimingControlPoint.DEFAULT` --- .../Screens/Edit/Timing/WaveformComparisonDisplay.cs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/Edit/Timing/WaveformComparisonDisplay.cs b/osu.Game/Screens/Edit/Timing/WaveformComparisonDisplay.cs index 67a1d30ebe..e410d380d6 100644 --- a/osu.Game/Screens/Edit/Timing/WaveformComparisonDisplay.cs +++ b/osu.Game/Screens/Edit/Timing/WaveformComparisonDisplay.cs @@ -34,7 +34,7 @@ namespace osu.Game.Screens.Edit.Timing private IBindable beatmap { get; set; } = null!; [Resolved] - private Bindable selectedGroup { get; set; } = null!; + private Bindable selectedGroup { get; set; } = null!; [Resolved] private EditorClock editorClock { get; set; } = null!; @@ -46,6 +46,8 @@ namespace osu.Game.Screens.Edit.Timing private int lastDisplayedBeatIndex; + private double offsetZeroTime => selectedGroup.Value?.Time ?? 0; + public WaveformComparisonDisplay() { RelativeSizeAxes = Axes.Both; @@ -93,10 +95,10 @@ namespace osu.Game.Screens.Edit.Timing beatLength.BindValueChanged(_ => showFrom(lastDisplayedBeatIndex), true); } - private void selectedGroupChanged(ValueChangedEvent group) + private void selectedGroupChanged(ValueChangedEvent group) { timingPoint = selectedGroup.Value?.ControlPoints.OfType().FirstOrDefault() - ?? TimingControlPoint.DEFAULT; + ?? new TimingControlPoint(); beatLength.UnbindBindings(); beatLength.BindTo(timingPoint.BeatLengthBindable); @@ -120,7 +122,7 @@ namespace osu.Game.Screens.Edit.Timing if (!IsHovered) { - int beatOffset = (int)Math.Max(0, ((editorClock.CurrentTime - selectedGroup.Value.Time) / timingPoint.BeatLength)); + int beatOffset = (int)Math.Max(0, ((editorClock.CurrentTime - offsetZeroTime) / timingPoint.BeatLength)); showFrom(beatOffset); } @@ -138,7 +140,7 @@ namespace osu.Game.Screens.Edit.Timing foreach (var waveform in InternalChildren.OfType()) { // offset to the required beat index. - float offset = (float)(selectedGroup.Value.Time + (beatIndex * timingPoint.BeatLength - (visible_width / 2))) / trackLength * scale; + float offset = (float)(offsetZeroTime + (beatIndex * timingPoint.BeatLength - (visible_width / 2))) / trackLength * scale; waveform.X = -offset; waveform.Scale = new Vector2(scale, 1); From c1720c128d1f591281194b4b76744ea7dcefd7cd Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 23 May 2022 21:00:21 +0900 Subject: [PATCH 150/395] Dim rows which are not part of the active control point (and offset centre) --- .../Edit/Timing/WaveformComparisonDisplay.cs | 55 ++++++++++++++++--- 1 file changed, 46 insertions(+), 9 deletions(-) diff --git a/osu.Game/Screens/Edit/Timing/WaveformComparisonDisplay.cs b/osu.Game/Screens/Edit/Timing/WaveformComparisonDisplay.cs index e410d380d6..2667ae5396 100644 --- a/osu.Game/Screens/Edit/Timing/WaveformComparisonDisplay.cs +++ b/osu.Game/Screens/Edit/Timing/WaveformComparisonDisplay.cs @@ -33,6 +33,9 @@ namespace osu.Game.Screens.Edit.Timing [Resolved] private IBindable beatmap { get; set; } = null!; + [Resolved] + private EditorBeatmap editorBeatmap { get; set; } = null!; + [Resolved] private Bindable selectedGroup { get; set; } = null!; @@ -46,7 +49,10 @@ namespace osu.Game.Screens.Edit.Timing private int lastDisplayedBeatIndex; - private double offsetZeroTime => selectedGroup.Value?.Time ?? 0; + private double selectedGroupStartTime; + private double selectedGroupEndTime; + + private readonly BindableList controlPointGroups = new BindableList(); public WaveformComparisonDisplay() { @@ -88,20 +94,45 @@ namespace osu.Game.Screens.Edit.Timing AddInternal(beatIndexText = new OsuSpriteText { - Margin = new MarginPadding(5), + Anchor = Anchor.CentreLeft, + Margin = new MarginPadding(4), }); - selectedGroup.BindValueChanged(selectedGroupChanged, true); + selectedGroup.BindValueChanged(_ => updateTimingGroup(), true); + + ((IBindableList)controlPointGroups).BindTo(editorBeatmap.ControlPointInfo.Groups); + controlPointGroups.BindCollectionChanged((_, __) => updateTimingGroup()); + beatLength.BindValueChanged(_ => showFrom(lastDisplayedBeatIndex), true); } - private void selectedGroupChanged(ValueChangedEvent group) + private void updateTimingGroup() { - timingPoint = selectedGroup.Value?.ControlPoints.OfType().FirstOrDefault() - ?? new TimingControlPoint(); - beatLength.UnbindBindings(); + + selectedGroupStartTime = 0; + selectedGroupEndTime = beatmap.Value.Track.Length; + + var tcp = selectedGroup.Value?.ControlPoints.OfType().FirstOrDefault(); + + if (tcp == null) + { + timingPoint = new TimingControlPoint(); + return; + } + + timingPoint = tcp; beatLength.BindTo(timingPoint.BeatLengthBindable); + + selectedGroupStartTime = selectedGroup.Value?.Time ?? 0; + + var nextGroup = editorBeatmap.ControlPointInfo.TimingPoints + .SkipWhile(g => g != tcp) + .Skip(1) + .FirstOrDefault(); + + if (nextGroup != null) + selectedGroupEndTime = nextGroup.Time; } protected override bool OnHover(HoverEvent e) => true; @@ -122,7 +153,7 @@ namespace osu.Game.Screens.Edit.Timing if (!IsHovered) { - int beatOffset = (int)Math.Max(0, ((editorClock.CurrentTime - offsetZeroTime) / timingPoint.BeatLength)); + int beatOffset = (int)Math.Floor((editorClock.CurrentTimeAccurate - selectedGroupStartTime) / timingPoint.BeatLength); showFrom(beatOffset); } @@ -137,11 +168,17 @@ namespace osu.Game.Screens.Edit.Timing beatIndexText.Text = beatIndex.ToString(); + // Start displaying from before the current beat + beatIndex -= total_waveforms / 2; + foreach (var waveform in InternalChildren.OfType()) { // offset to the required beat index. - float offset = (float)(offsetZeroTime + (beatIndex * timingPoint.BeatLength - (visible_width / 2))) / trackLength * scale; + double time = selectedGroupStartTime + beatIndex * timingPoint.BeatLength; + float offset = (float)(time - visible_width / 2) / trackLength * scale; + + waveform.Alpha = time < selectedGroupStartTime || time > selectedGroupEndTime ? 0.2f : 1; waveform.X = -offset; waveform.Scale = new Vector2(scale, 1); From 4fcdcef166b5089fd9403011d2f8ae3405308059 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 23 May 2022 21:32:55 +0900 Subject: [PATCH 151/395] Add indexing on each wavefrom row --- .../Edit/Timing/WaveformComparisonDisplay.cs | 72 ++++++++++++------- 1 file changed, 46 insertions(+), 26 deletions(-) diff --git a/osu.Game/Screens/Edit/Timing/WaveformComparisonDisplay.cs b/osu.Game/Screens/Edit/Timing/WaveformComparisonDisplay.cs index 2667ae5396..ad4e1e737d 100644 --- a/osu.Game/Screens/Edit/Timing/WaveformComparisonDisplay.cs +++ b/osu.Game/Screens/Edit/Timing/WaveformComparisonDisplay.cs @@ -26,8 +26,6 @@ namespace osu.Game.Screens.Edit.Timing { private const int total_waveforms = 8; - private OsuSpriteText beatIndexText = null!; - private readonly BindableNumber beatLength = new BindableDouble(); [Resolved] @@ -42,9 +40,6 @@ namespace osu.Game.Screens.Edit.Timing [Resolved] private EditorClock editorClock { get; set; } = null!; - [Resolved] - private OverlayColourProvider colourProvider { get; set; } = null!; - private TimingControlPoint timingPoint = TimingControlPoint.DEFAULT; private int lastDisplayedBeatIndex; @@ -68,15 +63,9 @@ namespace osu.Game.Screens.Edit.Timing for (int i = 0; i < total_waveforms; i++) { - AddInternal(new WaveformGraph + AddInternal(new WaveformRow { RelativeSizeAxes = Axes.Both, - BaseColour = colourProvider.Colour0, - LowColour = colourProvider.Colour1, - MidColour = colourProvider.Colour2, - HighColour = colourProvider.Colour4, - Waveform = beatmap.Value.Waveform, - Resolution = 1, RelativePositionAxes = Axes.Both, Height = 1f / total_waveforms, Y = (float)i / total_waveforms, @@ -92,12 +81,6 @@ namespace osu.Game.Screens.Edit.Timing Width = 3, }); - AddInternal(beatIndexText = new OsuSpriteText - { - Anchor = Anchor.CentreLeft, - Margin = new MarginPadding(4), - }); - selectedGroup.BindValueChanged(_ => updateTimingGroup(), true); ((IBindableList)controlPointGroups).BindTo(editorBeatmap.ControlPointInfo.Groups); @@ -166,26 +149,63 @@ namespace osu.Game.Screens.Edit.Timing float trackLength = (float)beatmap.Value.Track.Length; float scale = trackLength / visible_width; - beatIndexText.Text = beatIndex.ToString(); - // Start displaying from before the current beat beatIndex -= total_waveforms / 2; - foreach (var waveform in InternalChildren.OfType()) + foreach (var row in InternalChildren.OfType()) { // offset to the required beat index. double time = selectedGroupStartTime + beatIndex * timingPoint.BeatLength; float offset = (float)(time - visible_width / 2) / trackLength * scale; - waveform.Alpha = time < selectedGroupStartTime || time > selectedGroupEndTime ? 0.2f : 1; - waveform.X = -offset; - waveform.Scale = new Vector2(scale, 1); - - beatIndex++; + row.Alpha = time < selectedGroupStartTime || time > selectedGroupEndTime ? 0.2f : 1; + row.WaveformOffset = -offset; + row.WaveformScale = new Vector2(scale, 1); + row.BeatIndex = beatIndex++; } lastDisplayedBeatIndex = beatIndex; } + + internal class WaveformRow : CompositeDrawable + { + private OsuSpriteText beatIndexText = null!; + private WaveformGraph waveformGraph = null!; + + [Resolved] + private OverlayColourProvider colourProvider { get; set; } = null!; + + [BackgroundDependencyLoader] + private void load(IBindable beatmap) + { + InternalChildren = new Drawable[] + { + waveformGraph = new WaveformGraph + { + RelativeSizeAxes = Axes.Both, + RelativePositionAxes = Axes.Both, + Waveform = beatmap.Value.Waveform, + Resolution = 1, + + BaseColour = colourProvider.Colour0, + LowColour = colourProvider.Colour1, + MidColour = colourProvider.Colour2, + HighColour = colourProvider.Colour4, + }, + beatIndexText = new OsuSpriteText + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Padding = new MarginPadding(5), + Colour = colourProvider.Content2 + } + }; + } + + public int BeatIndex { set => beatIndexText.Text = value.ToString(); } + public Vector2 WaveformScale { set => waveformGraph.Scale = value; } + public float WaveformOffset { set => waveformGraph.X = value; } + } } } From 49a21736464330baf0333ff5bfec2461885edd50 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 24 May 2022 14:33:05 +0900 Subject: [PATCH 152/395] Avoid needlessly updating display (and add better documentation on chosen scale) --- .../Screens/Edit/Timing/WaveformComparisonDisplay.cs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Edit/Timing/WaveformComparisonDisplay.cs b/osu.Game/Screens/Edit/Timing/WaveformComparisonDisplay.cs index ad4e1e737d..0830ba8cf4 100644 --- a/osu.Game/Screens/Edit/Timing/WaveformComparisonDisplay.cs +++ b/osu.Game/Screens/Edit/Timing/WaveformComparisonDisplay.cs @@ -136,14 +136,21 @@ namespace osu.Game.Screens.Edit.Timing if (!IsHovered) { - int beatOffset = (int)Math.Floor((editorClock.CurrentTimeAccurate - selectedGroupStartTime) / timingPoint.BeatLength); + int currentBeat = (int)Math.Floor((editorClock.CurrentTimeAccurate - selectedGroupStartTime) / timingPoint.BeatLength); - showFrom(beatOffset); + showFrom(currentBeat); } } private void showFrom(int beatIndex) { + if (lastDisplayedBeatIndex == beatIndex) + return; + + // Chosen as a pretty usable number across all BPMs. + // Optimally we'd want this to scale with the BPM in question, but performing + // scaling of the display is both expensive in resampling, and decreases usability + // (as it is harder to track the waveform when making realtime adjustments). const float visible_width = 300; float trackLength = (float)beatmap.Value.Track.Length; From 1137545d4ab4d59e58891a5171932d900120cd2d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 24 May 2022 16:02:01 +0900 Subject: [PATCH 153/395] Fix `Timeline` potentially not updating visuals to correct state on first display --- .../Screens/Edit/Compose/Components/Timeline/Timeline.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs index 992ab7947e..6812bbb72d 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs @@ -163,10 +163,11 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline { base.LoadComplete(); + WaveformVisible.BindValueChanged(_ => updateWaveformOpacity()); waveformOpacity.BindValueChanged(_ => updateWaveformOpacity(), true); - WaveformVisible.ValueChanged += _ => updateWaveformOpacity(); - TicksVisible.ValueChanged += visible => ticks.FadeTo(visible.NewValue ? 1 : 0, 200, Easing.OutQuint); + TicksVisible.BindValueChanged(visible => ticks.FadeTo(visible.NewValue ? 1 : 0, 200, Easing.OutQuint), true); + ControlPointsVisible.BindValueChanged(visible => { if (visible.NewValue) From f00dd27fcdd762eccdec2ec780d427c0da023518 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 24 May 2022 17:50:23 +0900 Subject: [PATCH 154/395] Move `OverlayColourProvider` provisioning of `RoundedButton` to `SettingsButton` for now --- osu.Game/Graphics/UserInterfaceV2/RoundedButton.cs | 9 +++++---- osu.Game/Overlays/Settings/SettingsButton.cs | 9 +++++++++ 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/osu.Game/Graphics/UserInterfaceV2/RoundedButton.cs b/osu.Game/Graphics/UserInterfaceV2/RoundedButton.cs index 3c0c3b69e8..cb8c63371d 100644 --- a/osu.Game/Graphics/UserInterfaceV2/RoundedButton.cs +++ b/osu.Game/Graphics/UserInterfaceV2/RoundedButton.cs @@ -2,13 +2,11 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; -using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Localisation; using osu.Game.Graphics.UserInterface; -using osu.Game.Overlays; namespace osu.Game.Graphics.UserInterfaceV2 { @@ -27,9 +25,12 @@ namespace osu.Game.Graphics.UserInterfaceV2 } [BackgroundDependencyLoader(true)] - private void load([CanBeNull] OverlayColourProvider overlayColourProvider, OsuColour colours) + private void load(OsuColour colours) { - DefaultBackgroundColour = overlayColourProvider?.Highlight1 ?? colours.Blue3; + // According to flyte, buttons are supposed to have explicit colours for now. + // Not sure this is the correct direction, but we haven't decided on an `OverlayColourProvider` stand-in yet. + // This is a better default. See `SettingsButton` for an override which uses `OverlayColourProvider`. + DefaultBackgroundColour = colours.Blue3; } protected override void LoadComplete() diff --git a/osu.Game/Overlays/Settings/SettingsButton.cs b/osu.Game/Overlays/Settings/SettingsButton.cs index 10aea92b22..9e4dc763ec 100644 --- a/osu.Game/Overlays/Settings/SettingsButton.cs +++ b/osu.Game/Overlays/Settings/SettingsButton.cs @@ -3,9 +3,12 @@ using System.Collections.Generic; using System.Linq; +using JetBrains.Annotations; +using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Cursor; using osu.Framework.Localisation; +using osu.Game.Graphics; using osu.Game.Graphics.UserInterfaceV2; namespace osu.Game.Overlays.Settings @@ -18,6 +21,12 @@ namespace osu.Game.Overlays.Settings Padding = new MarginPadding { Left = SettingsPanel.CONTENT_MARGINS, Right = SettingsPanel.CONTENT_MARGINS }; } + [BackgroundDependencyLoader(true)] + private void load([CanBeNull] OverlayColourProvider overlayColourProvider, OsuColour colours) + { + DefaultBackgroundColour = overlayColourProvider?.Highlight1 ?? colours.Blue3; + } + public LocalisableString TooltipText { get; set; } public override IEnumerable FilterTerms From a346990a8c7a3b9986d8c712ea4be568e6cf9bec Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 24 May 2022 16:02:08 +0900 Subject: [PATCH 155/395] Remove `TriangleButton` usage in editor --- osu.Game/Screens/Edit/Timing/GroupSection.cs | 4 ++-- osu.Game/Screens/Edit/Timing/TimingScreen.cs | 5 +++-- osu.Game/Screens/Edit/Verify/IssueList.cs | 4 ++-- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/osu.Game/Screens/Edit/Timing/GroupSection.cs b/osu.Game/Screens/Edit/Timing/GroupSection.cs index 03059ff6e1..bb2dd35a9c 100644 --- a/osu.Game/Screens/Edit/Timing/GroupSection.cs +++ b/osu.Game/Screens/Edit/Timing/GroupSection.cs @@ -17,7 +17,7 @@ namespace osu.Game.Screens.Edit.Timing { private LabelledTextBox textBox; - private TriangleButton button; + private OsuButton button; [Resolved] protected Bindable SelectedGroup { get; private set; } @@ -53,7 +53,7 @@ namespace osu.Game.Screens.Edit.Timing { Label = "Time" }, - button = new TriangleButton + button = new RoundedButton { Text = "Use current time", RelativeSizeAxes = Axes.X, diff --git a/osu.Game/Screens/Edit/Timing/TimingScreen.cs b/osu.Game/Screens/Edit/Timing/TimingScreen.cs index a4193d5084..5f1fd14617 100644 --- a/osu.Game/Screens/Edit/Timing/TimingScreen.cs +++ b/osu.Game/Screens/Edit/Timing/TimingScreen.cs @@ -10,6 +10,7 @@ using osu.Framework.Graphics.Shapes; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterface; +using osu.Game.Graphics.UserInterfaceV2; using osu.Game.Overlays; using osuTK; @@ -100,7 +101,7 @@ namespace osu.Game.Screens.Edit.Timing Spacing = new Vector2(5), Children = new Drawable[] { - deleteButton = new OsuButton + deleteButton = new RoundedButton { Text = "-", Size = new Vector2(30, 30), @@ -108,7 +109,7 @@ namespace osu.Game.Screens.Edit.Timing Anchor = Anchor.BottomRight, Origin = Anchor.BottomRight, }, - new OsuButton + new RoundedButton { Text = "+ Add at current time", Action = addNew, diff --git a/osu.Game/Screens/Edit/Verify/IssueList.cs b/osu.Game/Screens/Edit/Verify/IssueList.cs index 5fe43199cc..415acc0e22 100644 --- a/osu.Game/Screens/Edit/Verify/IssueList.cs +++ b/osu.Game/Screens/Edit/Verify/IssueList.cs @@ -10,7 +10,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Game.Beatmaps; using osu.Game.Graphics.Containers; -using osu.Game.Graphics.UserInterface; +using osu.Game.Graphics.UserInterfaceV2; using osu.Game.Overlays; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit.Checks.Components; @@ -67,7 +67,7 @@ namespace osu.Game.Screens.Edit.Verify Margin = new MarginPadding(20), Children = new Drawable[] { - new TriangleButton + new RoundedButton { Text = "Refresh", Action = refresh, From 4a88affd036f505c67e2ddbebdbb5e41ed1db339 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 24 May 2022 17:02:19 +0900 Subject: [PATCH 156/395] Move beat divisor tick size retrieval to static methods --- osu.Game/Screens/Edit/BindableBeatDivisor.cs | 27 +++++++++ .../Timeline/TimelineTickDisplay.cs | 58 +++---------------- 2 files changed, 35 insertions(+), 50 deletions(-) diff --git a/osu.Game/Screens/Edit/BindableBeatDivisor.cs b/osu.Game/Screens/Edit/BindableBeatDivisor.cs index af958e3448..8f430dce77 100644 --- a/osu.Game/Screens/Edit/BindableBeatDivisor.cs +++ b/osu.Game/Screens/Edit/BindableBeatDivisor.cs @@ -5,6 +5,7 @@ using System.Linq; using osu.Framework.Bindables; using osu.Game.Graphics; using osu.Game.Screens.Edit.Compose.Components; +using osuTK; using osuTK.Graphics; namespace osu.Game.Screens.Edit @@ -100,6 +101,32 @@ namespace osu.Game.Screens.Edit } } + /// + /// Get a relative display size for the specified divisor. + /// + /// The beat divisor. + /// A relative size which can be used to display ticks. + public static Vector2 GetSize(int beatDivisor) + { + switch (beatDivisor) + { + case 1: + case 2: + return new Vector2(0.6f, 0.9f); + + case 3: + case 4: + return new Vector2(0.5f, 0.8f); + + case 6: + case 8: + return new Vector2(0.4f, 0.7f); + + default: + return new Vector2(0.3f, 0.6f); + } + } + /// /// Retrieves the applicable divisor for a specific beat index. /// diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineTickDisplay.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineTickDisplay.cs index 3a32dc18e5..fda8416ecd 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineTickDisplay.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineTickDisplay.cs @@ -11,6 +11,7 @@ using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Screens.Edit.Components.Timelines.Summary.Parts; using osu.Game.Screens.Edit.Components.Timelines.Summary.Visualisations; +using osuTK; namespace osu.Game.Screens.Edit.Compose.Components.Timeline { @@ -132,10 +133,15 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline // even though "bar lines" take up the full vertical space, we render them in two pieces because it allows for less anchor/origin churn. + Vector2 size = Vector2.One; + + if (indexInBar != 1) + size = BindableBeatDivisor.GetSize(divisor); + var line = getNextUsableLine(); line.X = xPos; - line.Width = PointVisualisation.MAX_WIDTH * getWidth(indexInBar, divisor); - line.Height = 0.9f * getHeight(indexInBar, divisor); + line.Width = PointVisualisation.MAX_WIDTH * size.X; + line.Height = 0.9f * size.Y; line.Colour = colour; } @@ -170,54 +176,6 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline } } - private static float getWidth(int indexInBar, int divisor) - { - if (indexInBar == 0) - return 1; - - switch (divisor) - { - case 1: - case 2: - return 0.6f; - - case 3: - case 4: - return 0.5f; - - case 6: - case 8: - return 0.4f; - - default: - return 0.3f; - } - } - - private static float getHeight(int indexInBar, int divisor) - { - if (indexInBar == 0) - return 1; - - switch (divisor) - { - case 1: - case 2: - return 0.9f; - - case 3: - case 4: - return 0.8f; - - case 6: - case 8: - return 0.7f; - - default: - return 0.6f; - } - } - protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); From e6087f5f5be6c6d2038a8909564ae2bd3fc23386 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 24 May 2022 16:51:21 +0900 Subject: [PATCH 157/395] Handle beat divisor input on mouse down, rather than mouse up It felt way too unresponsive. --- osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs b/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs index 370c9016c7..9eda540b70 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs @@ -453,6 +453,7 @@ namespace osu.Game.Screens.Edit.Compose.Components protected override bool OnMouseDown(MouseDownEvent e) { marker.Active = true; + handleMouseInput(e.ScreenSpaceMousePosition); return base.OnMouseDown(e); } From 3c2e57bf00f04cbe2ad74ba786674c986298ff7a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 24 May 2022 16:50:16 +0900 Subject: [PATCH 158/395] Update `BeatDivisorControl` to better match new designs and metrics --- .../Editing/TestSceneBeatDivisorControl.cs | 5 ++ .../Compose/Components/BeatDivisorControl.cs | 78 ++++++------------- 2 files changed, 28 insertions(+), 55 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneBeatDivisorControl.cs b/osu.Game.Tests/Visual/Editing/TestSceneBeatDivisorControl.cs index 6a0950c6dd..79be8a02c8 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneBeatDivisorControl.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneBeatDivisorControl.cs @@ -5,11 +5,13 @@ using System; using System.Diagnostics; using System.Linq; using NUnit.Framework; +using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.UserInterface; using osu.Framework.Testing; +using osu.Game.Overlays; using osu.Game.Screens.Edit; using osu.Game.Screens.Edit.Compose.Components; using osuTK; @@ -25,6 +27,9 @@ namespace osu.Game.Tests.Visual.Editing private SliderBar tickSliderBar => beatDivisorControl.ChildrenOfType>().Single(); private EquilateralTriangle tickMarkerHead => tickSliderBar.ChildrenOfType().Single(); + [Cached] + private readonly OverlayColourProvider overlayColour = new OverlayColourProvider(OverlayColourScheme.Green); + [SetUp] public void SetUp() => Schedule(() => { diff --git a/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs b/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs index 370c9016c7..47f7f4b388 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs @@ -8,9 +8,7 @@ using Humanizer; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions; -using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; -using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Shapes; @@ -22,6 +20,7 @@ using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterfaceV2; +using osu.Game.Overlays; using osuTK; using osuTK.Graphics; using osuTK.Input; @@ -38,18 +37,17 @@ namespace osu.Game.Screens.Edit.Compose.Components } [BackgroundDependencyLoader] - private void load(OsuColour colours) + private void load(OverlayColourProvider colourProvider) { Masking = true; - CornerRadius = 5; InternalChildren = new Drawable[] { new Box { - Name = "Gray Background", + Name = "Main background", RelativeSizeAxes = Axes.Both, - Colour = colours.Gray4 + Colour = colourProvider.Background3, }, new GridContainer { @@ -65,9 +63,9 @@ namespace osu.Game.Screens.Edit.Compose.Components { new Box { - Name = "Black Background", + Name = "Tick area background", RelativeSizeAxes = Axes.Both, - Colour = Color4.Black + Colour = colourProvider.Background5, }, new TickSliderBar(beatDivisor) { @@ -86,7 +84,7 @@ namespace osu.Game.Screens.Edit.Compose.Components new Box { RelativeSizeAxes = Axes.Both, - Colour = colours.Gray4 + Colour = colourProvider.Background3 }, new Container { @@ -139,11 +137,6 @@ namespace osu.Game.Screens.Edit.Compose.Components RelativeSizeAxes = Axes.Both, Children = new Drawable[] { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = colours.Gray4 - }, new Container { RelativeSizeAxes = Axes.Both, @@ -402,15 +395,15 @@ namespace osu.Game.Screens.Edit.Compose.Components ClearInternal(); CurrentNumber.ValueChanged -= moveMarker; - foreach (int t in beatDivisor.ValidDivisors.Value.Presets) + foreach (int divisor in beatDivisor.ValidDivisors.Value.Presets) { - AddInternal(new Tick + AddInternal(new Tick(divisor) { - Anchor = Anchor.TopLeft, - Origin = Anchor.TopCentre, - RelativePositionAxes = Axes.X, - Colour = BindableBeatDivisor.GetColourFor(t, colours), - X = getMappedPosition(t) + Anchor = Anchor.CentreLeft, + Origin = Anchor.Centre, + RelativePositionAxes = Axes.Both, + Colour = BindableBeatDivisor.GetColourFor(divisor, colours), + X = getMappedPosition(divisor), }); } @@ -422,7 +415,6 @@ namespace osu.Game.Screens.Edit.Compose.Components private void moveMarker(ValueChangedEvent divisor) { marker.MoveToX(getMappedPosition(divisor.NewValue), 100, Easing.OutQuint); - marker.Flash(); } protected override void UpdateValue(float value) @@ -489,29 +481,26 @@ namespace osu.Game.Screens.Edit.Compose.Components private float getMappedPosition(float divisor) => MathF.Pow((divisor - 1) / (beatDivisor.ValidDivisors.Value.Presets.Last() - 1), 0.90f); - private class Tick : CompositeDrawable + private class Tick : Circle { - public Tick() + public Tick(int divisor) { - Size = new Vector2(2.5f, 10); - + Size = new Vector2(6f, 12) * BindableBeatDivisor.GetSize(divisor); InternalChild = new Box { RelativeSizeAxes = Axes.Both }; - - CornerRadius = 0.5f; - Masking = true; } } private class Marker : CompositeDrawable { - private Color4 defaultColour; + [Resolved] + private OverlayColourProvider colourProvider { get; set; } - private const float size = 7; + private const float size = 10; [BackgroundDependencyLoader] - private void load(OsuColour colours) + private void load() { - Colour = defaultColour = colours.Gray4; + Colour = colourProvider.Background3; Anchor = Anchor.TopLeft; Origin = Anchor.TopCentre; @@ -521,15 +510,6 @@ namespace osu.Game.Screens.Edit.Compose.Components InternalChildren = new Drawable[] { - new Box - { - Width = 2, - RelativeSizeAxes = Axes.Y, - Origin = Anchor.BottomCentre, - Anchor = Anchor.BottomCentre, - Colour = ColourInfo.GradientVertical(Color4.White.Opacity(0.2f), Color4.White), - Blending = BlendingParameters.Additive, - }, new EquilateralTriangle { Origin = Anchor.BottomCentre, @@ -548,22 +528,10 @@ namespace osu.Game.Screens.Edit.Compose.Components get => active; set { - this.FadeColour(value ? Color4.White : defaultColour, 500, Easing.OutQuint); + this.FadeColour(value ? colourProvider.Background1 : colourProvider.Background3, 500, Easing.OutQuint); active = value; } } - - public void Flash() - { - bool wasActive = active; - - Active = true; - - if (wasActive) return; - - using (BeginDelayedSequence(50)) - Active = false; - } } } } From 3ace5f135e826ae73e7e5630ae7a369f5ee7d6fc Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 24 May 2022 18:25:57 +0900 Subject: [PATCH 159/395] Adjust marker to match design proportions --- .../Compose/Components/BeatDivisorControl.cs | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs b/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs index 47f7f4b388..e3c862f91b 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs @@ -495,26 +495,22 @@ namespace osu.Game.Screens.Edit.Compose.Components [Resolved] private OverlayColourProvider colourProvider { get; set; } - private const float size = 10; - [BackgroundDependencyLoader] private void load() { Colour = colourProvider.Background3; - Anchor = Anchor.TopLeft; - Origin = Anchor.TopCentre; + Anchor = Anchor.BottomLeft; + Origin = Anchor.BottomCentre; + + Size = new Vector2(8, 6.5f); - Width = size; - RelativeSizeAxes = Axes.Y; RelativePositionAxes = Axes.X; InternalChildren = new Drawable[] { - new EquilateralTriangle + new Triangle { - Origin = Anchor.BottomCentre, - Anchor = Anchor.BottomCentre, - Height = size, + RelativeSizeAxes = Axes.Both, EdgeSmoothness = new Vector2(1), Colour = Color4.White, } From bb82abe48d81d596946d1781d0621c1bbf5f18da Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 24 May 2022 18:38:08 +0900 Subject: [PATCH 160/395] Update test to use new triangle type resolution --- osu.Game.Tests/Visual/Editing/TestSceneBeatDivisorControl.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneBeatDivisorControl.cs b/osu.Game.Tests/Visual/Editing/TestSceneBeatDivisorControl.cs index 79be8a02c8..cdd1175692 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneBeatDivisorControl.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneBeatDivisorControl.cs @@ -25,7 +25,7 @@ namespace osu.Game.Tests.Visual.Editing private BindableBeatDivisor bindableBeatDivisor; private SliderBar tickSliderBar => beatDivisorControl.ChildrenOfType>().Single(); - private EquilateralTriangle tickMarkerHead => tickSliderBar.ChildrenOfType().Single(); + private Triangle tickMarkerHead => tickSliderBar.ChildrenOfType().Single(); [Cached] private readonly OverlayColourProvider overlayColour = new OverlayColourProvider(OverlayColourScheme.Green); From 33fd1555f23a45c4d325c4db4d4d2aa643a6c0c1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 24 May 2022 18:42:40 +0900 Subject: [PATCH 161/395] Update `TestSceneRoundedButton` with new colour assertions --- .../UserInterface/TestSceneRoundedButton.cs | 37 +++++++++++++++---- 1 file changed, 29 insertions(+), 8 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneRoundedButton.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneRoundedButton.cs index f45c55d912..454a71e6d2 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneRoundedButton.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneRoundedButton.cs @@ -5,9 +5,12 @@ using System.Linq; using NUnit.Framework; using osu.Framework.Bindables; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Framework.Testing; +using osu.Game.Graphics; using osu.Game.Graphics.UserInterfaceV2; using osu.Game.Overlays; +using osu.Game.Overlays.Settings; namespace osu.Game.Tests.Visual.UserInterface { @@ -15,14 +18,31 @@ namespace osu.Game.Tests.Visual.UserInterface { private readonly BindableBool enabled = new BindableBool(true); - protected override Drawable CreateContent() => new RoundedButton + protected override Drawable CreateContent() { - Width = 400, - Text = "Test button", - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Enabled = { BindTarget = enabled }, - }; + return new FillFlowContainer + { + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + new RoundedButton + { + Width = 400, + Text = "Test button", + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Enabled = { BindTarget = enabled }, + }, + new SettingsButton + { + Text = "Test button", + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Enabled = { BindTarget = enabled }, + }, + } + }; + } [Test] public void TestDisabled() @@ -34,7 +54,8 @@ namespace osu.Game.Tests.Visual.UserInterface public void TestBackgroundColour() { AddStep("set red scheme", () => CreateThemedContent(OverlayColourScheme.Red)); - AddAssert("first button has correct colour", () => Cell(0, 1).ChildrenOfType().First().BackgroundColour == new OverlayColourProvider(OverlayColourScheme.Red).Highlight1); + AddAssert("rounded button has correct colour", () => Cell(0, 1).ChildrenOfType().First().BackgroundColour == new OsuColour().Blue3); + AddAssert("settings button has correct colour", () => Cell(0, 1).ChildrenOfType().First().BackgroundColour == new OverlayColourProvider(OverlayColourScheme.Red).Highlight1); } } } From 17206bbebf4635a635b612255ec8112466dd14c5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 24 May 2022 16:15:24 +0900 Subject: [PATCH 162/395] Update screen padding and colouring to better match new designs --- .../Edit/DistancedHitObjectComposer.cs | 2 +- osu.Game/Rulesets/Edit/HitObjectComposer.cs | 4 +-- .../Screens/Edit/EditorScreenWithTimeline.cs | 13 ++----- osu.Game/Screens/Edit/Timing/TimingScreen.cs | 36 +++++++++---------- 4 files changed, 22 insertions(+), 33 deletions(-) diff --git a/osu.Game/Rulesets/Edit/DistancedHitObjectComposer.cs b/osu.Game/Rulesets/Edit/DistancedHitObjectComposer.cs index 5e6d9dbe34..7019dad803 100644 --- a/osu.Game/Rulesets/Edit/DistancedHitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/DistancedHitObjectComposer.cs @@ -52,7 +52,7 @@ namespace osu.Game.Rulesets.Edit { AddInternal(RightSideToolboxContainer = new ExpandingToolboxContainer(130, 250) { - Padding = new MarginPadding { Right = 10 }, + Padding = new MarginPadding(10), Alpha = DistanceSpacingMultiplier.Disabled ? 0 : 1, Anchor = Anchor.TopRight, Origin = Anchor.TopRight, diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index f8d796a778..f6fdb228ce 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -114,9 +114,9 @@ namespace osu.Game.Rulesets.Edit .WithChild(BlueprintContainer = CreateBlueprintContainer()) } }, - new ExpandingToolboxContainer(80, 200) + new ExpandingToolboxContainer(90, 200) { - Padding = new MarginPadding { Left = 10 }, + Padding = new MarginPadding(10), Children = new Drawable[] { new EditorToolboxGroup("toolbox (1-9)") diff --git a/osu.Game/Screens/Edit/EditorScreenWithTimeline.cs b/osu.Game/Screens/Edit/EditorScreenWithTimeline.cs index 0d59a7a1a8..5482aa4ff0 100644 --- a/osu.Game/Screens/Edit/EditorScreenWithTimeline.cs +++ b/osu.Game/Screens/Edit/EditorScreenWithTimeline.cs @@ -3,14 +3,13 @@ using JetBrains.Annotations; using osu.Framework.Allocation; -using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Game.Graphics.UserInterface; +using osu.Game.Overlays; using osu.Game.Screens.Edit.Compose.Components; using osu.Game.Screens.Edit.Compose.Components.Timeline; -using osuTK.Graphics; namespace osu.Game.Screens.Edit { @@ -33,7 +32,7 @@ namespace osu.Game.Screens.Edit private LoadingSpinner spinner; [BackgroundDependencyLoader(true)] - private void load([CanBeNull] BindableBeatDivisor beatDivisor) + private void load(OverlayColourProvider colourProvider, [CanBeNull] BindableBeatDivisor beatDivisor) { if (beatDivisor != null) this.beatDivisor.BindTo(beatDivisor); @@ -60,7 +59,7 @@ namespace osu.Game.Screens.Edit new Box { RelativeSizeAxes = Axes.Both, - Colour = Color4.Black.Opacity(0.5f) + Colour = colourProvider.Background4 }, new Container { @@ -106,12 +105,6 @@ namespace osu.Game.Screens.Edit Name = "Main content", RelativeSizeAxes = Axes.Both, Depth = float.MaxValue, - Padding = new MarginPadding - { - Horizontal = horizontal_margins, - Top = vertical_margins, - Bottom = vertical_margins - }, Child = spinner = new LoadingSpinner(true) { State = { Value = Visibility.Visible }, diff --git a/osu.Game/Screens/Edit/Timing/TimingScreen.cs b/osu.Game/Screens/Edit/Timing/TimingScreen.cs index 5f1fd14617..f71a8d7d22 100644 --- a/osu.Game/Screens/Edit/Timing/TimingScreen.cs +++ b/osu.Game/Screens/Edit/Timing/TimingScreen.cs @@ -16,7 +16,7 @@ using osuTK; namespace osu.Game.Screens.Edit.Timing { - public class TimingScreen : EditorRoundedScreen + public class TimingScreen : EditorScreenWithTimeline { [Cached] private Bindable selectedGroup = new Bindable(); @@ -26,27 +26,23 @@ namespace osu.Game.Screens.Edit.Timing { } - [BackgroundDependencyLoader] - private void load() + protected override Drawable CreateMainContent() => new GridContainer { - Add(new GridContainer + RelativeSizeAxes = Axes.Both, + ColumnDimensions = new[] { - RelativeSizeAxes = Axes.Both, - ColumnDimensions = new[] + new Dimension(), + new Dimension(GridSizeMode.Absolute, 350), + }, + Content = new[] + { + new Drawable[] { - new Dimension(), - new Dimension(GridSizeMode.Absolute, 350), + new ControlPointList(), + new ControlPointSettings(), }, - Content = new[] - { - new Drawable[] - { - new ControlPointList(), - new ControlPointSettings(), - }, - } - }); - } + } + }; public class ControlPointList : CompositeDrawable { @@ -77,12 +73,12 @@ namespace osu.Game.Screens.Edit.Timing { new Box { - Colour = colours.Background3, + Colour = colours.Background4, RelativeSizeAxes = Axes.Both, }, new Box { - Colour = colours.Background2, + Colour = colours.Background3, RelativeSizeAxes = Axes.Y, Width = ControlPointTable.TIMING_COLUMN_WIDTH + margins, }, From a905731053c2dfbcdab9466f0da2fe97a2cb1925 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 24 May 2022 16:32:44 +0900 Subject: [PATCH 163/395] Update timeline colours and paddings to match new designs --- .../Compose/Components/Timeline/TimelineArea.cs | 13 +++++++------ osu.Game/Screens/Edit/EditorScreenWithTimeline.cs | 5 ++--- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineArea.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineArea.cs index 1541ceade5..4cffebc57c 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineArea.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineArea.cs @@ -2,12 +2,12 @@ // 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.Shapes; using osu.Framework.Graphics.Sprites; using osu.Game.Graphics.UserInterface; +using osu.Game.Overlays; using osuTK; namespace osu.Game.Screens.Edit.Compose.Components.Timeline @@ -27,10 +27,9 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline } [BackgroundDependencyLoader] - private void load() + private void load(OverlayColourProvider colourProvider) { Masking = true; - CornerRadius = 5; OsuCheckbox waveformCheckbox; OsuCheckbox controlPointsCheckbox; @@ -41,7 +40,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline new Box { RelativeSizeAxes = Axes.Both, - Colour = Color4Extensions.FromHex("111") + Colour = colourProvider.Background5 }, new GridContainer { @@ -55,12 +54,13 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline { RelativeSizeAxes = Axes.Y, AutoSizeAxes = Axes.X, + Name = @"Toggle controls", Children = new Drawable[] { new Box { RelativeSizeAxes = Axes.Both, - Colour = Color4Extensions.FromHex("222") + Colour = colourProvider.Background2, }, new FillFlowContainer { @@ -94,12 +94,13 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline { RelativeSizeAxes = Axes.Y, AutoSizeAxes = Axes.X, + Name = @"Zoom controls", Children = new Drawable[] { new Box { RelativeSizeAxes = Axes.Both, - Colour = Color4Extensions.FromHex("333") + Colour = colourProvider.Background3, }, new Container { diff --git a/osu.Game/Screens/Edit/EditorScreenWithTimeline.cs b/osu.Game/Screens/Edit/EditorScreenWithTimeline.cs index 5482aa4ff0..186a4bddb3 100644 --- a/osu.Game/Screens/Edit/EditorScreenWithTimeline.cs +++ b/osu.Game/Screens/Edit/EditorScreenWithTimeline.cs @@ -15,8 +15,7 @@ namespace osu.Game.Screens.Edit { public abstract class EditorScreenWithTimeline : EditorScreen { - private const float vertical_margins = 10; - private const float horizontal_margins = 20; + private const float padding = 10; private readonly BindableBeatDivisor beatDivisor = new BindableBeatDivisor(); @@ -66,7 +65,7 @@ namespace osu.Game.Screens.Edit Name = "Timeline content", RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, - Padding = new MarginPadding { Horizontal = horizontal_margins, Vertical = vertical_margins }, + Padding = new MarginPadding { Horizontal = padding, Top = padding }, Child = new GridContainer { RelativeSizeAxes = Axes.X, From 1d196262d0a06e5d03db25ca1c99082a255c341e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 24 May 2022 16:33:20 +0900 Subject: [PATCH 164/395] Remove unused `OnTimelineLoaded` flow --- osu.Game/Screens/Edit/EditorScreenWithTimeline.cs | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/osu.Game/Screens/Edit/EditorScreenWithTimeline.cs b/osu.Game/Screens/Edit/EditorScreenWithTimeline.cs index 186a4bddb3..0b80af68f2 100644 --- a/osu.Game/Screens/Edit/EditorScreenWithTimeline.cs +++ b/osu.Game/Screens/Edit/EditorScreenWithTimeline.cs @@ -125,18 +125,10 @@ namespace osu.Game.Screens.Edit mainContent.Add(content); content.FadeInFromZero(300, Easing.OutQuint); - LoadComponentAsync(new TimelineArea(CreateTimelineContent()), t => - { - timelineContainer.Add(t); - OnTimelineLoaded(t); - }); + LoadComponentAsync(new TimelineArea(CreateTimelineContent()), timelineContainer.Add); }); } - protected virtual void OnTimelineLoaded(TimelineArea timelineArea) - { - } - protected abstract Drawable CreateMainContent(); protected virtual Drawable CreateTimelineContent() => new Container(); From c02ec050569e86811c1ad937a41edcbc95900451 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 24 May 2022 17:13:44 +0900 Subject: [PATCH 165/395] Remove remaining usage of `EditorRoundedScreen` --- osu.Game/Screens/Edit/EditorRoundedScreen.cs | 53 ------------------- osu.Game/Screens/Edit/Setup/SetupScreen.cs | 12 ++++- .../Screens/Edit/Setup/SetupScreenHeader.cs | 2 +- osu.Game/Screens/Edit/Setup/SetupSection.cs | 2 +- osu.Game/Screens/Edit/Verify/VerifyScreen.cs | 5 +- 5 files changed, 14 insertions(+), 60 deletions(-) delete mode 100644 osu.Game/Screens/Edit/EditorRoundedScreen.cs diff --git a/osu.Game/Screens/Edit/EditorRoundedScreen.cs b/osu.Game/Screens/Edit/EditorRoundedScreen.cs deleted file mode 100644 index 62f40f0325..0000000000 --- a/osu.Game/Screens/Edit/EditorRoundedScreen.cs +++ /dev/null @@ -1,53 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using osu.Framework.Allocation; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; -using osu.Game.Overlays; - -namespace osu.Game.Screens.Edit -{ - public class EditorRoundedScreen : EditorScreen - { - public const int HORIZONTAL_PADDING = 100; - - private Container roundedContent; - - protected override Container Content => roundedContent; - - public EditorRoundedScreen(EditorScreenMode mode) - : base(mode) - { - } - - [BackgroundDependencyLoader] - private void load(OverlayColourProvider colourProvider) - { - base.Content.Add(new Container - { - RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding(50), - Child = new Container - { - RelativeSizeAxes = Axes.Both, - Masking = true, - CornerRadius = 10, - Children = new Drawable[] - { - new Box - { - Colour = colourProvider.Background3, - RelativeSizeAxes = Axes.Both, - }, - roundedContent = new Container - { - RelativeSizeAxes = Axes.Both, - }, - } - } - }); - } - } -} diff --git a/osu.Game/Screens/Edit/Setup/SetupScreen.cs b/osu.Game/Screens/Edit/Setup/SetupScreen.cs index 231d977aab..b95aabc1c4 100644 --- a/osu.Game/Screens/Edit/Setup/SetupScreen.cs +++ b/osu.Game/Screens/Edit/Setup/SetupScreen.cs @@ -4,11 +4,13 @@ using System.Collections.Generic; using osu.Framework.Allocation; using osu.Framework.Graphics; +using osu.Framework.Graphics.Shapes; using osu.Game.Graphics.Containers; +using osu.Game.Overlays; namespace osu.Game.Screens.Edit.Setup { - public class SetupScreen : EditorRoundedScreen + public class SetupScreen : EditorScreen { [Cached] private SectionsContainer sections { get; } = new SetupScreenSectionsContainer(); @@ -22,7 +24,7 @@ namespace osu.Game.Screens.Edit.Setup } [BackgroundDependencyLoader] - private void load(EditorBeatmap beatmap) + private void load(EditorBeatmap beatmap, OverlayColourProvider colourProvider) { var sectionsEnumerable = new List { @@ -37,6 +39,12 @@ namespace osu.Game.Screens.Edit.Setup if (rulesetSpecificSection != null) sectionsEnumerable.Add(rulesetSpecificSection); + Add(new Box + { + Colour = colourProvider.Background2, + RelativeSizeAxes = Axes.Both, + }); + Add(sections.With(s => { s.RelativeSizeAxes = Axes.Both; diff --git a/osu.Game/Screens/Edit/Setup/SetupScreenHeader.cs b/osu.Game/Screens/Edit/Setup/SetupScreenHeader.cs index 2d0afda001..2412f1c4ed 100644 --- a/osu.Game/Screens/Edit/Setup/SetupScreenHeader.cs +++ b/osu.Game/Screens/Edit/Setup/SetupScreenHeader.cs @@ -93,7 +93,7 @@ namespace osu.Game.Screens.Edit.Setup public SetupScreenTabControl() { - TabContainer.Margin = new MarginPadding { Horizontal = EditorRoundedScreen.HORIZONTAL_PADDING }; + TabContainer.Margin = new MarginPadding { Horizontal = 100 }; AddInternal(background = new Box { diff --git a/osu.Game/Screens/Edit/Setup/SetupSection.cs b/osu.Game/Screens/Edit/Setup/SetupSection.cs index 1dde6fb926..02bb05d227 100644 --- a/osu.Game/Screens/Edit/Setup/SetupSection.cs +++ b/osu.Game/Screens/Edit/Setup/SetupSection.cs @@ -40,7 +40,7 @@ namespace osu.Game.Screens.Edit.Setup Padding = new MarginPadding { Vertical = 10, - Horizontal = EditorRoundedScreen.HORIZONTAL_PADDING + Horizontal = 100 }; InternalChild = new FillFlowContainer diff --git a/osu.Game/Screens/Edit/Verify/VerifyScreen.cs b/osu.Game/Screens/Edit/Verify/VerifyScreen.cs index 08643eb8c1..9dc5a53907 100644 --- a/osu.Game/Screens/Edit/Verify/VerifyScreen.cs +++ b/osu.Game/Screens/Edit/Verify/VerifyScreen.cs @@ -11,7 +11,7 @@ using osu.Game.Rulesets.Edit.Checks.Components; namespace osu.Game.Screens.Edit.Verify { [Cached] - public class VerifyScreen : EditorRoundedScreen + public class VerifyScreen : EditorScreen { public readonly Bindable SelectedIssue = new Bindable(); @@ -32,7 +32,6 @@ namespace osu.Game.Screens.Edit.Verify InterpretedDifficulty.Default = BeatmapDifficultyCache.GetDifficultyRating(EditorBeatmap.BeatmapInfo.StarRating); InterpretedDifficulty.SetDefault(); - IssueList = new IssueList(); Child = new Container { RelativeSizeAxes = Axes.Both, @@ -48,7 +47,7 @@ namespace osu.Game.Screens.Edit.Verify { new Drawable[] { - IssueList, + IssueList = new IssueList(), new IssueSettings(), }, } From 3f5ccd4db8c5aa6ae027b4c00a1df344891d3896 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 24 May 2022 18:02:37 +0900 Subject: [PATCH 166/395] Update timing screen sections to newer design metrics --- osu.Game/Graphics/UserInterfaceV2/LabelledDrawable.cs | 2 +- osu.Game/Screens/Edit/EditorRoundedScreenSettings.cs | 5 ++++- osu.Game/Screens/Edit/Timing/Section.cs | 11 ++++++----- .../Screens/Edit/Timing/SliderWithTextBoxInput.cs | 2 ++ 4 files changed, 13 insertions(+), 7 deletions(-) diff --git a/osu.Game/Graphics/UserInterfaceV2/LabelledDrawable.cs b/osu.Game/Graphics/UserInterfaceV2/LabelledDrawable.cs index 066e1a7978..2353d9e0e8 100644 --- a/osu.Game/Graphics/UserInterfaceV2/LabelledDrawable.cs +++ b/osu.Game/Graphics/UserInterfaceV2/LabelledDrawable.cs @@ -154,7 +154,7 @@ namespace osu.Game.Graphics.UserInterfaceV2 [BackgroundDependencyLoader(true)] private void load(OverlayColourProvider? colourProvider, OsuColour osuColour) { - background.Colour = colourProvider?.Background4 ?? Color4Extensions.FromHex(@"1c2125"); + background.Colour = colourProvider?.Background5 ?? Color4Extensions.FromHex(@"1c2125"); descriptionText.Colour = osuColour.Yellow; } diff --git a/osu.Game/Screens/Edit/EditorRoundedScreenSettings.cs b/osu.Game/Screens/Edit/EditorRoundedScreenSettings.cs index cb17484d27..94a83a82aa 100644 --- a/osu.Game/Screens/Edit/EditorRoundedScreenSettings.cs +++ b/osu.Game/Screens/Edit/EditorRoundedScreenSettings.cs @@ -8,6 +8,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Game.Graphics.Containers; using osu.Game.Overlays; +using osuTK; namespace osu.Game.Screens.Edit { @@ -22,7 +23,7 @@ namespace osu.Game.Screens.Edit { new Box { - Colour = colours.Background4, + Colour = colours.Background6, RelativeSizeAxes = Axes.Both, }, new OsuScrollContainer @@ -33,6 +34,8 @@ namespace osu.Game.Screens.Edit RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, Direction = FillDirection.Vertical, + Padding = new MarginPadding(10), + Spacing = new Vector2(10), Children = CreateSections() }, } diff --git a/osu.Game/Screens/Edit/Timing/Section.cs b/osu.Game/Screens/Edit/Timing/Section.cs index 8659b7aff6..139abfb187 100644 --- a/osu.Game/Screens/Edit/Timing/Section.cs +++ b/osu.Game/Screens/Edit/Timing/Section.cs @@ -44,9 +44,15 @@ namespace osu.Game.Screens.Edit.Timing AutoSizeAxes = Axes.Y; Masking = true; + CornerRadius = 5; InternalChildren = new Drawable[] { + new Box + { + Colour = colours.Background4, + RelativeSizeAxes = Axes.Both, + }, new Container { RelativeSizeAxes = Axes.X, @@ -69,11 +75,6 @@ namespace osu.Game.Screens.Edit.Timing AutoSizeAxes = Axes.Y, Children = new Drawable[] { - new Box - { - Colour = colours.Background3, - RelativeSizeAxes = Axes.Both, - }, Flow = new FillFlowContainer { Padding = new MarginPadding(20), diff --git a/osu.Game/Screens/Edit/Timing/SliderWithTextBoxInput.cs b/osu.Game/Screens/Edit/Timing/SliderWithTextBoxInput.cs index 67f1dacec4..9f036f0215 100644 --- a/osu.Game/Screens/Edit/Timing/SliderWithTextBoxInput.cs +++ b/osu.Game/Screens/Edit/Timing/SliderWithTextBoxInput.cs @@ -11,6 +11,7 @@ using osu.Framework.Localisation; using osu.Game.Graphics.UserInterfaceV2; using osu.Game.Overlays.Settings; using osu.Game.Utils; +using osuTK; namespace osu.Game.Screens.Edit.Timing { @@ -33,6 +34,7 @@ namespace osu.Game.Screens.Edit.Timing RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, Direction = FillDirection.Vertical, + Spacing = new Vector2(20), Children = new Drawable[] { textBox = new LabelledTextBox From 648cee71061f043eb813c5eb7e01bebfd63ecb22 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 24 May 2022 17:23:42 +0900 Subject: [PATCH 167/395] Remove editor screen transitions for now --- osu.Game/Screens/Edit/Editor.cs | 1 + osu.Game/Screens/Edit/EditorScreen.cs | 12 ++---------- 2 files changed, 3 insertions(+), 10 deletions(-) diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 0bb3f51903..90bf9c6c3c 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -777,6 +777,7 @@ namespace osu.Game.Screens.Edit if ((currentScreen = screenContainer.SingleOrDefault(s => s.Type == e.NewValue)) != null) { screenContainer.ChangeChildDepth(currentScreen, lastScreen?.Depth + 1 ?? 0); + currentScreen.Show(); return; } diff --git a/osu.Game/Screens/Edit/EditorScreen.cs b/osu.Game/Screens/Edit/EditorScreen.cs index 2837cdcd9a..31c34edd7c 100644 --- a/osu.Game/Screens/Edit/EditorScreen.cs +++ b/osu.Game/Screens/Edit/EditorScreen.cs @@ -33,17 +33,9 @@ namespace osu.Game.Screens.Edit InternalChild = content = new PopoverContainer { RelativeSizeAxes = Axes.Both }; } - protected override void PopIn() - { - this.ScaleTo(1f, 200, Easing.OutQuint) - .FadeIn(200, Easing.OutQuint); - } + protected override void PopIn() => this.FadeIn(); - protected override void PopOut() - { - this.ScaleTo(0.98f, 200, Easing.OutQuint) - .FadeOut(200, Easing.OutQuint); - } + protected override void PopOut() => this.FadeOut(); #region Clipboard operations From 0bcf8c846c91fb5482615d39fb82b0b45498a82b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 24 May 2022 17:36:43 +0900 Subject: [PATCH 168/395] Change editor colour scheme to aquamarine --- osu.Game.Tests/Visual/Editing/TestSceneBeatDivisorControl.cs | 2 +- osu.Game/Overlays/OverlayColourProvider.cs | 4 ++++ osu.Game/Screens/Edit/Editor.cs | 2 +- osu.Game/Tests/Visual/EditorClockTestScene.cs | 4 ++++ 4 files changed, 10 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneBeatDivisorControl.cs b/osu.Game.Tests/Visual/Editing/TestSceneBeatDivisorControl.cs index cdd1175692..073a228224 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneBeatDivisorControl.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneBeatDivisorControl.cs @@ -28,7 +28,7 @@ namespace osu.Game.Tests.Visual.Editing private Triangle tickMarkerHead => tickSliderBar.ChildrenOfType().Single(); [Cached] - private readonly OverlayColourProvider overlayColour = new OverlayColourProvider(OverlayColourScheme.Green); + private readonly OverlayColourProvider overlayColour = new OverlayColourProvider(OverlayColourScheme.Aquamarine); [SetUp] public void SetUp() => Schedule(() => diff --git a/osu.Game/Overlays/OverlayColourProvider.cs b/osu.Game/Overlays/OverlayColourProvider.cs index 7bddb924a0..a4f6527024 100644 --- a/osu.Game/Overlays/OverlayColourProvider.cs +++ b/osu.Game/Overlays/OverlayColourProvider.cs @@ -72,6 +72,9 @@ namespace osu.Game.Overlays case OverlayColourScheme.Green: return 125 / 360f; + case OverlayColourScheme.Aquamarine: + return 160 / 360f; + case OverlayColourScheme.Purple: return 255 / 360f; @@ -94,5 +97,6 @@ namespace osu.Game.Overlays Purple, Blue, Plum, + Aquamarine } } diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 90bf9c6c3c..7e7764397c 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -140,7 +140,7 @@ namespace osu.Game.Screens.Edit public readonly EditorClipboard Clipboard = new EditorClipboard(); [Cached] - private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Blue); + private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Aquamarine); public Editor(EditorLoader loader = null) { diff --git a/osu.Game/Tests/Visual/EditorClockTestScene.cs b/osu.Game/Tests/Visual/EditorClockTestScene.cs index 66ab427565..542f06f86b 100644 --- a/osu.Game/Tests/Visual/EditorClockTestScene.cs +++ b/osu.Game/Tests/Visual/EditorClockTestScene.cs @@ -5,6 +5,7 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Input.Events; using osu.Game.Beatmaps; +using osu.Game.Overlays; using osu.Game.Screens.Edit; namespace osu.Game.Tests.Visual @@ -15,6 +16,9 @@ namespace osu.Game.Tests.Visual /// public abstract class EditorClockTestScene : OsuManualInputManagerTestScene { + [Cached] + private readonly OverlayColourProvider overlayColour = new OverlayColourProvider(OverlayColourScheme.Aquamarine); + protected readonly BindableBeatDivisor BeatDivisor = new BindableBeatDivisor(); protected new readonly EditorClock Clock; From fce527b0dfbcd056dc2b2590448ae2925c803d9e Mon Sep 17 00:00:00 2001 From: Jai Sharma Date: Tue, 24 May 2022 21:25:27 +0100 Subject: [PATCH 169/395] Schedule channel manager bindable events to ensure they happen after the first poll --- osu.Game/Overlays/ChatOverlayV2.cs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/osu.Game/Overlays/ChatOverlayV2.cs b/osu.Game/Overlays/ChatOverlayV2.cs index 441042432a..3aab81cb04 100644 --- a/osu.Game/Overlays/ChatOverlayV2.cs +++ b/osu.Game/Overlays/ChatOverlayV2.cs @@ -155,13 +155,15 @@ namespace osu.Game.Overlays chatHeight.BindValueChanged(height => { Height = height.NewValue; }, true); currentChannel.BindTo(channelManager.CurrentChannel); - currentChannel.BindValueChanged(currentChannelChanged, true); - joinedChannels.BindTo(channelManager.JoinedChannels); - joinedChannels.BindCollectionChanged(joinedChannelsChanged, true); - availableChannels.BindTo(channelManager.AvailableChannels); - availableChannels.BindCollectionChanged(availableChannelsChanged, true); + + Schedule(() => + { + currentChannel.BindValueChanged(currentChannelChanged, true); + joinedChannels.BindCollectionChanged(joinedChannelsChanged, true); + availableChannels.BindCollectionChanged(availableChannelsChanged, true); + }); channelList.OnRequestSelect += channel => channelManager.CurrentChannel.Value = channel; channelList.OnRequestLeave += channel => channelManager.LeaveChannel(channel); From c33aee17b002cc863ebb4826560de4257fe59ca5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 24 May 2022 23:14:28 +0200 Subject: [PATCH 170/395] Cache some more `OverlayColourProvider`s to fix tests --- .../Editor/TestSceneManiaComposeScreen.cs | 2 ++ osu.Game.Tests/Visual/Editing/TestSceneComposeScreen.cs | 2 ++ osu.Game.Tests/Visual/Editing/TestSceneTimelineTickDisplay.cs | 4 ++++ 3 files changed, 8 insertions(+) diff --git a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaComposeScreen.cs b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaComposeScreen.cs index 5dd7c23ab6..746bdae02e 100644 --- a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaComposeScreen.cs +++ b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaComposeScreen.cs @@ -9,6 +9,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Testing; using osu.Game.Database; +using osu.Game.Overlays; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Screens.Edit; @@ -45,6 +46,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor { (typeof(EditorBeatmap), editorBeatmap), (typeof(IBeatSnapProvider), editorBeatmap), + (typeof(OverlayColourProvider), new OverlayColourProvider(OverlayColourScheme.Green)), }, Child = new ComposeScreen { State = { Value = Visibility.Visible } }, }; diff --git a/osu.Game.Tests/Visual/Editing/TestSceneComposeScreen.cs b/osu.Game.Tests/Visual/Editing/TestSceneComposeScreen.cs index 30c8539d85..fa15c00cd4 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneComposeScreen.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneComposeScreen.cs @@ -9,6 +9,7 @@ using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Testing; +using osu.Game.Overlays; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Beatmaps; @@ -47,6 +48,7 @@ namespace osu.Game.Tests.Visual.Editing { (typeof(EditorBeatmap), editorBeatmap), (typeof(IBeatSnapProvider), editorBeatmap), + (typeof(OverlayColourProvider), new OverlayColourProvider(OverlayColourScheme.Green)), }, Child = new ComposeScreen { State = { Value = Visibility.Visible } }, }; diff --git a/osu.Game.Tests/Visual/Editing/TestSceneTimelineTickDisplay.cs b/osu.Game.Tests/Visual/Editing/TestSceneTimelineTickDisplay.cs index 20e58c3d2a..b78512e469 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneTimelineTickDisplay.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneTimelineTickDisplay.cs @@ -4,6 +4,7 @@ using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics; +using osu.Game.Overlays; using osu.Game.Screens.Edit.Compose.Components; using osuTK; @@ -14,6 +15,9 @@ namespace osu.Game.Tests.Visual.Editing { public override Drawable CreateTestComponent() => Empty(); // tick display is implicitly inside the timeline. + [Cached] + private readonly OverlayColourProvider overlayColour = new OverlayColourProvider(OverlayColourScheme.Green); + [BackgroundDependencyLoader] private void load() { From f6810d3f59d831e02ae1c4a42fe61001746858d8 Mon Sep 17 00:00:00 2001 From: Jai Sharma Date: Tue, 24 May 2022 23:05:25 +0100 Subject: [PATCH 171/395] Add keyboard shortcuts to `ChatOverlayV2` with tests --- .../Visual/Online/TestSceneChatOverlayV2.cs | 72 +++++++++++++++++++ osu.Game/Overlays/ChatOverlayV2.cs | 52 +++++++++++++- 2 files changed, 122 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneChatOverlayV2.cs b/osu.Game.Tests/Visual/Online/TestSceneChatOverlayV2.cs index 7268dd31f6..0580d20171 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneChatOverlayV2.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneChatOverlayV2.cs @@ -12,6 +12,7 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Input; using osu.Framework.Logging; using osu.Framework.Testing; using osu.Framework.Utils; @@ -417,6 +418,67 @@ namespace osu.Game.Tests.Visual.Online AddAssert("Channel 1 displayed", () => channelIsVisible && currentDrawableChannel.Channel == testChannel1); } + [Test] + public void TestKeyboardCloseAndRestoreChannel() + { + AddStep("Show overlay with channel 1", () => + { + channelManager.JoinChannel(testChannel1); + chatOverlay.Show(); + }); + AddAssert("Channel 1 displayed", () => channelIsVisible && currentDrawableChannel.Channel == testChannel1); + + AddStep("Press document close keys", () => InputManager.Keys(PlatformAction.DocumentClose)); + AddAssert("Listing is visible", () => listingIsVisible); + + AddStep("Press tab restore keys", () => InputManager.Keys(PlatformAction.TabRestore)); + AddAssert("Channel 1 displayed", () => channelIsVisible && currentDrawableChannel.Channel == testChannel1); + } + + [Test] + public void TestKeyboardNewChannel() + { + AddStep("Show overlay with channel 1", () => + { + channelManager.JoinChannel(testChannel1); + chatOverlay.Show(); + }); + AddAssert("Channel 1 displayed", () => channelIsVisible && currentDrawableChannel.Channel == testChannel1); + + AddStep("Press tab new keys", () => InputManager.Keys(PlatformAction.TabNew)); + AddAssert("Listing is visible", () => listingIsVisible); + } + + [Test] + public void TestKeyboardNextChannel() + { + Channel pmChannel1 = createPrivateChannel(); + Channel pmChannel2 = createPrivateChannel(); + + AddStep("Show overlay with channels", () => + { + channelManager.JoinChannel(testChannel1); + channelManager.JoinChannel(testChannel2); + channelManager.JoinChannel(pmChannel1); + channelManager.JoinChannel(pmChannel2); + chatOverlay.Show(); + }); + + AddAssert("Channel 1 displayed", () => channelIsVisible && currentDrawableChannel.Channel == testChannel1); + + AddStep("Press document next keys", () => InputManager.Keys(PlatformAction.DocumentNext)); + AddAssert("Channel 2 displayed", () => channelIsVisible && currentDrawableChannel.Channel == testChannel2); + + AddStep("Press document next keys", () => InputManager.Keys(PlatformAction.DocumentNext)); + AddAssert("PM Channel 1 displayed", () => channelIsVisible && currentDrawableChannel.Channel == pmChannel1); + + AddStep("Press document next keys", () => InputManager.Keys(PlatformAction.DocumentNext)); + AddAssert("PM Channel 2 displayed", () => channelIsVisible && currentDrawableChannel.Channel == pmChannel2); + + AddStep("Press document next keys", () => InputManager.Keys(PlatformAction.DocumentNext)); + AddAssert("Channel 1 displayed", () => channelIsVisible && currentDrawableChannel.Channel == testChannel1); + } + private bool listingIsVisible => chatOverlay.ChildrenOfType().Single().State.Value == Visibility.Visible; @@ -467,6 +529,16 @@ namespace osu.Game.Tests.Visual.Online Type = ChannelType.Public, }; + private Channel createPrivateChannel() + { + int id = RNG.Next(0, 10000); + return new Channel(new APIUser + { + Id = id, + Username = $"test user {id}", + }); + } + private class TestChatOverlayV2 : ChatOverlayV2 { public bool SlowLoading { get; set; } diff --git a/osu.Game/Overlays/ChatOverlayV2.cs b/osu.Game/Overlays/ChatOverlayV2.cs index 3aab81cb04..39717b1f31 100644 --- a/osu.Game/Overlays/ChatOverlayV2.cs +++ b/osu.Game/Overlays/ChatOverlayV2.cs @@ -13,6 +13,8 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; +using osu.Framework.Input; +using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; using osu.Framework.Localisation; using osu.Game.Configuration; @@ -26,7 +28,7 @@ using osu.Game.Overlays.Chat.Listing; namespace osu.Game.Overlays { - public class ChatOverlayV2 : OsuFocusedOverlayContainer, INamedOverlayComponent + public class ChatOverlayV2 : OsuFocusedOverlayContainer, INamedOverlayComponent, IKeyBindingHandler { public string IconTexture => "Icons/Hexacons/messaging"; public LocalisableString Title => ChatStrings.HeaderTitle; @@ -197,6 +199,35 @@ namespace osu.Game.Overlays Show(); } + public bool OnPressed(KeyBindingPressEvent e) + { + switch (e.Action) + { + case PlatformAction.TabNew: + currentChannel.Value = channelList.ChannelListingChannel; + return true; + + case PlatformAction.DocumentClose: + channelManager.LeaveChannel(currentChannel.Value); + return true; + + case PlatformAction.TabRestore: + channelManager.JoinLastClosedChannel(); + return true; + + case PlatformAction.DocumentNext: + cycleChannel(); + return true; + + default: + return false; + } + } + + public void OnReleased(KeyBindingReleaseEvent e) + { + } + protected override bool OnDragStart(DragStartEvent e) { isDraggingTopBar = topBar.IsHovered; @@ -341,7 +372,7 @@ namespace osu.Game.Overlays private void availableChannelsChanged(object sender, NotifyCollectionChangedEventArgs args) => channelListing.UpdateAvailableChannels(channelManager.AvailableChannels); - private IEnumerable filterChannels(IList channels) + private IEnumerable filterChannels(IEnumerable channels) => channels.Cast().Where(c => c.Type == ChannelType.Public || c.Type == ChannelType.PM); private void handleChatMessage(string message) @@ -354,5 +385,22 @@ namespace osu.Game.Overlays else channelManager.PostMessage(message); } + + private void cycleChannel() + { + List overlayChannels = filterChannels(channelManager.JoinedChannels).ToList(); + + if (overlayChannels.Count < 2) + return; + + int currentIdx = overlayChannels.IndexOf(currentChannel.Value); + int nextIdx = currentIdx + 1; + + // Cycle the list when reaching the end + if (nextIdx > overlayChannels.Count - 1) + nextIdx = 0; + + currentChannel.Value = overlayChannels[nextIdx]; + } } } From de5d2df100a58a5dcd407ab434f2512be215d400 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 25 May 2022 12:17:11 +0900 Subject: [PATCH 172/395] Move UI scale setup to the beginning of the first run setup --- .../Visual/UserInterface/TestSceneFirstRunSetupOverlay.cs | 4 ++-- osu.Game/Overlays/FirstRunSetupOverlay.cs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneFirstRunSetupOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneFirstRunSetupOverlay.cs index 48b5690243..d09efdc925 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneFirstRunSetupOverlay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneFirstRunSetupOverlay.cs @@ -185,7 +185,7 @@ namespace osu.Game.Tests.Visual.UserInterface { AddStep("step to next", () => overlay.NextButton.TriggerClick()); - AddAssert("is at known screen", () => overlay.CurrentScreen is ScreenBeatmaps); + AddAssert("is at known screen", () => overlay.CurrentScreen is ScreenUIScale); AddStep("hide", () => overlay.Hide()); AddAssert("overlay hidden", () => overlay.State.Value == Visibility.Hidden); @@ -195,7 +195,7 @@ namespace osu.Game.Tests.Visual.UserInterface AddStep("run notification action", () => lastNotification.Activated()); AddAssert("overlay shown", () => overlay.State.Value == Visibility.Visible); - AddAssert("is resumed", () => overlay.CurrentScreen is ScreenBeatmaps); + AddAssert("is resumed", () => overlay.CurrentScreen is ScreenUIScale); } // interface mocks break hot reload, mocking this stub implementation instead works around it. diff --git a/osu.Game/Overlays/FirstRunSetupOverlay.cs b/osu.Game/Overlays/FirstRunSetupOverlay.cs index 7b0de4affe..a5bece0832 100644 --- a/osu.Game/Overlays/FirstRunSetupOverlay.cs +++ b/osu.Game/Overlays/FirstRunSetupOverlay.cs @@ -76,10 +76,10 @@ namespace osu.Game.Overlays private void load(OsuColour colours, LegacyImportManager? legacyImportManager) { steps.Add(typeof(ScreenWelcome)); + steps.Add(typeof(ScreenUIScale)); steps.Add(typeof(ScreenBeatmaps)); if (legacyImportManager?.SupportsImportFromStable == true) steps.Add(typeof(ScreenImportFromStable)); - steps.Add(typeof(ScreenUIScale)); steps.Add(typeof(ScreenBehaviour)); Header.Title = FirstRunSetupOverlayStrings.FirstRunSetupTitle; From 87959a59d980286daf6c082ef3edda8494e94bf6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 25 May 2022 18:28:29 +0900 Subject: [PATCH 173/395] Add missing "announce" channel type Of note, this doesn't mean the channels will display, but it does fix parsing errors which cause the whole chat display to fail. --- osu.Game/Online/Chat/ChannelType.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Online/Chat/ChannelType.cs b/osu.Game/Online/Chat/ChannelType.cs index 151efc4645..bd628e90c4 100644 --- a/osu.Game/Online/Chat/ChannelType.cs +++ b/osu.Game/Online/Chat/ChannelType.cs @@ -13,5 +13,6 @@ namespace osu.Game.Online.Chat PM, Group, System, + Announce, } } From a92089c443b9cd256f145aef6711b76bd4e18579 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 25 May 2022 18:42:33 +0900 Subject: [PATCH 174/395] Change filter method to consider all non-system channels as joinable --- osu.Game/Overlays/ChatOverlayV2.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Overlays/ChatOverlayV2.cs b/osu.Game/Overlays/ChatOverlayV2.cs index 39717b1f31..a23595e1c0 100644 --- a/osu.Game/Overlays/ChatOverlayV2.cs +++ b/osu.Game/Overlays/ChatOverlayV2.cs @@ -342,7 +342,7 @@ namespace osu.Game.Overlays switch (args.Action) { case NotifyCollectionChangedAction.Add: - IEnumerable newChannels = filterChannels(args.NewItems); + IEnumerable newChannels = filterToChatChannels(args.NewItems); foreach (var channel in newChannels) channelList.AddChannel(channel); @@ -350,7 +350,7 @@ namespace osu.Game.Overlays break; case NotifyCollectionChangedAction.Remove: - IEnumerable leftChannels = filterChannels(args.OldItems); + IEnumerable leftChannels = filterToChatChannels(args.OldItems); foreach (var channel in leftChannels) { @@ -372,8 +372,6 @@ namespace osu.Game.Overlays private void availableChannelsChanged(object sender, NotifyCollectionChangedEventArgs args) => channelListing.UpdateAvailableChannels(channelManager.AvailableChannels); - private IEnumerable filterChannels(IEnumerable channels) - => channels.Cast().Where(c => c.Type == ChannelType.Public || c.Type == ChannelType.PM); private void handleChatMessage(string message) { @@ -402,5 +400,7 @@ namespace osu.Game.Overlays currentChannel.Value = overlayChannels[nextIdx]; } + + private IEnumerable filterToChatChannels(IEnumerable channels) => channels.Cast().Where(c => c.Type != ChannelType.System); } } From 49ab031e75f0398ed321b0664d68acee9aba5b31 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 25 May 2022 18:42:49 +0900 Subject: [PATCH 175/395] Allow traversing in both directions using ctrl-tab shortcuts --- osu.Game/Overlays/ChatOverlayV2.cs | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/osu.Game/Overlays/ChatOverlayV2.cs b/osu.Game/Overlays/ChatOverlayV2.cs index a23595e1c0..d0355e0165 100644 --- a/osu.Game/Overlays/ChatOverlayV2.cs +++ b/osu.Game/Overlays/ChatOverlayV2.cs @@ -215,8 +215,12 @@ namespace osu.Game.Overlays channelManager.JoinLastClosedChannel(); return true; + case PlatformAction.DocumentPrevious: + cycleChannel(-1); + return true; + case PlatformAction.DocumentNext: - cycleChannel(); + cycleChannel(1); return true; default: @@ -372,7 +376,6 @@ namespace osu.Game.Overlays private void availableChannelsChanged(object sender, NotifyCollectionChangedEventArgs args) => channelListing.UpdateAvailableChannels(channelManager.AvailableChannels); - private void handleChatMessage(string message) { if (string.IsNullOrWhiteSpace(message)) @@ -384,21 +387,16 @@ namespace osu.Game.Overlays channelManager.PostMessage(message); } - private void cycleChannel() + private void cycleChannel(int direction) { - List overlayChannels = filterChannels(channelManager.JoinedChannels).ToList(); + List overlayChannels = filterToChatChannels(channelManager.JoinedChannels).ToList(); if (overlayChannels.Count < 2) return; - int currentIdx = overlayChannels.IndexOf(currentChannel.Value); - int nextIdx = currentIdx + 1; + int currentIndex = overlayChannels.IndexOf(currentChannel.Value); - // Cycle the list when reaching the end - if (nextIdx > overlayChannels.Count - 1) - nextIdx = 0; - - currentChannel.Value = overlayChannels[nextIdx]; + currentChannel.Value = overlayChannels[(currentIndex + direction + overlayChannels.Count) % overlayChannels.Count]; } private IEnumerable filterToChatChannels(IEnumerable channels) => channels.Cast().Where(c => c.Type != ChannelType.System); From f1d4902be524cbd65a73c8298739ba36f1990ef4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 25 May 2022 19:25:23 +0900 Subject: [PATCH 176/395] Remove unnecessary bindable cast --- osu.Game/Screens/Edit/Timing/WaveformComparisonDisplay.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Edit/Timing/WaveformComparisonDisplay.cs b/osu.Game/Screens/Edit/Timing/WaveformComparisonDisplay.cs index 0830ba8cf4..c80d3c4261 100644 --- a/osu.Game/Screens/Edit/Timing/WaveformComparisonDisplay.cs +++ b/osu.Game/Screens/Edit/Timing/WaveformComparisonDisplay.cs @@ -47,7 +47,7 @@ namespace osu.Game.Screens.Edit.Timing private double selectedGroupStartTime; private double selectedGroupEndTime; - private readonly BindableList controlPointGroups = new BindableList(); + private readonly IBindableList controlPointGroups = new BindableList(); public WaveformComparisonDisplay() { @@ -83,7 +83,7 @@ namespace osu.Game.Screens.Edit.Timing selectedGroup.BindValueChanged(_ => updateTimingGroup(), true); - ((IBindableList)controlPointGroups).BindTo(editorBeatmap.ControlPointInfo.Groups); + controlPointGroups.BindTo(editorBeatmap.ControlPointInfo.Groups); controlPointGroups.BindCollectionChanged((_, __) => updateTimingGroup()); beatLength.BindValueChanged(_ => showFrom(lastDisplayedBeatIndex), true); From 795c9fcbc9cfa9345d9ed1cbde3a8e9fc4a7606d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 25 May 2022 19:38:50 +0900 Subject: [PATCH 177/395] Update framework --- osu.Android.props | 2 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index aaea784852..d5a77c6349 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -52,7 +52,7 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 5bc65ca507..32a0adb859 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -35,7 +35,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/osu.iOS.props b/osu.iOS.props index 3597e7e5c0..112b5b4615 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -61,7 +61,7 @@ - + @@ -84,7 +84,7 @@ - + From 4a36f3aa4c5f7c6af19627ed38ccc48dc353a39f Mon Sep 17 00:00:00 2001 From: Jai Sharma Date: Wed, 25 May 2022 12:29:04 +0100 Subject: [PATCH 178/395] Ensure channel traversal shortcut is using visual order --- osu.Game/Overlays/Chat/ChannelList/ChannelList.cs | 7 +++++++ osu.Game/Overlays/ChatOverlayV2.cs | 5 +++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/Chat/ChannelList/ChannelList.cs b/osu.Game/Overlays/Chat/ChannelList/ChannelList.cs index d1ceae604c..f212f5b162 100644 --- a/osu.Game/Overlays/Chat/ChannelList/ChannelList.cs +++ b/osu.Game/Overlays/Chat/ChannelList/ChannelList.cs @@ -4,6 +4,7 @@ #nullable enable using System; +using System.Linq; using System.Collections.Generic; using osu.Framework.Allocation; using osu.Framework.Graphics; @@ -22,6 +23,8 @@ namespace osu.Game.Overlays.Chat.ChannelList public Action? OnRequestSelect; public Action? OnRequestLeave; + public IEnumerable Channels => publicChannelFlow.Channels.Concat(privateChannelFlow.Channels); + public readonly ChannelListing.ChannelListingChannel ChannelListingChannel = new ChannelListing.ChannelListingChannel(); private readonly Dictionary channelMap = new Dictionary(); @@ -118,6 +121,10 @@ namespace osu.Game.Overlays.Chat.ChannelList private class ChannelListItemFlow : FillFlowContainer { + public IEnumerable Channels => Children.Where(c => c is ChannelListItem) + .Cast() + .Select(c => c.Channel); + public ChannelListItemFlow(string label) { Direction = FillDirection.Vertical; diff --git a/osu.Game/Overlays/ChatOverlayV2.cs b/osu.Game/Overlays/ChatOverlayV2.cs index d0355e0165..cc780073ce 100644 --- a/osu.Game/Overlays/ChatOverlayV2.cs +++ b/osu.Game/Overlays/ChatOverlayV2.cs @@ -389,7 +389,7 @@ namespace osu.Game.Overlays private void cycleChannel(int direction) { - List overlayChannels = filterToChatChannels(channelManager.JoinedChannels).ToList(); + List overlayChannels = channelList.Channels.ToList(); if (overlayChannels.Count < 2) return; @@ -399,6 +399,7 @@ namespace osu.Game.Overlays currentChannel.Value = overlayChannels[(currentIndex + direction + overlayChannels.Count) % overlayChannels.Count]; } - private IEnumerable filterToChatChannels(IEnumerable channels) => channels.Cast().Where(c => c.Type != ChannelType.System); + private IEnumerable filterToChatChannels(IEnumerable channels) + => channels.Cast().Where(c => c.Type == ChannelType.PM || c.Type == ChannelType.Public); } } From cd90d2931534bf8210fedfc6a4c2efbb5863d05a Mon Sep 17 00:00:00 2001 From: Jai Sharma Date: Wed, 25 May 2022 12:37:28 +0100 Subject: [PATCH 179/395] Ensure channel traversal shortcut scrolls channel list item into view --- osu.Game/Overlays/Chat/ChannelList/ChannelList.cs | 5 ++++- osu.Game/Overlays/ChatOverlayV2.cs | 2 ++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Chat/ChannelList/ChannelList.cs b/osu.Game/Overlays/Chat/ChannelList/ChannelList.cs index f212f5b162..2798841925 100644 --- a/osu.Game/Overlays/Chat/ChannelList/ChannelList.cs +++ b/osu.Game/Overlays/Chat/ChannelList/ChannelList.cs @@ -29,6 +29,7 @@ namespace osu.Game.Overlays.Chat.ChannelList private readonly Dictionary channelMap = new Dictionary(); + private OsuScrollContainer scroll = null!; private ChannelListItemFlow publicChannelFlow = null!; private ChannelListItemFlow privateChannelFlow = null!; private ChannelListItem selector = null!; @@ -43,7 +44,7 @@ namespace osu.Game.Overlays.Chat.ChannelList RelativeSizeAxes = Axes.Both, Colour = colourProvider.Background6, }, - new OsuScrollContainer + scroll = new OsuScrollContainer { Padding = new MarginPadding { Vertical = 7 }, RelativeSizeAxes = Axes.Both, @@ -104,6 +105,8 @@ namespace osu.Game.Overlays.Chat.ChannelList return channelMap[channel]; } + public void ScrollChannelIntoView(Channel channel) => scroll.ScrollIntoView(this.GetItem(channel)); + private ChannelListItemFlow getFlowForChannel(Channel channel) { switch (channel.Type) diff --git a/osu.Game/Overlays/ChatOverlayV2.cs b/osu.Game/Overlays/ChatOverlayV2.cs index cc780073ce..ad7a856dbc 100644 --- a/osu.Game/Overlays/ChatOverlayV2.cs +++ b/osu.Game/Overlays/ChatOverlayV2.cs @@ -397,6 +397,8 @@ namespace osu.Game.Overlays int currentIndex = overlayChannels.IndexOf(currentChannel.Value); currentChannel.Value = overlayChannels[(currentIndex + direction + overlayChannels.Count) % overlayChannels.Count]; + + channelList.ScrollChannelIntoView(currentChannel.Value); } private IEnumerable filterToChatChannels(IEnumerable channels) From c4cae7f677968a72a4363ce9970f00b7891b9c2b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 25 May 2022 22:19:24 +0900 Subject: [PATCH 180/395] Update `EditorMenuBar` to match new design language --- .../Edit/Components/Menus/EditorMenuBar.cs | 107 +++++++----------- 1 file changed, 44 insertions(+), 63 deletions(-) diff --git a/osu.Game/Screens/Edit/Components/Menus/EditorMenuBar.cs b/osu.Game/Screens/Edit/Components/Menus/EditorMenuBar.cs index 2a8435ff47..440071bc4c 100644 --- a/osu.Game/Screens/Edit/Components/Menus/EditorMenuBar.cs +++ b/osu.Game/Screens/Edit/Components/Menus/EditorMenuBar.cs @@ -2,16 +2,13 @@ // 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.Shapes; using osu.Framework.Graphics.UserInterface; using osu.Framework.Input.Events; using osu.Game.Graphics; using osu.Game.Graphics.UserInterface; +using osu.Game.Overlays; using osuTK; -using osuTK.Graphics; namespace osu.Game.Screens.Edit.Components.Menus { @@ -24,7 +21,12 @@ namespace osu.Game.Screens.Edit.Components.Menus MaskingContainer.CornerRadius = 0; ItemsContainer.Padding = new MarginPadding { Left = 100 }; - BackgroundColour = Color4Extensions.FromHex("111"); + } + + [BackgroundDependencyLoader] + private void load(OverlayColourProvider colourProvider) + { + BackgroundColour = colourProvider.Background3; } protected override Framework.Graphics.UserInterface.Menu CreateSubMenu() => new SubMenu(); @@ -33,29 +35,26 @@ namespace osu.Game.Screens.Edit.Components.Menus private class DrawableEditorBarMenuItem : DrawableOsuMenuItem { - private BackgroundBox background; - public DrawableEditorBarMenuItem(MenuItem item) : base(item) { - Anchor = Anchor.CentreLeft; - Origin = Anchor.CentreLeft; - - StateChanged += stateChanged; } [BackgroundDependencyLoader] - private void load(OsuColour colours) + private void load(OverlayColourProvider colourProvider) { - ForegroundColour = colours.BlueLight; - BackgroundColour = Color4.Transparent; - ForegroundColourHover = Color4.White; - BackgroundColourHover = colours.Gray3; + ForegroundColour = colourProvider.Light3; + BackgroundColour = colourProvider.Background2; + ForegroundColourHover = colourProvider.Content1; + BackgroundColourHover = colourProvider.Background1; } - public override void SetFlowDirection(Direction direction) + protected override void LoadComplete() { - AutoSizeAxes = Axes.Both; + base.LoadComplete(); + + Foreground.Anchor = Anchor.CentreLeft; + Foreground.Origin = Anchor.CentreLeft; } protected override void UpdateBackgroundColour() @@ -74,54 +73,16 @@ namespace osu.Game.Screens.Edit.Components.Menus base.UpdateForegroundColour(); } - private void stateChanged(MenuItemState newState) - { - if (newState == MenuItemState.Selected) - background.Expand(); - else - background.Contract(); - } - - protected override Drawable CreateBackground() => background = new BackgroundBox(); protected override DrawableOsuMenuItem.TextContainer CreateTextContainer() => new TextContainer(); private new class TextContainer : DrawableOsuMenuItem.TextContainer { public TextContainer() { - NormalText.Font = NormalText.Font.With(size: 14); - BoldText.Font = BoldText.Font.With(size: 14); - NormalText.Margin = BoldText.Margin = new MarginPadding { Horizontal = 10, Vertical = MARGIN_VERTICAL }; + NormalText.Font = OsuFont.TorusAlternate; + BoldText.Font = OsuFont.TorusAlternate.With(weight: FontWeight.Bold); } } - - private class BackgroundBox : CompositeDrawable - { - private readonly Container innerBackground; - - public BackgroundBox() - { - RelativeSizeAxes = Axes.Both; - Masking = true; - InternalChild = innerBackground = new Container - { - RelativeSizeAxes = Axes.Both, - Masking = true, - CornerRadius = 4, - Child = new Box { RelativeSizeAxes = Axes.Both } - }; - } - - /// - /// Expands the background such that it doesn't show the bottom corners. - /// - public void Expand() => innerBackground.Height = 2; - - /// - /// Contracts the background such that it shows the bottom corners. - /// - public void Contract() => innerBackground.Height = 1; - } } private class SubMenu : OsuMenu @@ -129,14 +90,15 @@ namespace osu.Game.Screens.Edit.Components.Menus public SubMenu() : base(Direction.Vertical) { - OriginPosition = new Vector2(5, 1); - ItemsContainer.Padding = new MarginPadding { Top = 5, Bottom = 5 }; + ItemsContainer.Padding = new MarginPadding(); + + MaskingContainer.CornerRadius = 0; } [BackgroundDependencyLoader] - private void load(OsuColour colours) + private void load(OverlayColourProvider colourProvider) { - BackgroundColour = colours.Gray3; + BackgroundColour = colourProvider.Background2; } protected override Framework.Graphics.UserInterface.Menu CreateSubMenu() => new SubMenu(); @@ -147,9 +109,27 @@ namespace osu.Game.Screens.Edit.Components.Menus { case EditorMenuItemSpacer spacer: return new DrawableSpacer(spacer); + + default: + return new EditorMenuItem(item); + } + } + + private class EditorMenuItem : DrawableOsuMenuItem + { + public EditorMenuItem(MenuItem item) + : base(item) + { } - return base.CreateDrawableMenuItem(item); + [BackgroundDependencyLoader] + private void load(OverlayColourProvider colourProvider) + { + BackgroundColour = colourProvider.Background2; + BackgroundColourHover = colourProvider.Background1; + + Foreground.Padding = new MarginPadding { Vertical = 2 }; + } } private class DrawableSpacer : DrawableOsuMenuItem @@ -157,6 +137,7 @@ namespace osu.Game.Screens.Edit.Components.Menus public DrawableSpacer(MenuItem item) : base(item) { + Scale = new Vector2(1, 0.3f); } protected override bool OnHover(HoverEvent e) => true; From 206f2ca208fafa01fbfbf616077027ba8360dc41 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 25 May 2022 22:20:33 +0900 Subject: [PATCH 181/395] Rename screen switcher control to be in line with naming expectations --- ...nSelectionTabControl.cs => EditorScreenSwitcherControl.cs} | 4 ++-- osu.Game/Screens/Edit/Editor.cs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) rename osu.Game/Screens/Edit/Components/Menus/{ScreenSelectionTabControl.cs => EditorScreenSwitcherControl.cs} (94%) diff --git a/osu.Game/Screens/Edit/Components/Menus/ScreenSelectionTabControl.cs b/osu.Game/Screens/Edit/Components/Menus/EditorScreenSwitcherControl.cs similarity index 94% rename from osu.Game/Screens/Edit/Components/Menus/ScreenSelectionTabControl.cs rename to osu.Game/Screens/Edit/Components/Menus/EditorScreenSwitcherControl.cs index b8bc5cdf36..28a8efcdd2 100644 --- a/osu.Game/Screens/Edit/Components/Menus/ScreenSelectionTabControl.cs +++ b/osu.Game/Screens/Edit/Components/Menus/EditorScreenSwitcherControl.cs @@ -13,9 +13,9 @@ using osuTK.Graphics; namespace osu.Game.Screens.Edit.Components.Menus { - public class ScreenSelectionTabControl : OsuTabControl + public class EditorScreenSwitcherControl : OsuTabControl { - public ScreenSelectionTabControl() + public EditorScreenSwitcherControl() { AutoSizeAxes = Axes.X; RelativeSizeAxes = Axes.Y; diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 7e7764397c..bdf204c1b6 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -278,7 +278,7 @@ namespace osu.Game.Screens.Edit } } }, - new ScreenSelectionTabControl + new EditorScreenSwitcherControl { Anchor = Anchor.BottomRight, Origin = Anchor.BottomRight, From 463b8130c8351e14d7c7295e54b0ea886739beee Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 25 May 2022 22:31:27 +0900 Subject: [PATCH 182/395] Update editor screen switcher to match new design language --- .../Menus/EditorScreenSwitcherControl.cs | 33 +++++++++++-------- 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/osu.Game/Screens/Edit/Components/Menus/EditorScreenSwitcherControl.cs b/osu.Game/Screens/Edit/Components/Menus/EditorScreenSwitcherControl.cs index 28a8efcdd2..8b868a4649 100644 --- a/osu.Game/Screens/Edit/Components/Menus/EditorScreenSwitcherControl.cs +++ b/osu.Game/Screens/Edit/Components/Menus/EditorScreenSwitcherControl.cs @@ -2,14 +2,13 @@ // 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.Shapes; using osu.Framework.Graphics.UserInterface; using osu.Game.Graphics; using osu.Game.Graphics.UserInterface; +using osu.Game.Overlays; using osuTK; -using osuTK.Graphics; namespace osu.Game.Screens.Edit.Components.Menus { @@ -22,22 +21,19 @@ namespace osu.Game.Screens.Edit.Components.Menus TabContainer.RelativeSizeAxes &= ~Axes.X; TabContainer.AutoSizeAxes = Axes.X; - TabContainer.Padding = new MarginPadding(); - - AddInternal(new Box - { - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - RelativeSizeAxes = Axes.X, - Height = 1, - Colour = Color4.White.Opacity(0.2f), - }); + TabContainer.Padding = new MarginPadding(10); } [BackgroundDependencyLoader] - private void load(OsuColour colours) + private void load(OverlayColourProvider colourProvider) { - AccentColour = colours.Yellow; + AccentColour = colourProvider.Light3; + + AddInternal(new Box + { + RelativeSizeAxes = Axes.Both, + Colour = colourProvider.Background2, + }); } protected override Dropdown CreateDropdown() => null; @@ -54,6 +50,15 @@ namespace osu.Game.Screens.Edit.Components.Menus Text.Margin = new MarginPadding(); Text.Anchor = Anchor.CentreLeft; Text.Origin = Anchor.CentreLeft; + + Text.Font = OsuFont.TorusAlternate; + + Bar.Expire(); + } + + [BackgroundDependencyLoader] + private void load(OverlayColourProvider colourProvider) + { } protected override void OnActivated() From 91834f03190bfe66303975a54c3c07fbfb415b6a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 25 May 2022 22:56:47 +0900 Subject: [PATCH 183/395] Fix missing colour dependency in menu bar test scene --- osu.Game.Tests/Visual/Editing/TestSceneEditorMenuBar.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorMenuBar.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorMenuBar.cs index 3cb44d9ae8..ad6fc55a32 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorMenuBar.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorMenuBar.cs @@ -2,10 +2,12 @@ // 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.Framework.Graphics.Containers; using osu.Framework.Graphics.UserInterface; using osu.Game.Graphics.UserInterface; +using osu.Game.Overlays; using osu.Game.Screens.Edit.Components.Menus; namespace osu.Game.Tests.Visual.Editing @@ -13,6 +15,9 @@ namespace osu.Game.Tests.Visual.Editing [TestFixture] public class TestSceneEditorMenuBar : OsuTestScene { + [Cached] + private readonly OverlayColourProvider overlayColour = new OverlayColourProvider(OverlayColourScheme.Aquamarine); + public TestSceneEditorMenuBar() { Add(new Container From c0da05dda0454690d11d3d5536d322c80cced4c6 Mon Sep 17 00:00:00 2001 From: Jai Sharma Date: Wed, 25 May 2022 15:15:26 +0100 Subject: [PATCH 184/395] Use an array of channels to exclude from the overlay --- osu.Game/Overlays/ChatOverlayV2.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/ChatOverlayV2.cs b/osu.Game/Overlays/ChatOverlayV2.cs index ad7a856dbc..9ca4d9cb6c 100644 --- a/osu.Game/Overlays/ChatOverlayV2.cs +++ b/osu.Game/Overlays/ChatOverlayV2.cs @@ -56,6 +56,13 @@ namespace osu.Game.Overlays private const float side_bar_width = 190; private const float chat_bar_height = 60; + private readonly ChannelType[] excluded_channel_types = new[] + { + ChannelType.Multiplayer, + ChannelType.Spectator, + ChannelType.Temporary, + }; + [Resolved] private OsuConfigManager config { get; set; } = null!; @@ -402,6 +409,6 @@ namespace osu.Game.Overlays } private IEnumerable filterToChatChannels(IEnumerable channels) - => channels.Cast().Where(c => c.Type == ChannelType.PM || c.Type == ChannelType.Public); + => channels.Cast().Where(c => !excluded_channel_types.Contains(c.Type)); } } From 997fc716e8609d84e157cf602e468699dff4d04b Mon Sep 17 00:00:00 2001 From: Jai Sharma Date: Wed, 25 May 2022 15:15:46 +0100 Subject: [PATCH 185/395] Split out the channel list labels from the channel list item flow --- .../Overlays/Chat/ChannelList/ChannelList.cs | 35 ++++++++++--------- 1 file changed, 19 insertions(+), 16 deletions(-) diff --git a/osu.Game/Overlays/Chat/ChannelList/ChannelList.cs b/osu.Game/Overlays/Chat/ChannelList/ChannelList.cs index 2798841925..21b2251aca 100644 --- a/osu.Game/Overlays/Chat/ChannelList/ChannelList.cs +++ b/osu.Game/Overlays/Chat/ChannelList/ChannelList.cs @@ -57,12 +57,14 @@ namespace osu.Game.Overlays.Chat.ChannelList AutoSizeAxes = Axes.Y, Children = new Drawable[] { - publicChannelFlow = new ChannelListItemFlow("CHANNELS"), + new ChannelListLabel("CHANNELS"), + publicChannelFlow = new ChannelListItemFlow(), selector = new ChannelListItem(ChannelListingChannel) { Margin = new MarginPadding { Bottom = 10 }, }, - privateChannelFlow = new ChannelListItemFlow("DIRECT MESSAGES"), + new ChannelListLabel("DIRECT MESSAGES"), + privateChannelFlow = new ChannelListItemFlow(), }, }, }, @@ -105,7 +107,7 @@ namespace osu.Game.Overlays.Chat.ChannelList return channelMap[channel]; } - public void ScrollChannelIntoView(Channel channel) => scroll.ScrollIntoView(this.GetItem(channel)); + public void ScrollChannelIntoView(Channel channel) => scroll.ScrollIntoView(GetItem(channel)); private ChannelListItemFlow getFlowForChannel(Channel channel) { @@ -118,28 +120,29 @@ namespace osu.Game.Overlays.Chat.ChannelList return privateChannelFlow; default: - throw new ArgumentOutOfRangeException(); + return publicChannelFlow; } } - private class ChannelListItemFlow : FillFlowContainer + private class ChannelListLabel : OsuSpriteText { - public IEnumerable Channels => Children.Where(c => c is ChannelListItem) - .Cast() - .Select(c => c.Channel); + public ChannelListLabel(string label) + { + Text = label; + Margin = new MarginPadding { Left = 18, Bottom = 5 }; + Font = OsuFont.Torus.With(size: 12, weight: FontWeight.SemiBold); + } + } - public ChannelListItemFlow(string label) + private class ChannelListItemFlow : FillFlowContainer + { + public IEnumerable Channels => Children.Select(c => c.Channel); + + public ChannelListItemFlow() { Direction = FillDirection.Vertical; RelativeSizeAxes = Axes.X; AutoSizeAxes = Axes.Y; - - Add(new OsuSpriteText - { - Text = label, - Margin = new MarginPadding { Left = 18, Bottom = 5 }, - Font = OsuFont.Torus.With(size: 12, weight: FontWeight.SemiBold), - }); } } } From 8791e3b9ef37071dae3f1e44a56060e889f3f7d1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 25 May 2022 23:10:58 +0900 Subject: [PATCH 186/395] Split `BottomBar` out of `Editor` --- osu.Game/Screens/Edit/BottomBar.cs | 83 +++++++++++++++++++++ osu.Game/Screens/Edit/Editor.cs | 113 ++++++----------------------- 2 files changed, 107 insertions(+), 89 deletions(-) create mode 100644 osu.Game/Screens/Edit/BottomBar.cs diff --git a/osu.Game/Screens/Edit/BottomBar.cs b/osu.Game/Screens/Edit/BottomBar.cs new file mode 100644 index 0000000000..e48d54e811 --- /dev/null +++ b/osu.Game/Screens/Edit/BottomBar.cs @@ -0,0 +1,83 @@ +// 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.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Overlays; +using osu.Game.Screens.Edit.Components; +using osu.Game.Screens.Edit.Components.Timelines.Summary; +using osuTK; + +namespace osu.Game.Screens.Edit +{ + internal class BottomBar : CompositeDrawable + { + public TestGameplayButton TestGameplayButton { get; private set; } + + [BackgroundDependencyLoader] + private void load(OverlayColourProvider colourProvider, Editor editor) + { + Anchor = Anchor.BottomLeft; + Origin = Anchor.BottomLeft; + + RelativeSizeAxes = Axes.X; + + Height = 60; + + InternalChildren = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = colourProvider.Background4, + }, + new Container + { + RelativeSizeAxes = Axes.Both, + Child = new GridContainer + { + RelativeSizeAxes = Axes.Both, + ColumnDimensions = new[] + { + new Dimension(GridSizeMode.Absolute, 220), + new Dimension(), + new Dimension(GridSizeMode.Absolute, 220), + new Dimension(GridSizeMode.Absolute, 120), + }, + Content = new[] + { + new Drawable[] + { + new Container + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding { Right = 10 }, + Child = new TimeInfoContainer { RelativeSizeAxes = Axes.Both }, + }, + new SummaryTimeline + { + RelativeSizeAxes = Axes.Both, + }, + new Container + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding { Left = 10 }, + Child = new PlaybackControl { RelativeSizeAxes = Axes.Both }, + }, + TestGameplayButton = new TestGameplayButton + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding { Left = 10 }, + Size = new Vector2(1), + Action = editor.TestGameplay, + } + }, + } + }, + } + }; + } + } +} diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 7e7764397c..4281b49e76 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -12,7 +12,6 @@ using osu.Framework.Audio.Track; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.UserInterface; using osu.Framework.Input; using osu.Framework.Input.Bindings; @@ -26,7 +25,6 @@ using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Configuration; using osu.Game.Database; -using osu.Game.Graphics; using osu.Game.Graphics.Cursor; using osu.Game.Graphics.UserInterface; using osu.Game.Input.Bindings; @@ -37,9 +35,7 @@ using osu.Game.Resources.Localisation.Web; using osu.Game.Rulesets; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Objects; -using osu.Game.Screens.Edit.Components; using osu.Game.Screens.Edit.Components.Menus; -using osu.Game.Screens.Edit.Components.Timelines.Summary; using osu.Game.Screens.Edit.Compose; using osu.Game.Screens.Edit.Design; using osu.Game.Screens.Edit.GameplayTest; @@ -48,7 +44,6 @@ using osu.Game.Screens.Edit.Timing; using osu.Game.Screens.Edit.Verify; using osu.Game.Screens.Play; using osu.Game.Users; -using osuTK; using osuTK.Graphics; using osuTK.Input; @@ -119,13 +114,13 @@ namespace osu.Game.Screens.Edit private IBeatmap playableBeatmap; private EditorBeatmap editorBeatmap; + private BottomBar bottomBar; + [CanBeNull] // Should be non-null once it can support custom rulesets. private EditorChangeHandler changeHandler; private DependencyContainer dependencies; - private TestGameplayButton testGameplayButton; - private bool isNewBeatmap; protected override UserActivity InitialActivity => new UserActivity.Editing(Beatmap.Value.BeatmapInfo); @@ -148,7 +143,7 @@ namespace osu.Game.Screens.Edit } [BackgroundDependencyLoader] - private void load(OsuColour colours, OsuConfigManager config) + private void load(OsuConfigManager config) { var loadableBeatmap = Beatmap.Value; @@ -226,7 +221,7 @@ namespace osu.Game.Screens.Edit AddInternal(new OsuContextMenuContainer { RelativeSizeAxes = Axes.Both, - Children = new[] + Children = new Drawable[] { new Container { @@ -287,67 +282,7 @@ namespace osu.Game.Screens.Edit }, }, }, - new Container - { - Name = "Bottom bar", - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - RelativeSizeAxes = Axes.X, - Height = 60, - Children = new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = colours.Gray2 - }, - new Container - { - RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding { Vertical = 5, Horizontal = 10 }, - Child = new GridContainer - { - RelativeSizeAxes = Axes.Both, - ColumnDimensions = new[] - { - new Dimension(GridSizeMode.Absolute, 220), - new Dimension(), - new Dimension(GridSizeMode.Absolute, 220), - new Dimension(GridSizeMode.Absolute, 120), - }, - Content = new[] - { - new Drawable[] - { - new Container - { - RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding { Right = 10 }, - Child = new TimeInfoContainer { RelativeSizeAxes = Axes.Both }, - }, - new SummaryTimeline - { - RelativeSizeAxes = Axes.Both, - }, - new Container - { - RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding { Left = 10 }, - Child = new PlaybackControl { RelativeSizeAxes = Axes.Both }, - }, - testGameplayButton = new TestGameplayButton - { - RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding { Left = 10 }, - Size = new Vector2(1), - Action = testGameplay - } - }, - } - }, - } - } - }, + bottomBar = new BottomBar(), } }); @@ -392,6 +327,24 @@ namespace osu.Game.Screens.Edit Clipboard.Content.Value = state.ClipboardContent; }); + public void TestGameplay() + { + if (HasUnsavedChanges) + { + dialogOverlay.Push(new SaveBeforeGameplayTestDialog(() => + { + Save(); + pushEditorPlayer(); + })); + } + else + { + pushEditorPlayer(); + } + + void pushEditorPlayer() => this.Push(new EditorPlayerLoader(this)); + } + /// /// Saves the currently edited beatmap. /// @@ -589,7 +542,7 @@ namespace osu.Game.Screens.Edit return true; case GlobalAction.EditorTestGameplay: - testGameplayButton.TriggerClick(); + bottomBar.TestGameplayButton.TriggerClick(); return true; default: @@ -935,24 +888,6 @@ namespace osu.Game.Screens.Edit loader?.CancelPendingDifficultySwitch(); } - private void testGameplay() - { - if (HasUnsavedChanges) - { - dialogOverlay.Push(new SaveBeforeGameplayTestDialog(() => - { - Save(); - pushEditorPlayer(); - })); - } - else - { - pushEditorPlayer(); - } - - void pushEditorPlayer() => this.Push(new EditorPlayerLoader(this)); - } - public double SnapTime(double time, double? referenceTime) => editorBeatmap.SnapTime(time, referenceTime); public double GetBeatLengthAtTime(double referenceTime) => editorBeatmap.GetBeatLengthAtTime(referenceTime); From 2c61a9d3d1b4cf1035d3a087c283ac086c2682a4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 25 May 2022 23:17:52 +0900 Subject: [PATCH 187/395] Update bottom bar components to new design language (mostly) --- osu.Game/Screens/Edit/BottomBar.cs | 19 +++---------------- .../Edit/Components/BottomBarContainer.cs | 16 ++++++++-------- .../Edit/Components/PlaybackControl.cs | 7 ++++--- .../Edit/Components/TimeInfoContainer.cs | 8 ++++++-- .../Timelines/Summary/SummaryTimeline.cs | 10 ++++++---- .../Timelines/Summary/TestGameplayButton.cs | 2 ++ 6 files changed, 29 insertions(+), 33 deletions(-) diff --git a/osu.Game/Screens/Edit/BottomBar.cs b/osu.Game/Screens/Edit/BottomBar.cs index e48d54e811..0f857940b3 100644 --- a/osu.Game/Screens/Edit/BottomBar.cs +++ b/osu.Game/Screens/Edit/BottomBar.cs @@ -50,22 +50,9 @@ namespace osu.Game.Screens.Edit { new Drawable[] { - new Container - { - RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding { Right = 10 }, - Child = new TimeInfoContainer { RelativeSizeAxes = Axes.Both }, - }, - new SummaryTimeline - { - RelativeSizeAxes = Axes.Both, - }, - new Container - { - RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding { Left = 10 }, - Child = new PlaybackControl { RelativeSizeAxes = Axes.Both }, - }, + new TimeInfoContainer { RelativeSizeAxes = Axes.Both }, + new SummaryTimeline { RelativeSizeAxes = Axes.Both }, + new PlaybackControl { RelativeSizeAxes = Axes.Both }, TestGameplayButton = new TestGameplayButton { RelativeSizeAxes = Axes.Both, diff --git a/osu.Game/Screens/Edit/Components/BottomBarContainer.cs b/osu.Game/Screens/Edit/Components/BottomBarContainer.cs index 08091fc3f7..3c63da3a4a 100644 --- a/osu.Game/Screens/Edit/Components/BottomBarContainer.cs +++ b/osu.Game/Screens/Edit/Components/BottomBarContainer.cs @@ -8,20 +8,19 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Game.Beatmaps; -using osu.Game.Graphics; +using osuTK.Graphics; namespace osu.Game.Screens.Edit.Components { public class BottomBarContainer : Container { - private const float corner_radius = 5; private const float contents_padding = 15; protected readonly IBindable Beatmap = new Bindable(); protected readonly IBindable Track = new Bindable(); - private readonly Drawable background; + protected readonly Drawable Background; private readonly Container content; protected override Container Content => content; @@ -29,11 +28,14 @@ namespace osu.Game.Screens.Edit.Components public BottomBarContainer() { Masking = true; - CornerRadius = corner_radius; InternalChildren = new[] { - background = new Box { RelativeSizeAxes = Axes.Both }, + Background = new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.Transparent, + }, content = new Container { RelativeSizeAxes = Axes.Both, @@ -43,12 +45,10 @@ namespace osu.Game.Screens.Edit.Components } [BackgroundDependencyLoader] - private void load(IBindable beatmap, OsuColour colours, EditorClock clock) + private void load(IBindable beatmap, EditorClock clock) { Beatmap.BindTo(beatmap); Track.BindTo(clock.Track); - - background.Colour = colours.Gray1; } } } diff --git a/osu.Game/Screens/Edit/Components/PlaybackControl.cs b/osu.Game/Screens/Edit/Components/PlaybackControl.cs index bdc6e238c8..d1a999c2d1 100644 --- a/osu.Game/Screens/Edit/Components/PlaybackControl.cs +++ b/osu.Game/Screens/Edit/Components/PlaybackControl.cs @@ -16,6 +16,7 @@ using osu.Framework.Input.Events; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; +using osu.Game.Overlays; using osuTK.Input; namespace osu.Game.Screens.Edit.Components @@ -155,10 +156,10 @@ namespace osu.Game.Screens.Edit.Components private Color4 normalColour; [BackgroundDependencyLoader] - private void load(OsuColour colours) + private void load(OverlayColourProvider colourProvider) { - text.Colour = normalColour = colours.YellowDarker; - textBold.Colour = hoveredColour = colours.Yellow; + text.Colour = normalColour = colourProvider.Light3; + textBold.Colour = hoveredColour = colourProvider.Content1; } protected override bool OnHover(HoverEvent e) diff --git a/osu.Game/Screens/Edit/Components/TimeInfoContainer.cs b/osu.Game/Screens/Edit/Components/TimeInfoContainer.cs index 0a8c339559..ac79a8ce7e 100644 --- a/osu.Game/Screens/Edit/Components/TimeInfoContainer.cs +++ b/osu.Game/Screens/Edit/Components/TimeInfoContainer.cs @@ -6,18 +6,22 @@ using osu.Game.Graphics.Sprites; using osu.Framework.Allocation; using osu.Game.Extensions; using osu.Game.Graphics; +using osu.Game.Overlays; namespace osu.Game.Screens.Edit.Components { public class TimeInfoContainer : BottomBarContainer { - private readonly OsuSpriteText trackTimer; + private OsuSpriteText trackTimer; [Resolved] private EditorClock editorClock { get; set; } - public TimeInfoContainer() + [BackgroundDependencyLoader] + private void load(OverlayColourProvider colourProvider) { + Background.Colour = colourProvider.Background5; + Children = new Drawable[] { trackTimer = new OsuSpriteText diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/SummaryTimeline.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/SummaryTimeline.cs index e90ae411de..1706c47c96 100644 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/SummaryTimeline.cs +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/SummaryTimeline.cs @@ -1,13 +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 osuTK; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; -using osu.Game.Graphics; +using osu.Game.Overlays; using osu.Game.Screens.Edit.Components.Timelines.Summary.Parts; +using osuTK; namespace osu.Game.Screens.Edit.Components.Timelines.Summary { @@ -17,8 +17,10 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary public class SummaryTimeline : BottomBarContainer { [BackgroundDependencyLoader] - private void load(OsuColour colours) + private void load(OverlayColourProvider colourProvider) { + Background.Colour = colourProvider.Background6; + Children = new Drawable[] { new MarkerPart { RelativeSizeAxes = Axes.Both }, @@ -41,7 +43,7 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary { Name = "centre line", RelativeSizeAxes = Axes.Both, - Colour = colours.Gray5, + Colour = colourProvider.Background2, Children = new Drawable[] { new Circle diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/TestGameplayButton.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/TestGameplayButton.cs index 0d7a4ad057..99cdc014aa 100644 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/TestGameplayButton.cs +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/TestGameplayButton.cs @@ -28,6 +28,8 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary BackgroundColour = colours.Orange1; SpriteText.Colour = colourProvider.Background6; + Content.CornerRadius = 0; + Text = "Test!"; } } From acd554d91820fa861612c9978d80bbbf2ad6c42c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 25 May 2022 23:30:53 +0900 Subject: [PATCH 188/395] Update time info and add bpm --- .../Editing/TestScenePlaybackControl.cs | 2 +- .../Edit/Components/TimeInfoContainer.cs | 23 +++++++++++++++---- osu.Game/Tests/Visual/EditorClockTestScene.cs | 9 +++++++- 3 files changed, 27 insertions(+), 7 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestScenePlaybackControl.cs b/osu.Game.Tests/Visual/Editing/TestScenePlaybackControl.cs index 6aa884a197..bf0a7876a9 100644 --- a/osu.Game.Tests/Visual/Editing/TestScenePlaybackControl.cs +++ b/osu.Game.Tests/Visual/Editing/TestScenePlaybackControl.cs @@ -12,7 +12,7 @@ using osuTK; namespace osu.Game.Tests.Visual.Editing { [TestFixture] - public class TestScenePlaybackControl : OsuTestScene + public class TestScenePlaybackControl : EditorClockTestScene { [BackgroundDependencyLoader] private void load() diff --git a/osu.Game/Screens/Edit/Components/TimeInfoContainer.cs b/osu.Game/Screens/Edit/Components/TimeInfoContainer.cs index ac79a8ce7e..6024716f4b 100644 --- a/osu.Game/Screens/Edit/Components/TimeInfoContainer.cs +++ b/osu.Game/Screens/Edit/Components/TimeInfoContainer.cs @@ -7,18 +7,23 @@ using osu.Framework.Allocation; using osu.Game.Extensions; using osu.Game.Graphics; using osu.Game.Overlays; +using osuTK; namespace osu.Game.Screens.Edit.Components { public class TimeInfoContainer : BottomBarContainer { private OsuSpriteText trackTimer; + private OsuSpriteText bpm; + + [Resolved] + private EditorBeatmap editorBeatmap { get; set; } [Resolved] private EditorClock editorClock { get; set; } [BackgroundDependencyLoader] - private void load(OverlayColourProvider colourProvider) + private void load(OsuColour colours, OverlayColourProvider colourProvider) { Background.Colour = colourProvider.Background5; @@ -28,10 +33,16 @@ namespace osu.Game.Screens.Edit.Components { Anchor = Anchor.CentreRight, Origin = Anchor.CentreRight, - // intentionally fudged centre to avoid movement of the number portion when - // going negative. - X = -35, - Font = OsuFont.GetFont(size: 25, fixedWidth: true), + Spacing = new Vector2(-2, 0), + Font = OsuFont.Torus.With(size: 36, fixedWidth: true, weight: FontWeight.Light), + Y = -10, + }, + bpm = new OsuSpriteText + { + Colour = colours.Orange1, + Anchor = Anchor.CentreRight, + Font = OsuFont.Torus.With(size: 18, weight: FontWeight.SemiBold), + Y = 5, } }; } @@ -40,6 +51,8 @@ namespace osu.Game.Screens.Edit.Components { base.Update(); trackTimer.Text = editorClock.CurrentTime.ToEditorFormattedString(); + bpm.Text = @$"{editorBeatmap.ControlPointInfo.TimingPointAt(editorClock.CurrentTime).BPM:0} BPM"; + bpm.X = 5 - trackTimer.DrawWidth; } } } diff --git a/osu.Game/Tests/Visual/EditorClockTestScene.cs b/osu.Game/Tests/Visual/EditorClockTestScene.cs index 542f06f86b..93dfe58076 100644 --- a/osu.Game/Tests/Visual/EditorClockTestScene.cs +++ b/osu.Game/Tests/Visual/EditorClockTestScene.cs @@ -6,6 +6,7 @@ using osu.Framework.Bindables; using osu.Framework.Input.Events; using osu.Game.Beatmaps; using osu.Game.Overlays; +using osu.Game.Rulesets.Edit; using osu.Game.Screens.Edit; namespace osu.Game.Tests.Visual @@ -19,6 +20,10 @@ namespace osu.Game.Tests.Visual [Cached] private readonly OverlayColourProvider overlayColour = new OverlayColourProvider(OverlayColourScheme.Aquamarine); + [Cached(typeof(EditorBeatmap))] + [Cached(typeof(IBeatSnapProvider))] + private readonly EditorBeatmap editorBeatmap; + protected readonly BindableBeatDivisor BeatDivisor = new BindableBeatDivisor(); protected new readonly EditorClock Clock; @@ -26,7 +31,9 @@ namespace osu.Game.Tests.Visual protected EditorClockTestScene() { - Clock = new EditorClock(new Beatmap(), BeatDivisor) { IsCoupled = false }; + editorBeatmap = new EditorBeatmap(new Beatmap()); + + Clock = new EditorClock(editorBeatmap, BeatDivisor) { IsCoupled = false }; } protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) From f94afa5f8cf0b384322ba12a1ca527b36bae1580 Mon Sep 17 00:00:00 2001 From: Jai Sharma Date: Wed, 25 May 2022 18:28:18 +0100 Subject: [PATCH 189/395] Code quality --- osu.Game/Overlays/ChatOverlayV2.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/ChatOverlayV2.cs b/osu.Game/Overlays/ChatOverlayV2.cs index 9ca4d9cb6c..68199c9d25 100644 --- a/osu.Game/Overlays/ChatOverlayV2.cs +++ b/osu.Game/Overlays/ChatOverlayV2.cs @@ -56,7 +56,7 @@ namespace osu.Game.Overlays private const float side_bar_width = 190; private const float chat_bar_height = 60; - private readonly ChannelType[] excluded_channel_types = new[] + private readonly ChannelType[] excludedChannelTypes = { ChannelType.Multiplayer, ChannelType.Spectator, @@ -409,6 +409,6 @@ namespace osu.Game.Overlays } private IEnumerable filterToChatChannels(IEnumerable channels) - => channels.Cast().Where(c => !excluded_channel_types.Contains(c.Type)); + => channels.Cast().Where(c => !excludedChannelTypes.Contains(c.Type)); } } From 4f8b6b52ba1757396e1ef407d47bed974d5568f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 25 May 2022 21:49:31 +0200 Subject: [PATCH 190/395] Add failing test coverage for select/deselect all enable behaviour --- .../Visual/Multiplayer/TestSceneFreeModSelectOverlay.cs | 4 ++++ .../Visual/UserInterface/TestSceneModSelectOverlay.cs | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneFreeModSelectOverlay.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneFreeModSelectOverlay.cs index c37bff2066..44ae467c80 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneFreeModSelectOverlay.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneFreeModSelectOverlay.cs @@ -73,12 +73,15 @@ namespace osu.Game.Tests.Visual.Multiplayer { createFreeModSelect(); + AddAssert("select all button enabled", () => this.ChildrenOfType().ElementAt(1).Enabled.Value); + AddStep("click select all button", () => { InputManager.MoveMouseTo(this.ChildrenOfType().ElementAt(1)); InputManager.Click(MouseButton.Left); }); AddUntilStep("all mods selected", assertAllAvailableModsSelected); + AddAssert("select all button disabled", () => !this.ChildrenOfType().ElementAt(1).Enabled.Value); AddStep("click deselect all button", () => { @@ -86,6 +89,7 @@ namespace osu.Game.Tests.Visual.Multiplayer InputManager.Click(MouseButton.Left); }); AddUntilStep("all mods deselected", () => !freeModSelectOverlay.SelectedMods.Value.Any()); + AddAssert("select all button enabled", () => this.ChildrenOfType().ElementAt(1).Enabled.Value); } private void createFreeModSelect() diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs index 75e30f76c3..0c3acded88 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs @@ -435,8 +435,11 @@ namespace osu.Game.Tests.Visual.UserInterface createScreen(); changeRuleset(0); + AddAssert("deselect all button disabled", () => !this.ChildrenOfType().Last().Enabled.Value); + AddStep("select DT + HD", () => SelectedMods.Value = new Mod[] { new OsuModDoubleTime(), new OsuModHidden() }); AddAssert("DT + HD selected", () => modSelectOverlay.ChildrenOfType().Count(panel => panel.Active.Value) == 2); + AddAssert("deselect all button enabled", () => this.ChildrenOfType().Last().Enabled.Value); AddStep("click deselect all button", () => { @@ -444,6 +447,7 @@ namespace osu.Game.Tests.Visual.UserInterface InputManager.Click(MouseButton.Left); }); AddUntilStep("all mods deselected", () => !SelectedMods.Value.Any()); + AddAssert("deselect all button disabled", () => !this.ChildrenOfType().Last().Enabled.Value); } [Test] From a4bd399b0cbd6a1c14a20fbb8fc2b431f2e97ea7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 25 May 2022 22:09:31 +0200 Subject: [PATCH 191/395] Split off "deselect all mods" button to separate class --- .../Overlays/Mods/DeselectAllModsButton.cs | 38 +++++++++++++++++++ osu.Game/Overlays/Mods/ModSelectOverlay.cs | 15 ++------ 2 files changed, 41 insertions(+), 12 deletions(-) create mode 100644 osu.Game/Overlays/Mods/DeselectAllModsButton.cs diff --git a/osu.Game/Overlays/Mods/DeselectAllModsButton.cs b/osu.Game/Overlays/Mods/DeselectAllModsButton.cs new file mode 100644 index 0000000000..39009c8656 --- /dev/null +++ b/osu.Game/Overlays/Mods/DeselectAllModsButton.cs @@ -0,0 +1,38 @@ +// 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.Linq; +using osu.Framework.Bindables; +using osu.Framework.Input.Bindings; +using osu.Framework.Input.Events; +using osu.Game.Graphics.UserInterface; +using osu.Game.Input.Bindings; +using osu.Game.Localisation; +using osu.Game.Rulesets.Mods; + +namespace osu.Game.Overlays.Mods +{ + public class DeselectAllModsButton : ShearedButton, IKeyBindingHandler + { + public DeselectAllModsButton(ModSelectOverlay modSelectOverlay) + : base(ModSelectOverlay.BUTTON_WIDTH) + { + Text = CommonStrings.DeselectAll; + Action = modSelectOverlay.DeselectAll; + } + + public bool OnPressed(KeyBindingPressEvent e) + { + if (e.Repeat || e.Action != GlobalAction.DeselectAllMods) + return false; + + TriggerClick(); + return true; + } + + public void OnReleased(KeyBindingReleaseEvent e) + { + } + } +} diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index f1a998bd3c..ea7f969450 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -29,7 +29,7 @@ namespace osu.Game.Overlays.Mods { public abstract class ModSelectOverlay : ShearedOverlayContainer, ISamplePlaybackDisabler { - protected const int BUTTON_WIDTH = 200; + public const int BUTTON_WIDTH = 200; [Cached] public Bindable> SelectedMods { get; private set; } = new Bindable>(Array.Empty()); @@ -76,11 +76,7 @@ namespace osu.Game.Overlays.Mods }; } - yield return deselectAllButton = new ShearedButton(BUTTON_WIDTH) - { - Text = CommonStrings.DeselectAll, - Action = DeselectAll - }; + yield return new DeselectAllModsButton(this); } private readonly Bindable>> availableMods = new Bindable>>(); @@ -98,7 +94,6 @@ namespace osu.Game.Overlays.Mods private DifficultyMultiplierDisplay? multiplierDisplay; private ShearedToggleButton? customisationButton; - private ShearedButton? deselectAllButton; protected ModSelectOverlay(OverlayColourScheme colourScheme = OverlayColourScheme.Green) : base(colourScheme) @@ -256,7 +251,7 @@ namespace osu.Game.Overlays.Mods /// /// Deselect all visible mods in all columns. /// - protected void DeselectAll() + public void DeselectAll() { foreach (var column in columnFlow.Columns) column.DeselectAll(); @@ -514,10 +509,6 @@ namespace osu.Game.Overlays.Mods hideOverlay(true); return true; } - - case GlobalAction.DeselectAllMods: - deselectAllButton?.TriggerClick(); - return true; } return base.OnPressed(e); From a3f29625587fd0cb52c8d4042dd1b48f82d62db2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 25 May 2022 22:12:09 +0200 Subject: [PATCH 192/395] Disable "deselect all mods" button if none are selected --- osu.Game/Overlays/Mods/DeselectAllModsButton.cs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/osu.Game/Overlays/Mods/DeselectAllModsButton.cs b/osu.Game/Overlays/Mods/DeselectAllModsButton.cs index 39009c8656..8288d34c95 100644 --- a/osu.Game/Overlays/Mods/DeselectAllModsButton.cs +++ b/osu.Game/Overlays/Mods/DeselectAllModsButton.cs @@ -15,11 +15,27 @@ namespace osu.Game.Overlays.Mods { public class DeselectAllModsButton : ShearedButton, IKeyBindingHandler { + private readonly Bindable> selectedMods = new Bindable>(); + public DeselectAllModsButton(ModSelectOverlay modSelectOverlay) : base(ModSelectOverlay.BUTTON_WIDTH) { Text = CommonStrings.DeselectAll; Action = modSelectOverlay.DeselectAll; + + selectedMods.BindTo(modSelectOverlay.SelectedMods); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + selectedMods.BindValueChanged(_ => updateEnabledState(), true); + } + + private void updateEnabledState() + { + Enabled.Value = selectedMods.Value.Any(); } public bool OnPressed(KeyBindingPressEvent e) From f0303d76e8e8dfb4b174bec2bd15e5a9aae23f08 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 25 May 2022 22:18:30 +0200 Subject: [PATCH 193/395] Split off "select all mods" button to separate class --- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 2 +- osu.Game/Overlays/Mods/SelectAllModsButton.cs | 35 +++++++++++++++++++ .../OnlinePlay/FreeModSelectOverlay.cs | 31 ++-------------- 3 files changed, 38 insertions(+), 30 deletions(-) create mode 100644 osu.Game/Overlays/Mods/SelectAllModsButton.cs diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index ea7f969450..d8355a4dea 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -242,7 +242,7 @@ namespace osu.Game.Overlays.Mods /// /// Select all visible mods in all columns. /// - protected void SelectAll() + public void SelectAll() { foreach (var column in columnFlow.Columns) column.SelectAll(); diff --git a/osu.Game/Overlays/Mods/SelectAllModsButton.cs b/osu.Game/Overlays/Mods/SelectAllModsButton.cs new file mode 100644 index 0000000000..b9d6bf9633 --- /dev/null +++ b/osu.Game/Overlays/Mods/SelectAllModsButton.cs @@ -0,0 +1,35 @@ +// 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.Input; +using osu.Framework.Input.Bindings; +using osu.Framework.Input.Events; +using osu.Game.Graphics.UserInterface; +using osu.Game.Localisation; +using osu.Game.Screens.OnlinePlay; + +namespace osu.Game.Overlays.Mods +{ + public class SelectAllModsButton : ShearedButton, IKeyBindingHandler + { + public SelectAllModsButton(FreeModSelectOverlay modSelectOverlay) + : base(ModSelectOverlay.BUTTON_WIDTH) + { + Text = CommonStrings.SelectAll; + Action = modSelectOverlay.SelectAll; + } + + public bool OnPressed(KeyBindingPressEvent e) + { + if (e.Repeat || e.Action != PlatformAction.SelectAll) + return false; + + TriggerClick(); + return true; + } + + public void OnReleased(KeyBindingReleaseEvent e) + { + } + } +} diff --git a/osu.Game/Screens/OnlinePlay/FreeModSelectOverlay.cs b/osu.Game/Screens/OnlinePlay/FreeModSelectOverlay.cs index f494f9799a..7c9184cc0f 100644 --- a/osu.Game/Screens/OnlinePlay/FreeModSelectOverlay.cs +++ b/osu.Game/Screens/OnlinePlay/FreeModSelectOverlay.cs @@ -6,18 +6,14 @@ using osu.Game.Overlays; using System.Collections.Generic; using System.Linq; using osu.Framework.Graphics; -using osu.Framework.Input; -using osu.Framework.Input.Bindings; -using osu.Framework.Input.Events; using osu.Game.Graphics.UserInterface; using osu.Game.Overlays.Mods; using osu.Game.Rulesets.Mods; using osuTK.Input; -using osu.Game.Localisation; namespace osu.Game.Screens.OnlinePlay { - public class FreeModSelectOverlay : ModSelectOverlay, IKeyBindingHandler + public class FreeModSelectOverlay : ModSelectOverlay { protected override bool ShowTotalMultiplier => false; @@ -29,8 +25,6 @@ namespace osu.Game.Screens.OnlinePlay set => base.IsValidMod = m => m.UserPlayable && value.Invoke(m); } - private ShearedButton selectAllButton; - public FreeModSelectOverlay() : base(OverlayColourScheme.Plum) { @@ -40,31 +34,10 @@ namespace osu.Game.Screens.OnlinePlay protected override ModColumn CreateModColumn(ModType modType, Key[] toggleKeys = null) => new ModColumn(modType, true, toggleKeys); protected override IEnumerable CreateFooterButtons() => base.CreateFooterButtons().Prepend( - selectAllButton = new ShearedButton(BUTTON_WIDTH) + new SelectAllModsButton(this) { Anchor = Anchor.BottomLeft, Origin = Anchor.BottomLeft, - Text = CommonStrings.SelectAll, - Action = SelectAll }); - - public bool OnPressed(KeyBindingPressEvent e) - { - if (e.Repeat) - return false; - - switch (e.Action) - { - case PlatformAction.SelectAll: - selectAllButton.TriggerClick(); - return true; - } - - return false; - } - - public void OnReleased(KeyBindingReleaseEvent e) - { - } } } From 071e158a297139bff0812b567cf6f6064e98839e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 25 May 2022 22:20:10 +0200 Subject: [PATCH 194/395] Expose available mod state outwardly as a bindable --- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 38 ++++++++++++++-------- 1 file changed, 24 insertions(+), 14 deletions(-) diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index d8355a4dea..4bad34d94f 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -34,6 +34,15 @@ namespace osu.Game.Overlays.Mods [Cached] public Bindable> SelectedMods { get; private set; } = new Bindable>(Array.Empty()); + /// + /// Contains a dictionary with the current of all mods applicable for the current ruleset. + /// + /// + /// Contrary to and , the instances + /// inside the objects are owned solely by this instance. + /// + public Bindable>> AvailableMods { get; } = new Bindable>>(new Dictionary>()); + private Func isValidMod = m => true; /// @@ -79,9 +88,9 @@ namespace osu.Game.Overlays.Mods yield return new DeselectAllModsButton(this); } - private readonly Bindable>> availableMods = new Bindable>>(); - private readonly Dictionary> localAvailableMods = new Dictionary>(); - private IEnumerable allLocalAvailableMods => localAvailableMods.SelectMany(pair => pair.Value); + private readonly Bindable>> globalAvailableMods = new Bindable>>(); + + private IEnumerable allAvailableMods => AvailableMods.Value.SelectMany(pair => pair.Value); private readonly BindableBool customisationVisible = new BindableBool(); @@ -204,13 +213,13 @@ namespace osu.Game.Overlays.Mods }) }; - availableMods.BindTo(game.AvailableMods); + globalAvailableMods.BindTo(game.AvailableMods); } protected override void LoadComplete() { // this is called before base call so that the mod state is populated early, and the transition in `PopIn()` can play out properly. - availableMods.BindValueChanged(_ => createLocalMods(), true); + globalAvailableMods.BindValueChanged(_ => createLocalMods(), true); base.LoadComplete(); @@ -275,9 +284,9 @@ namespace osu.Game.Overlays.Mods private void createLocalMods() { - localAvailableMods.Clear(); + var newLocalAvailableMods = new Dictionary>(); - foreach (var (modType, mods) in availableMods.Value) + foreach (var (modType, mods) in globalAvailableMods.Value) { var modStates = mods.SelectMany(ModUtils.FlattenMod) .Select(mod => new ModState(mod.DeepClone())) @@ -286,18 +295,19 @@ namespace osu.Game.Overlays.Mods foreach (var modState in modStates) modState.Active.BindValueChanged(_ => updateFromInternalSelection()); - localAvailableMods[modType] = modStates; + newLocalAvailableMods[modType] = modStates; } + AvailableMods.Value = newLocalAvailableMods; filterMods(); foreach (var column in columnFlow.Columns) - column.AvailableMods = localAvailableMods.GetValueOrDefault(column.ModType, Array.Empty()); + column.AvailableMods = AvailableMods.Value.GetValueOrDefault(column.ModType, Array.Empty()); } private void filterMods() { - foreach (var modState in allLocalAvailableMods) + foreach (var modState in allAvailableMods) modState.Filtered.Value = !modState.Mod.HasImplementation || !IsValidMod.Invoke(modState.Mod); } @@ -378,7 +388,7 @@ namespace osu.Game.Overlays.Mods var newSelection = new List(); - foreach (var modState in allLocalAvailableMods) + foreach (var modState in allAvailableMods) { var matchingSelectedMod = SelectedMods.Value.SingleOrDefault(selected => selected.GetType() == modState.Mod.GetType()); @@ -405,9 +415,9 @@ namespace osu.Game.Overlays.Mods if (externalSelectionUpdateInProgress) return; - var candidateSelection = allLocalAvailableMods.Where(modState => modState.Active.Value) - .Select(modState => modState.Mod) - .ToArray(); + var candidateSelection = allAvailableMods.Where(modState => modState.Active.Value) + .Select(modState => modState.Mod) + .ToArray(); SelectedMods.Value = ComputeNewModsFromSelection(SelectedMods.Value, candidateSelection); } From a1b8fa09921666ee65531cea264aeade2aa5718b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 25 May 2022 22:24:26 +0200 Subject: [PATCH 195/395] Disable "select all mods" button if all are selected --- osu.Game/Overlays/Mods/SelectAllModsButton.cs | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/osu.Game/Overlays/Mods/SelectAllModsButton.cs b/osu.Game/Overlays/Mods/SelectAllModsButton.cs index b9d6bf9633..f7078b2fa5 100644 --- a/osu.Game/Overlays/Mods/SelectAllModsButton.cs +++ b/osu.Game/Overlays/Mods/SelectAllModsButton.cs @@ -1,22 +1,48 @@ // 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.Linq; +using osu.Framework.Bindables; using osu.Framework.Input; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; using osu.Game.Graphics.UserInterface; using osu.Game.Localisation; +using osu.Game.Rulesets.Mods; using osu.Game.Screens.OnlinePlay; namespace osu.Game.Overlays.Mods { public class SelectAllModsButton : ShearedButton, IKeyBindingHandler { + private readonly Bindable> selectedMods = new Bindable>(); + private readonly Bindable>> availableMods = new Bindable>>(); + public SelectAllModsButton(FreeModSelectOverlay modSelectOverlay) : base(ModSelectOverlay.BUTTON_WIDTH) { Text = CommonStrings.SelectAll; Action = modSelectOverlay.SelectAll; + + selectedMods.BindTo(modSelectOverlay.SelectedMods); + availableMods.BindTo(modSelectOverlay.AvailableMods); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + selectedMods.BindValueChanged(_ => Scheduler.AddOnce(updateEnabledState)); + availableMods.BindValueChanged(_ => Scheduler.AddOnce(updateEnabledState)); + updateEnabledState(); + } + + private void updateEnabledState() + { + Enabled.Value = availableMods.Value + .SelectMany(pair => pair.Value) + .Any(modState => !modState.Active.Value && !modState.Filtered.Value); } public bool OnPressed(KeyBindingPressEvent e) From af1d4d3aee582950473057e8733855c71ddbff62 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 25 May 2022 22:27:54 +0200 Subject: [PATCH 196/395] Use more proper button subtypes in tests --- .../Multiplayer/TestSceneFreeModSelectOverlay.cs | 11 +++++------ .../Visual/UserInterface/TestSceneModSelectOverlay.cs | 8 ++++---- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneFreeModSelectOverlay.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneFreeModSelectOverlay.cs index 44ae467c80..a8471edbf8 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneFreeModSelectOverlay.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneFreeModSelectOverlay.cs @@ -9,7 +9,6 @@ using osu.Framework.Bindables; using osu.Framework.Graphics.Containers; using osu.Framework.Input; using osu.Framework.Testing; -using osu.Game.Graphics.UserInterface; using osu.Game.Overlays.Mods; using osu.Game.Rulesets.Osu.Mods; using osu.Game.Rulesets.Mods; @@ -73,23 +72,23 @@ namespace osu.Game.Tests.Visual.Multiplayer { createFreeModSelect(); - AddAssert("select all button enabled", () => this.ChildrenOfType().ElementAt(1).Enabled.Value); + AddAssert("select all button enabled", () => this.ChildrenOfType().Single().Enabled.Value); AddStep("click select all button", () => { - InputManager.MoveMouseTo(this.ChildrenOfType().ElementAt(1)); + InputManager.MoveMouseTo(this.ChildrenOfType().Single()); InputManager.Click(MouseButton.Left); }); AddUntilStep("all mods selected", assertAllAvailableModsSelected); - AddAssert("select all button disabled", () => !this.ChildrenOfType().ElementAt(1).Enabled.Value); + AddAssert("select all button disabled", () => !this.ChildrenOfType().Single().Enabled.Value); AddStep("click deselect all button", () => { - InputManager.MoveMouseTo(this.ChildrenOfType().Last()); + InputManager.MoveMouseTo(this.ChildrenOfType().Single()); InputManager.Click(MouseButton.Left); }); AddUntilStep("all mods deselected", () => !freeModSelectOverlay.SelectedMods.Value.Any()); - AddAssert("select all button enabled", () => this.ChildrenOfType().ElementAt(1).Enabled.Value); + AddAssert("select all button enabled", () => this.ChildrenOfType().Single().Enabled.Value); } private void createFreeModSelect() diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs index 0c3acded88..9bb02c3e75 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs @@ -435,19 +435,19 @@ namespace osu.Game.Tests.Visual.UserInterface createScreen(); changeRuleset(0); - AddAssert("deselect all button disabled", () => !this.ChildrenOfType().Last().Enabled.Value); + AddAssert("deselect all button disabled", () => !this.ChildrenOfType().Single().Enabled.Value); AddStep("select DT + HD", () => SelectedMods.Value = new Mod[] { new OsuModDoubleTime(), new OsuModHidden() }); AddAssert("DT + HD selected", () => modSelectOverlay.ChildrenOfType().Count(panel => panel.Active.Value) == 2); - AddAssert("deselect all button enabled", () => this.ChildrenOfType().Last().Enabled.Value); + AddAssert("deselect all button enabled", () => this.ChildrenOfType().Single().Enabled.Value); AddStep("click deselect all button", () => { - InputManager.MoveMouseTo(this.ChildrenOfType().Last()); + InputManager.MoveMouseTo(this.ChildrenOfType().Single()); InputManager.Click(MouseButton.Left); }); AddUntilStep("all mods deselected", () => !SelectedMods.Value.Any()); - AddAssert("deselect all button disabled", () => !this.ChildrenOfType().Last().Enabled.Value); + AddAssert("deselect all button disabled", () => !this.ChildrenOfType().Single().Enabled.Value); } [Test] From 63dd9bd49372f0f051d86124fcd6b0a71a95ea1e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 26 May 2022 18:02:50 +0900 Subject: [PATCH 197/395] Expose `Reconnect` logic in `HubClientConnector` --- osu.Game/Online/HubClientConnector.cs | 34 ++++++++++++++++---------- osu.Game/Online/IHubClientConnector.cs | 5 ++++ 2 files changed, 26 insertions(+), 13 deletions(-) diff --git a/osu.Game/Online/HubClientConnector.cs b/osu.Game/Online/HubClientConnector.cs index ca9bf00b23..5c11eed381 100644 --- a/osu.Game/Online/HubClientConnector.cs +++ b/osu.Game/Online/HubClientConnector.cs @@ -64,20 +64,28 @@ namespace osu.Game.Online this.preferMessagePack = preferMessagePack; apiState.BindTo(api.State); - apiState.BindValueChanged(state => - { - switch (state.NewValue) - { - case APIState.Failing: - case APIState.Offline: - Task.Run(() => disconnect(true)); - break; + apiState.BindValueChanged(state => connectIfPossible(), true); + } - case APIState.Online: - Task.Run(connect); - break; - } - }, true); + public void Reconnect() + { + Logger.Log($"{clientName} reconnecting...", LoggingTarget.Network); + Task.Run(connectIfPossible); + } + + private void connectIfPossible() + { + switch (apiState.Value) + { + case APIState.Failing: + case APIState.Offline: + Task.Run(() => disconnect(true)); + break; + + case APIState.Online: + Task.Run(connect); + break; + } } private async Task connect() diff --git a/osu.Game/Online/IHubClientConnector.cs b/osu.Game/Online/IHubClientConnector.cs index d2ceb1f030..b168e4669f 100644 --- a/osu.Game/Online/IHubClientConnector.cs +++ b/osu.Game/Online/IHubClientConnector.cs @@ -30,5 +30,10 @@ namespace osu.Game.Online /// Invoked whenever a new hub connection is built, to configure it before it's started. /// public Action? ConfigureConnection { get; set; } + + /// + /// Reconnect if already connected. + /// + void Reconnect(); } } From abb69a49b05591586adeb8034203cef2a07eee81 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 26 May 2022 18:03:41 +0900 Subject: [PATCH 198/395] Handle server shutdown messages in room creation and spectator initialisation --- osu.Game/Online/HubClientConnector.cs | 2 ++ .../Multiplayer/MultiplayerClientExtensions.cs | 18 ++++++++++++------ .../Multiplayer/OnlineMultiplayerClient.cs | 17 ++++++++++++++--- .../Online/Spectator/OnlineSpectatorClient.cs | 17 ++++++++++++++--- 4 files changed, 42 insertions(+), 12 deletions(-) diff --git a/osu.Game/Online/HubClientConnector.cs b/osu.Game/Online/HubClientConnector.cs index 5c11eed381..261724e315 100644 --- a/osu.Game/Online/HubClientConnector.cs +++ b/osu.Game/Online/HubClientConnector.cs @@ -20,6 +20,8 @@ namespace osu.Game.Online { public class HubClientConnector : IHubClientConnector { + public const string SERVER_SHUTDOWN_MESSAGE = "Server is shutting down."; + /// /// Invoked whenever a new hub connection is built, to configure it before it's started. /// diff --git a/osu.Game/Online/Multiplayer/MultiplayerClientExtensions.cs b/osu.Game/Online/Multiplayer/MultiplayerClientExtensions.cs index 4729765084..cda313bd0a 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerClientExtensions.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerClientExtensions.cs @@ -25,12 +25,7 @@ namespace osu.Game.Online.Multiplayer Debug.Assert(exception != null); - string message = exception is HubException - // HubExceptions arrive with additional message context added, but we want to display the human readable message: - // "An unexpected error occurred invoking 'AddPlaylistItem' on the server.InvalidStateException: Can't enqueue more than 3 items at once." - // We generally use the message field for a user-parseable error (eventually to be replaced), so drop the first part for now. - ? exception.Message.Substring(exception.Message.IndexOf(':') + 1).Trim() - : exception.Message; + string message = exception.GetHubExceptionMessage() ?? exception.Message; Logger.Log(message, level: LogLevel.Important); onError?.Invoke(exception); @@ -40,5 +35,16 @@ namespace osu.Game.Online.Multiplayer onSuccess?.Invoke(); } }); + + public static string? GetHubExceptionMessage(this Exception exception) + { + if (exception is HubException hubException) + // HubExceptions arrive with additional message context added, but we want to display the human readable message: + // "An unexpected error occurred invoking 'AddPlaylistItem' on the server.InvalidStateException: Can't enqueue more than 3 items at once." + // We generally use the message field for a user-parseable error (eventually to be replaced), so drop the first part for now. + return hubException.Message.Substring(exception.Message.IndexOf(':') + 1).Trim(); + + return null; + } } } diff --git a/osu.Game/Online/Multiplayer/OnlineMultiplayerClient.cs b/osu.Game/Online/Multiplayer/OnlineMultiplayerClient.cs index 4dc23d8b85..a3423d4189 100644 --- a/osu.Game/Online/Multiplayer/OnlineMultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/OnlineMultiplayerClient.cs @@ -3,10 +3,12 @@ #nullable enable +using System; using System.Collections.Generic; using System.Diagnostics; using System.Threading; using System.Threading.Tasks; +using Microsoft.AspNetCore.SignalR; using Microsoft.AspNetCore.SignalR.Client; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -71,14 +73,23 @@ namespace osu.Game.Online.Multiplayer } } - protected override Task JoinRoom(long roomId, string? password = null) + protected override async Task JoinRoom(long roomId, string? password = null) { if (!IsConnected.Value) - return Task.FromCanceled(new CancellationToken(true)); + throw new OperationCanceledException(); Debug.Assert(connection != null); - return connection.InvokeAsync(nameof(IMultiplayerServer.JoinRoomWithPassword), roomId, password ?? string.Empty); + try + { + return await connection.InvokeAsync(nameof(IMultiplayerServer.JoinRoomWithPassword), roomId, password ?? string.Empty); + } + catch (HubException exception) + { + if (exception.GetHubExceptionMessage() == HubClientConnector.SERVER_SHUTDOWN_MESSAGE) + connector?.Reconnect(); + throw; + } } protected override Task LeaveRoomInternal() diff --git a/osu.Game/Online/Spectator/OnlineSpectatorClient.cs b/osu.Game/Online/Spectator/OnlineSpectatorClient.cs index 4d6ca0b311..0f77f723db 100644 --- a/osu.Game/Online/Spectator/OnlineSpectatorClient.cs +++ b/osu.Game/Online/Spectator/OnlineSpectatorClient.cs @@ -5,10 +5,12 @@ using System.Diagnostics; using System.Threading.Tasks; +using Microsoft.AspNetCore.SignalR; using Microsoft.AspNetCore.SignalR.Client; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Game.Online.API; +using osu.Game.Online.Multiplayer; namespace osu.Game.Online.Spectator { @@ -47,14 +49,23 @@ namespace osu.Game.Online.Spectator } } - protected override Task BeginPlayingInternal(SpectatorState state) + protected override async Task BeginPlayingInternal(SpectatorState state) { if (!IsConnected.Value) - return Task.CompletedTask; + return; Debug.Assert(connection != null); - return connection.SendAsync(nameof(ISpectatorServer.BeginPlaySession), state); + try + { + await connection.SendAsync(nameof(ISpectatorServer.BeginPlaySession), state); + } + catch (HubException exception) + { + if (exception.GetHubExceptionMessage() == HubClientConnector.SERVER_SHUTDOWN_MESSAGE) + connector?.Reconnect(); + throw; + } } protected override Task SendFramesInternal(FrameDataBundle bundle) From 7f4ea5d522de3dc6c53d9e6cb892720b580c47c5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 26 May 2022 18:12:28 +0900 Subject: [PATCH 199/395] Use left alignment and remove hacky text positioning code --- osu.Game/Screens/Edit/BottomBar.cs | 2 +- osu.Game/Screens/Edit/Components/TimeInfoContainer.cs | 9 ++++----- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/osu.Game/Screens/Edit/BottomBar.cs b/osu.Game/Screens/Edit/BottomBar.cs index 0f857940b3..5994184c11 100644 --- a/osu.Game/Screens/Edit/BottomBar.cs +++ b/osu.Game/Screens/Edit/BottomBar.cs @@ -41,7 +41,7 @@ namespace osu.Game.Screens.Edit RelativeSizeAxes = Axes.Both, ColumnDimensions = new[] { - new Dimension(GridSizeMode.Absolute, 220), + new Dimension(GridSizeMode.Absolute, 170), new Dimension(), new Dimension(GridSizeMode.Absolute, 220), new Dimension(GridSizeMode.Absolute, 120), diff --git a/osu.Game/Screens/Edit/Components/TimeInfoContainer.cs b/osu.Game/Screens/Edit/Components/TimeInfoContainer.cs index 6024716f4b..bd5377e578 100644 --- a/osu.Game/Screens/Edit/Components/TimeInfoContainer.cs +++ b/osu.Game/Screens/Edit/Components/TimeInfoContainer.cs @@ -31,8 +31,8 @@ namespace osu.Game.Screens.Edit.Components { trackTimer = new OsuSpriteText { - Anchor = Anchor.CentreRight, - Origin = Anchor.CentreRight, + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, Spacing = new Vector2(-2, 0), Font = OsuFont.Torus.With(size: 36, fixedWidth: true, weight: FontWeight.Light), Y = -10, @@ -40,9 +40,9 @@ namespace osu.Game.Screens.Edit.Components bpm = new OsuSpriteText { Colour = colours.Orange1, - Anchor = Anchor.CentreRight, + Anchor = Anchor.CentreLeft, Font = OsuFont.Torus.With(size: 18, weight: FontWeight.SemiBold), - Y = 5, + Position = new Vector2(2, 5), } }; } @@ -52,7 +52,6 @@ namespace osu.Game.Screens.Edit.Components base.Update(); trackTimer.Text = editorClock.CurrentTime.ToEditorFormattedString(); bpm.Text = @$"{editorBeatmap.ControlPointInfo.TimingPointAt(editorClock.CurrentTime).BPM:0} BPM"; - bpm.X = 5 - trackTimer.DrawWidth; } } } From 93c94b8ea0f21cb19cfb22a1376b6a0ec61f4911 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 26 May 2022 18:30:37 +0900 Subject: [PATCH 200/395] Fix test caching woes --- osu.Game.Tests/Visual/Editing/TestSceneEditorClock.cs | 6 ++++++ osu.Game/Tests/Visual/EditorClockTestScene.cs | 11 +++-------- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorClock.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorClock.cs index 4b9be77471..393d3886e7 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorClock.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorClock.cs @@ -2,10 +2,13 @@ // 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.Framework.Graphics.Containers; using osu.Game.Rulesets.Osu; +using osu.Game.Screens.Edit; using osu.Game.Screens.Edit.Components; +using osu.Game.Tests.Beatmaps; using osuTK; namespace osu.Game.Tests.Visual.Editing @@ -13,6 +16,9 @@ namespace osu.Game.Tests.Visual.Editing [TestFixture] public class TestSceneEditorClock : EditorClockTestScene { + [Cached] + private EditorBeatmap editorBeatmap = new EditorBeatmap(new TestBeatmap(new OsuRuleset().RulesetInfo)); + public TestSceneEditorClock() { Add(new FillFlowContainer diff --git a/osu.Game/Tests/Visual/EditorClockTestScene.cs b/osu.Game/Tests/Visual/EditorClockTestScene.cs index 93dfe58076..2d3e1960d9 100644 --- a/osu.Game/Tests/Visual/EditorClockTestScene.cs +++ b/osu.Game/Tests/Visual/EditorClockTestScene.cs @@ -6,7 +6,6 @@ using osu.Framework.Bindables; using osu.Framework.Input.Events; using osu.Game.Beatmaps; using osu.Game.Overlays; -using osu.Game.Rulesets.Edit; using osu.Game.Screens.Edit; namespace osu.Game.Tests.Visual @@ -20,20 +19,16 @@ namespace osu.Game.Tests.Visual [Cached] private readonly OverlayColourProvider overlayColour = new OverlayColourProvider(OverlayColourScheme.Aquamarine); - [Cached(typeof(EditorBeatmap))] - [Cached(typeof(IBeatSnapProvider))] - private readonly EditorBeatmap editorBeatmap; - protected readonly BindableBeatDivisor BeatDivisor = new BindableBeatDivisor(); + + [Cached] protected new readonly EditorClock Clock; protected virtual bool ScrollUsingMouseWheel => true; protected EditorClockTestScene() { - editorBeatmap = new EditorBeatmap(new Beatmap()); - - Clock = new EditorClock(editorBeatmap, BeatDivisor) { IsCoupled = false }; + Clock = new EditorClock(new Beatmap(), BeatDivisor) { IsCoupled = false }; } protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) From 08935cff86f8d962ea04044c720fea6cfed5964c Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 26 May 2022 18:37:04 +0900 Subject: [PATCH 201/395] Detect exclusive fullscreen on Windows --- .../Sections/Graphics/LayoutSettings.cs | 38 ++++++++++++++++++- 1 file changed, 36 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs b/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs index 602ace6dea..981f5dbac3 100644 --- a/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs @@ -13,6 +13,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Localisation; using osu.Framework.Platform; +using osu.Framework.Platform.Windows; using osu.Game.Configuration; using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterface; @@ -34,10 +35,14 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics private Bindable sizeFullscreen; private readonly BindableList resolutions = new BindableList(new[] { new Size(9999, 9999) }); + private readonly IBindable fullscreenCapability = new Bindable(FullscreenCapability.Capable); [Resolved] private OsuGameBase game { get; set; } + [Resolved] + private GameHost host { get; set; } + private SettingsDropdown resolutionDropdown; private SettingsDropdown displayDropdown; private SettingsDropdown windowModeDropdown; @@ -65,6 +70,9 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics windowModes.BindTo(host.Window.SupportedWindowModes); } + if (host.Window is WindowsWindow windowsWindow) + fullscreenCapability.BindTo(windowsWindow.FullscreenCapability); + Children = new Drawable[] { windowModeDropdown = new SettingsDropdown @@ -139,6 +147,8 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics } }, }; + + fullscreenCapability.BindValueChanged(_ => Schedule(updateScreenModeWarning), true); } protected override void LoadComplete() @@ -150,8 +160,7 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics windowModeDropdown.Current.BindValueChanged(mode => { updateDisplayModeDropdowns(); - - windowModeDropdown.WarningText = mode.NewValue != WindowMode.Fullscreen ? GraphicsSettingsStrings.NotFullscreenNote : default; + updateScreenModeWarning(); }, true); windowModes.BindCollectionChanged((sender, args) => @@ -213,6 +222,31 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics } } + private void updateScreenModeWarning() + { + if (windowModeDropdown.Current.Value != WindowMode.Fullscreen) + { + windowModeDropdown.WarningText = GraphicsSettingsStrings.NotFullscreenNote; + return; + } + + switch (fullscreenCapability.Value) + { + case FullscreenCapability.Unknown: + if (host.Window is WindowsWindow) + windowModeDropdown.WarningText = "Checking for exclusive fullscreen..."; + break; + + case FullscreenCapability.Capable: + windowModeDropdown.WarningText = default; + break; + + case FullscreenCapability.Incapable: + windowModeDropdown.WarningText = "Unable to enter exclusive fullscreen. You'll still experience some input latency."; + break; + } + } + private void bindPreviewEvent(Bindable bindable) { bindable.ValueChanged += _ => From a98d0cf0d8b6d8c91cdaff9626cc76859ca71537 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 26 May 2022 18:47:50 +0900 Subject: [PATCH 202/395] Simplify channel filter method --- osu.Game/Overlays/ChatOverlayV2.cs | 30 ++++++++++++++++++------------ 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/osu.Game/Overlays/ChatOverlayV2.cs b/osu.Game/Overlays/ChatOverlayV2.cs index 68199c9d25..f2ec007b19 100644 --- a/osu.Game/Overlays/ChatOverlayV2.cs +++ b/osu.Game/Overlays/ChatOverlayV2.cs @@ -3,7 +3,6 @@ #nullable enable -using System.Collections; using System.Collections.Generic; using System.Collections.Specialized; using System.Diagnostics; @@ -56,13 +55,6 @@ namespace osu.Game.Overlays private const float side_bar_width = 190; private const float chat_bar_height = 60; - private readonly ChannelType[] excludedChannelTypes = - { - ChannelType.Multiplayer, - ChannelType.Spectator, - ChannelType.Temporary, - }; - [Resolved] private OsuConfigManager config { get; set; } = null!; @@ -353,7 +345,7 @@ namespace osu.Game.Overlays switch (args.Action) { case NotifyCollectionChangedAction.Add: - IEnumerable newChannels = filterToChatChannels(args.NewItems); + IEnumerable newChannels = args.NewItems.OfType().Where(isChatChannel); foreach (var channel in newChannels) channelList.AddChannel(channel); @@ -361,7 +353,7 @@ namespace osu.Game.Overlays break; case NotifyCollectionChangedAction.Remove: - IEnumerable leftChannels = filterToChatChannels(args.OldItems); + IEnumerable leftChannels = args.OldItems.OfType().Where(isChatChannel); foreach (var channel in leftChannels) { @@ -408,7 +400,21 @@ namespace osu.Game.Overlays channelList.ScrollChannelIntoView(currentChannel.Value); } - private IEnumerable filterToChatChannels(IEnumerable channels) - => channels.Cast().Where(c => !excludedChannelTypes.Contains(c.Type)); + /// + /// Whether a channel should be displayed in this overlay, based on its type. + /// + private static bool isChatChannel(Channel channel) + { + switch (channel.Type) + { + case ChannelType.Multiplayer: + case ChannelType.Spectator: + case ChannelType.Temporary: + return false; + + default: + return true; + } + } } } From 3915b8e414a00e085e6c59a2da6c7df76f32fcc1 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 26 May 2022 20:01:33 +0900 Subject: [PATCH 203/395] Fix multiplayer race condition when starting gameplay --- .../Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs | 6 ++++++ .../OnlinePlay/Multiplayer/MultiplayerPlayerLoader.cs | 9 +++++++-- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs index 5dab845999..d9125dc0a0 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs @@ -149,6 +149,12 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer protected override void StartGameplay() { + // We can enter this screen one of two ways: + // 1. Via the automatic natural progression of PlayerLoader into Player. + // We'll arrive here in a Loaded state, and we need to let the server know that we're ready to start. + // 2. Via the server forcefully starting gameplay because players have been hanging out in PlayerLoader for too long. + // We'll arrive here in a Playing state, and we should neither show the loading spinner nor tell the server that we're ready to start (gameplay has already started). + if (client.LocalUser?.State == MultiplayerUserState.Loaded) { // block base call, but let the server know we are ready to start. diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayerLoader.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayerLoader.cs index 7f01bd64ab..e9bf5339a9 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayerLoader.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayerLoader.cs @@ -26,8 +26,13 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer } protected override bool ReadyForGameplay => - base.ReadyForGameplay - // The server is forcefully starting gameplay. + ( + // The user is ready to enter gameplay. + base.ReadyForGameplay + // And the server has received the message that we're loaded. + && multiplayerClient.LocalUser?.State == MultiplayerUserState.Loaded + ) + // Or the server is forcefully starting gameplay. || multiplayerClient.LocalUser?.State == MultiplayerUserState.Playing; protected override void OnPlayerLoaded() From 0224947de0b886468dad7940c4dd6cd94b242e13 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 26 May 2022 20:09:28 +0900 Subject: [PATCH 204/395] Add comment about how starting gameplay works --- osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs index d9125dc0a0..3bf8a09cb9 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs @@ -154,10 +154,11 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer // We'll arrive here in a Loaded state, and we need to let the server know that we're ready to start. // 2. Via the server forcefully starting gameplay because players have been hanging out in PlayerLoader for too long. // We'll arrive here in a Playing state, and we should neither show the loading spinner nor tell the server that we're ready to start (gameplay has already started). + // + // The base call is blocked here because in both cases gameplay is started only when the server says so via onGameplayStarted(). if (client.LocalUser?.State == MultiplayerUserState.Loaded) { - // block base call, but let the server know we are ready to start. loadingDisplay.Show(); client.ChangeState(MultiplayerUserState.ReadyForGameplay); } From 3183621d3d20e05d65d4f6b9ea383d7cc6df3512 Mon Sep 17 00:00:00 2001 From: sw1tchbl4d3 Date: Thu, 26 May 2022 22:57:24 +0200 Subject: [PATCH 205/395] Add search bar for the `CurrentlyPlayingDisplay` --- .../Dashboard/CurrentlyPlayingDisplay.cs | 119 ++++++++++++++---- 1 file changed, 98 insertions(+), 21 deletions(-) diff --git a/osu.Game/Overlays/Dashboard/CurrentlyPlayingDisplay.cs b/osu.Game/Overlays/Dashboard/CurrentlyPlayingDisplay.cs index a9312e9a3a..1c14eee9c7 100644 --- a/osu.Game/Overlays/Dashboard/CurrentlyPlayingDisplay.cs +++ b/osu.Game/Overlays/Dashboard/CurrentlyPlayingDisplay.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.Collections.Generic; using System.Collections.Specialized; using System.Diagnostics; using System.Linq; @@ -11,6 +12,8 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Screens; using osu.Game.Database; +using osu.Game.Graphics; +using osu.Game.Graphics.UserInterface; using osu.Game.Online.API; using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Spectator; @@ -24,10 +27,17 @@ namespace osu.Game.Overlays.Dashboard { internal class CurrentlyPlayingDisplay : CompositeDrawable { + private const float search_bar_height = 40; + private const float search_bar_width = 250; + private readonly IBindableList playingUsers = new BindableList(); private FillFlowContainer userFlow; + private List currentUsers = new List(); + private FocusedTextBox searchBar; + private Container searchBarContainer; + [Resolved] private SpectatorClient spectatorClient { get; set; } @@ -37,13 +47,46 @@ namespace osu.Game.Overlays.Dashboard RelativeSizeAxes = Axes.X; AutoSizeAxes = Axes.Y; - InternalChild = userFlow = new FillFlowContainer + searchBarContainer = new Container + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Padding = new MarginPadding(10), + Child = searchBar = new FocusedTextBox + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Height = search_bar_height, + Width = search_bar_width, + + Colour = OsuColour.Gray(0.8f), + + PlaceholderText = "Search for User...", + HoldFocus = true, + ReleaseFocusOnCommit = true, + }, + }; + + userFlow = new FillFlowContainer { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, - Padding = new MarginPadding(10), + Padding = new MarginPadding { + Top = 10 + 10 + search_bar_height, + Bottom = 10, + Right = 10, + Left = 10, + }, Spacing = new Vector2(10), }; + + InternalChildren = new Drawable[] + { + searchBarContainer, + userFlow, + }; + + searchBar.Current.ValueChanged += onSearchBarValueChanged; } [Resolved] @@ -57,6 +100,57 @@ namespace osu.Game.Overlays.Dashboard playingUsers.BindCollectionChanged(onPlayingUsersChanged, true); } + private void addCard(int userId) + { + if (currentUsers.Contains(userId)) + return; + + users.GetUserAsync(userId).ContinueWith(task => + { + var user = task.GetResultSafely(); + + if (user == null) + return; + + if (!user.Username.ToLower().Contains(searchBar.Text.ToLower())) + return; + + Schedule(() => + { + // user may no longer be playing. + if (!playingUsers.Contains(user.Id)) + return; + + currentUsers.Add(user.Id); + + userFlow.Add(createUserPanel(user)); + }); + }); + } + + private void removeCard(PlayingUserPanel card) + { + if (card == null) + return; + + currentUsers.Remove(card.User.Id); + card.Expire(); + } + + private void onSearchBarValueChanged(ValueChangedEvent change) + { + foreach (PlayingUserPanel card in userFlow) + { + if (!card.User.Username.ToLower().Contains(change.NewValue.ToLower())) + removeCard(card); + } + + foreach (int userId in playingUsers) + { + addCard(userId); + } + } + private void onPlayingUsersChanged(object sender, NotifyCollectionChangedEventArgs e) => Schedule(() => { switch (e.Action) @@ -65,24 +159,7 @@ namespace osu.Game.Overlays.Dashboard Debug.Assert(e.NewItems != null); foreach (int userId in e.NewItems) - { - users.GetUserAsync(userId).ContinueWith(task => - { - var user = task.GetResultSafely(); - - if (user == null) - return; - - Schedule(() => - { - // user may no longer be playing. - if (!playingUsers.Contains(user.Id)) - return; - - userFlow.Add(createUserPanel(user)); - }); - }); - } + addCard(userId); break; @@ -90,7 +167,7 @@ namespace osu.Game.Overlays.Dashboard Debug.Assert(e.OldItems != null); foreach (int userId in e.OldItems) - userFlow.FirstOrDefault(card => card.User.Id == userId)?.Expire(); + removeCard(userFlow.FirstOrDefault(card => card.User.Id == userId)); break; } }); From de5b60ab66a6d33655e7b003c1ff21caa87867ad Mon Sep 17 00:00:00 2001 From: sw1tchbl4d3 Date: Fri, 27 May 2022 00:40:23 +0200 Subject: [PATCH 206/395] Move filtering from manual loops to `SearchContainer` --- .../Dashboard/CurrentlyPlayingDisplay.cs | 57 ++++++++----------- 1 file changed, 25 insertions(+), 32 deletions(-) diff --git a/osu.Game/Overlays/Dashboard/CurrentlyPlayingDisplay.cs b/osu.Game/Overlays/Dashboard/CurrentlyPlayingDisplay.cs index 1c14eee9c7..7dd5c73b79 100644 --- a/osu.Game/Overlays/Dashboard/CurrentlyPlayingDisplay.cs +++ b/osu.Game/Overlays/Dashboard/CurrentlyPlayingDisplay.cs @@ -10,6 +10,7 @@ using osu.Framework.Bindables; using osu.Framework.Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Localisation; using osu.Framework.Screens; using osu.Game.Database; using osu.Game.Graphics; @@ -32,9 +33,8 @@ namespace osu.Game.Overlays.Dashboard private readonly IBindableList playingUsers = new BindableList(); - private FillFlowContainer userFlow; + private SearchContainer userFlow; - private List currentUsers = new List(); private FocusedTextBox searchBar; private Container searchBarContainer; @@ -67,7 +67,7 @@ namespace osu.Game.Overlays.Dashboard }, }; - userFlow = new FillFlowContainer + userFlow = new SearchContainer { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, @@ -102,9 +102,6 @@ namespace osu.Game.Overlays.Dashboard private void addCard(int userId) { - if (currentUsers.Contains(userId)) - return; - users.GetUserAsync(userId).ContinueWith(task => { var user = task.GetResultSafely(); @@ -121,35 +118,12 @@ namespace osu.Game.Overlays.Dashboard if (!playingUsers.Contains(user.Id)) return; - currentUsers.Add(user.Id); - userFlow.Add(createUserPanel(user)); }); }); } - private void removeCard(PlayingUserPanel card) - { - if (card == null) - return; - - currentUsers.Remove(card.User.Id); - card.Expire(); - } - - private void onSearchBarValueChanged(ValueChangedEvent change) - { - foreach (PlayingUserPanel card in userFlow) - { - if (!card.User.Username.ToLower().Contains(change.NewValue.ToLower())) - removeCard(card); - } - - foreach (int userId in playingUsers) - { - addCard(userId); - } - } + private void onSearchBarValueChanged(ValueChangedEvent change) => userFlow.SearchTerm = searchBar.Text; private void onPlayingUsersChanged(object sender, NotifyCollectionChangedEventArgs e) => Schedule(() => { @@ -167,7 +141,7 @@ namespace osu.Game.Overlays.Dashboard Debug.Assert(e.OldItems != null); foreach (int userId in e.OldItems) - removeCard(userFlow.FirstOrDefault(card => card.User.Id == userId)); + userFlow.FirstOrDefault(card => card.User.Id == userId)?.Expire(); break; } }); @@ -179,16 +153,35 @@ namespace osu.Game.Overlays.Dashboard panel.Origin = Anchor.TopCentre; }); - private class PlayingUserPanel : CompositeDrawable + private class PlayingUserPanel : CompositeDrawable, IFilterable { public readonly APIUser User; + public IEnumerable FilterTerms => filterTerm; [Resolved(canBeNull: true)] private IPerformFromScreenRunner performer { get; set; } + private IEnumerable filterTerm; + + public bool MatchingFilter + { + set + { + if (value) + Show(); + else + Hide(); + } + } + + public bool FilteringActive + { + set { } + } public PlayingUserPanel(APIUser user) { User = user; + filterTerm = new LocalisableString[] { User.Username }; AutoSizeAxes = Axes.Both; } From a8e453e6601f495613b11ebbe42c635113cf4d8d Mon Sep 17 00:00:00 2001 From: sw1tchbl4d3 Date: Fri, 27 May 2022 00:43:29 +0200 Subject: [PATCH 207/395] Remove now unnecessary username filter check --- osu.Game/Overlays/Dashboard/CurrentlyPlayingDisplay.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/osu.Game/Overlays/Dashboard/CurrentlyPlayingDisplay.cs b/osu.Game/Overlays/Dashboard/CurrentlyPlayingDisplay.cs index 7dd5c73b79..f57c808144 100644 --- a/osu.Game/Overlays/Dashboard/CurrentlyPlayingDisplay.cs +++ b/osu.Game/Overlays/Dashboard/CurrentlyPlayingDisplay.cs @@ -109,9 +109,6 @@ namespace osu.Game.Overlays.Dashboard if (user == null) return; - if (!user.Username.ToLower().Contains(searchBar.Text.ToLower())) - return; - Schedule(() => { // user may no longer be playing. From 0cdefbc6df3200bbb758f99cad47d55168a9a812 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Thu, 26 May 2022 17:39:29 -0700 Subject: [PATCH 208/395] Add failing beatmap info status test --- .../Visual/Online/TestSceneBeatmapSetOverlay.cs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlay.cs index 859727e632..80bb2aa2e8 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlay.cs @@ -10,6 +10,8 @@ using osu.Game.Rulesets; using System; using System.Collections.Generic; using System.Linq; +using osu.Framework.Testing; +using osu.Game.Beatmaps.Drawables; using osu.Game.Online.API.Requests.Responses; using APIUser = osu.Game.Online.API.Requests.Responses.APIUser; @@ -101,6 +103,12 @@ namespace osu.Game.Tests.Visual.Online AddStep("show many difficulties", () => overlay.ShowBeatmapSet(createManyDifficultiesBeatmapSet())); downloadAssert(true); + + AddAssert("status is loved", () => overlay.ChildrenOfType().Single().Status == BeatmapOnlineStatus.Loved); + + AddStep("go to second beatmap", () => overlay.ChildrenOfType().ElementAt(1).TriggerClick()); + + AddAssert("status is graveyard", () => overlay.ChildrenOfType().Single().Status == BeatmapOnlineStatus.Graveyard); } [Test] @@ -232,6 +240,7 @@ namespace osu.Game.Tests.Visual.Online Fails = Enumerable.Range(1, 100).Select(j => j % 12 - 6).ToArray(), Retries = Enumerable.Range(-2, 100).Select(j => j % 12 - 6).ToArray(), }, + Status = i % 2 == 0 ? BeatmapOnlineStatus.Graveyard : BeatmapOnlineStatus.Loved, }); } From 2b5aa35b56a0c23058ef9c658b4411f3d7e3ce4a Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Thu, 26 May 2022 17:39:16 -0700 Subject: [PATCH 209/395] Fix beatmap info status pill being incorrect on multiple status beatmaps --- osu.Game/Overlays/BeatmapSet/BeatmapSetHeaderContent.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/BeatmapSet/BeatmapSetHeaderContent.cs b/osu.Game/Overlays/BeatmapSet/BeatmapSetHeaderContent.cs index 56efb725cd..e0fc7482f6 100644 --- a/osu.Game/Overlays/BeatmapSet/BeatmapSetHeaderContent.cs +++ b/osu.Game/Overlays/BeatmapSet/BeatmapSetHeaderContent.cs @@ -10,6 +10,7 @@ using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Localisation; +using osu.Game.Beatmaps; using osu.Game.Beatmaps.Drawables; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; @@ -229,6 +230,8 @@ namespace osu.Game.Overlays.BeatmapSet { Details.BeatmapInfo = b.NewValue; externalLink.Link = $@"{api.WebsiteRootUrl}/beatmapsets/{BeatmapSet.Value?.OnlineID}#{b.NewValue?.Ruleset.ShortName}/{b.NewValue?.OnlineID}"; + + onlineStatusPill.Status = b.NewValue?.Status ?? BeatmapOnlineStatus.None; }; } @@ -272,7 +275,6 @@ namespace osu.Game.Overlays.BeatmapSet featuredArtist.Alpha = setInfo.NewValue.TrackId != null ? 1 : 0; onlineStatusPill.FadeIn(500, Easing.OutQuint); - onlineStatusPill.Status = setInfo.NewValue.Status; downloadButtonsContainer.FadeIn(transition_duration); favouriteButton.FadeIn(transition_duration); From 125dda716dbd42b0b2203a441c80c6eb546b893a Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Thu, 26 May 2022 19:46:42 -0700 Subject: [PATCH 210/395] Add failing scores container visibility test --- osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlay.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlay.cs index 80bb2aa2e8..9d206af40e 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlay.cs @@ -13,6 +13,7 @@ using System.Linq; using osu.Framework.Testing; using osu.Game.Beatmaps.Drawables; using osu.Game.Online.API.Requests.Responses; +using osu.Game.Overlays.BeatmapSet.Scores; using APIUser = osu.Game.Online.API.Requests.Responses.APIUser; namespace osu.Game.Tests.Visual.Online @@ -105,10 +106,12 @@ namespace osu.Game.Tests.Visual.Online downloadAssert(true); AddAssert("status is loved", () => overlay.ChildrenOfType().Single().Status == BeatmapOnlineStatus.Loved); + AddAssert("scores container is visible", () => overlay.ChildrenOfType().Single().Alpha == 1); AddStep("go to second beatmap", () => overlay.ChildrenOfType().ElementAt(1).TriggerClick()); AddAssert("status is graveyard", () => overlay.ChildrenOfType().Single().Status == BeatmapOnlineStatus.Graveyard); + AddAssert("scores container is hidden", () => overlay.ChildrenOfType().Single().Alpha == 0); } [Test] From 355456311447d46394adf2114d6118a11daf6564 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Thu, 26 May 2022 19:50:54 -0700 Subject: [PATCH 211/395] Fix beatmap info scores container not hiding when there's no leaderboard --- osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs index 591e4cf73e..5f24a6549d 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs @@ -253,7 +253,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores noScoresPlaceholder.Hide(); - if (Beatmap.Value == null || Beatmap.Value.OnlineID <= 0 || (Beatmap.Value.BeatmapSet as IBeatmapSetOnlineInfo)?.Status <= BeatmapOnlineStatus.Pending) + if (Beatmap.Value == null || Beatmap.Value.OnlineID <= 0 || (Beatmap.Value.Status <= BeatmapOnlineStatus.Pending)) { Scores = null; Hide(); From 389020b4975636f63962e35c60049ac468ccae7f Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Thu, 26 May 2022 20:58:47 -0700 Subject: [PATCH 212/395] Fix scores container test failures --- osu.Game/Tests/Beatmaps/TestBeatmap.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Tests/Beatmaps/TestBeatmap.cs b/osu.Game/Tests/Beatmaps/TestBeatmap.cs index 3b4547cb49..ff670e1232 100644 --- a/osu.Game/Tests/Beatmaps/TestBeatmap.cs +++ b/osu.Game/Tests/Beatmaps/TestBeatmap.cs @@ -35,6 +35,7 @@ namespace osu.Game.Tests.Beatmaps BeatmapInfo.Length = 75000; BeatmapInfo.OnlineInfo = new APIBeatmap(); BeatmapInfo.OnlineID = Interlocked.Increment(ref onlineBeatmapID); + BeatmapInfo.Status = BeatmapOnlineStatus.Ranked; Debug.Assert(BeatmapInfo.BeatmapSet != null); From e551a536015a691297993eb5ce005c50e4d8f11a Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Thu, 26 May 2022 21:45:07 -0700 Subject: [PATCH 213/395] Fix checkmarks not showing on editor stateful menu items --- .../Edit/Components/Menus/EditorMenuBar.cs | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/osu.Game/Screens/Edit/Components/Menus/EditorMenuBar.cs b/osu.Game/Screens/Edit/Components/Menus/EditorMenuBar.cs index 440071bc4c..20b8bba6da 100644 --- a/osu.Game/Screens/Edit/Components/Menus/EditorMenuBar.cs +++ b/osu.Game/Screens/Edit/Components/Menus/EditorMenuBar.cs @@ -110,11 +110,31 @@ namespace osu.Game.Screens.Edit.Components.Menus case EditorMenuItemSpacer spacer: return new DrawableSpacer(spacer); + case StatefulMenuItem stateful: + return new EditorStatefulMenuItem(stateful); + default: return new EditorMenuItem(item); } } + private class EditorStatefulMenuItem : DrawableStatefulMenuItem + { + public EditorStatefulMenuItem(StatefulMenuItem item) + : base(item) + { + } + + [BackgroundDependencyLoader] + private void load(OverlayColourProvider colourProvider) + { + BackgroundColour = colourProvider.Background2; + BackgroundColourHover = colourProvider.Background1; + + Foreground.Padding = new MarginPadding { Vertical = 2 }; + } + } + private class EditorMenuItem : DrawableOsuMenuItem { public EditorMenuItem(MenuItem item) From 105d581f247ce41a9235e356104334b4c903ba46 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Thu, 26 May 2022 22:23:03 -0700 Subject: [PATCH 214/395] Use existing web localisation for chat strings --- osu.Game/Overlays/Chat/ChannelList/ChannelList.cs | 9 ++++++--- osu.Game/Overlays/Chat/ChatOverlayTopBar.cs | 3 ++- osu.Game/Overlays/Chat/ChatTextBar.cs | 5 +++-- osu.Game/Overlays/Chat/ChatTextBox.cs | 3 ++- 4 files changed, 13 insertions(+), 7 deletions(-) diff --git a/osu.Game/Overlays/Chat/ChannelList/ChannelList.cs b/osu.Game/Overlays/Chat/ChannelList/ChannelList.cs index 21b2251aca..ce1ed2b4d7 100644 --- a/osu.Game/Overlays/Chat/ChannelList/ChannelList.cs +++ b/osu.Game/Overlays/Chat/ChannelList/ChannelList.cs @@ -7,14 +7,17 @@ using System; using System.Linq; using System.Collections.Generic; using osu.Framework.Allocation; +using osu.Framework.Extensions.LocalisationExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; +using osu.Framework.Localisation; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osu.Game.Online.Chat; using osu.Game.Overlays.Chat.Listing; +using osu.Game.Resources.Localisation.Web; namespace osu.Game.Overlays.Chat.ChannelList { @@ -57,13 +60,13 @@ namespace osu.Game.Overlays.Chat.ChannelList AutoSizeAxes = Axes.Y, Children = new Drawable[] { - new ChannelListLabel("CHANNELS"), + new ChannelListLabel(ChatStrings.ChannelsListTitlePUBLIC.ToUpper()), publicChannelFlow = new ChannelListItemFlow(), selector = new ChannelListItem(ChannelListingChannel) { Margin = new MarginPadding { Bottom = 10 }, }, - new ChannelListLabel("DIRECT MESSAGES"), + new ChannelListLabel(ChatStrings.ChannelsListTitlePM.ToUpper()), privateChannelFlow = new ChannelListItemFlow(), }, }, @@ -126,7 +129,7 @@ namespace osu.Game.Overlays.Chat.ChannelList private class ChannelListLabel : OsuSpriteText { - public ChannelListLabel(string label) + public ChannelListLabel(LocalisableString label) { Text = label; Margin = new MarginPadding { Left = 18, Bottom = 5 }; diff --git a/osu.Game/Overlays/Chat/ChatOverlayTopBar.cs b/osu.Game/Overlays/Chat/ChatOverlayTopBar.cs index 3a8cd1fb91..79f22b51f7 100644 --- a/osu.Game/Overlays/Chat/ChatOverlayTopBar.cs +++ b/osu.Game/Overlays/Chat/ChatOverlayTopBar.cs @@ -13,6 +13,7 @@ using osu.Framework.Graphics.Textures; using osu.Framework.Input.Events; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; +using osu.Game.Resources.Localisation.Web; using osuTK; using osuTK.Graphics; @@ -58,7 +59,7 @@ namespace osu.Game.Overlays.Chat { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, - Text = "osu!chat", + Text = ChatStrings.TitleCompact, Font = OsuFont.Torus.With(size: 16, weight: FontWeight.SemiBold), Margin = new MarginPadding { Bottom = 2f }, }, diff --git a/osu.Game/Overlays/Chat/ChatTextBar.cs b/osu.Game/Overlays/Chat/ChatTextBar.cs index 404d686d91..4dc609acd1 100644 --- a/osu.Game/Overlays/Chat/ChatTextBar.cs +++ b/osu.Game/Overlays/Chat/ChatTextBar.cs @@ -14,6 +14,7 @@ using osu.Framework.Graphics.UserInterface; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Online.Chat; +using osu.Game.Resources.Localisation.Web; using osuTK; namespace osu.Game.Overlays.Chat @@ -141,11 +142,11 @@ namespace osu.Game.Overlays.Chat switch (newChannel?.Type) { case ChannelType.Public: - chattingText.Text = $"chatting in {newChannel.Name}"; + chattingText.Text = ChatStrings.TalkingIn(newChannel.Name); break; case ChannelType.PM: - chattingText.Text = $"chatting with {newChannel.Name}"; + chattingText.Text = ChatStrings.TalkingWith(newChannel.Name); break; default: diff --git a/osu.Game/Overlays/Chat/ChatTextBox.cs b/osu.Game/Overlays/Chat/ChatTextBox.cs index e0f949caba..1ee0e8445f 100644 --- a/osu.Game/Overlays/Chat/ChatTextBox.cs +++ b/osu.Game/Overlays/Chat/ChatTextBox.cs @@ -5,6 +5,7 @@ using osu.Framework.Bindables; using osu.Game.Graphics.UserInterface; +using osu.Game.Resources.Localisation.Web; namespace osu.Game.Overlays.Chat { @@ -22,7 +23,7 @@ namespace osu.Game.Overlays.Chat { bool showSearch = change.NewValue; - PlaceholderText = showSearch ? "type here to search" : "type here"; + PlaceholderText = showSearch ? HomeStrings.SearchPlaceholder : ChatStrings.InputPlaceholder; Text = string.Empty; }, true); } From 4967d036063f954cdd37ac33134288ebfb26a881 Mon Sep 17 00:00:00 2001 From: sw1tchbl4d3 Date: Fri, 27 May 2022 09:58:40 +0200 Subject: [PATCH 215/395] Update currently playing search bar to resemble existing UI --- .../Dashboard/CurrentlyPlayingDisplay.cs | 37 +++++++++++-------- 1 file changed, 22 insertions(+), 15 deletions(-) diff --git a/osu.Game/Overlays/Dashboard/CurrentlyPlayingDisplay.cs b/osu.Game/Overlays/Dashboard/CurrentlyPlayingDisplay.cs index f57c808144..b6deae27e5 100644 --- a/osu.Game/Overlays/Dashboard/CurrentlyPlayingDisplay.cs +++ b/osu.Game/Overlays/Dashboard/CurrentlyPlayingDisplay.cs @@ -10,6 +10,8 @@ using osu.Framework.Bindables; using osu.Framework.Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Effects; +using osu.Framework.Graphics.Shapes; using osu.Framework.Localisation; using osu.Framework.Screens; using osu.Game.Database; @@ -35,35 +37,42 @@ namespace osu.Game.Overlays.Dashboard private SearchContainer userFlow; - private FocusedTextBox searchBar; - private Container searchBarContainer; + private Box searchBarBackground; + private BasicSearchTextBox searchBar; + private Container searchBarContainer; [Resolved] private SpectatorClient spectatorClient { get; set; } [BackgroundDependencyLoader] - private void load() + private void load(OverlayColourProvider colourProvider) { RelativeSizeAxes = Axes.X; AutoSizeAxes = Axes.Y; - searchBarContainer = new Container + searchBarBackground = new Box + { + RelativeSizeAxes = Axes.X, + Height = 10*2 + search_bar_height, + Colour = colourProvider.Background4, + }; + + searchBarContainer = new Container { Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, + RelativeSizeAxes = Axes.X, Padding = new MarginPadding(10), - Child = searchBar = new FocusedTextBox + + Child = searchBar = new BasicSearchTextBox { Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, Height = search_bar_height, - Width = search_bar_width, - Colour = OsuColour.Gray(0.8f), + RelativeSizeAxes = Axes.X, - PlaceholderText = "Search for User...", - HoldFocus = true, - ReleaseFocusOnCommit = true, + PlaceholderText = "type to search", }, }; @@ -72,7 +81,7 @@ namespace osu.Game.Overlays.Dashboard RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, Padding = new MarginPadding { - Top = 10 + 10 + search_bar_height, + Top = 10*3 + search_bar_height, Bottom = 10, Right = 10, Left = 10, @@ -82,6 +91,7 @@ namespace osu.Game.Overlays.Dashboard InternalChildren = new Drawable[] { + searchBarBackground, searchBarContainer, userFlow, }; @@ -170,10 +180,7 @@ namespace osu.Game.Overlays.Dashboard } } - public bool FilteringActive - { - set { } - } + public bool FilteringActive { set; get; } public PlayingUserPanel(APIUser user) { From 7c459faaf0bd9978f44ec6b2436b08818561866b Mon Sep 17 00:00:00 2001 From: sw1tchbl4d3 Date: Fri, 27 May 2022 10:05:59 +0200 Subject: [PATCH 216/395] Remove unneeded search_bar_width and more --- osu.Game/Overlays/Dashboard/CurrentlyPlayingDisplay.cs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/osu.Game/Overlays/Dashboard/CurrentlyPlayingDisplay.cs b/osu.Game/Overlays/Dashboard/CurrentlyPlayingDisplay.cs index b6deae27e5..c7b08a1ffc 100644 --- a/osu.Game/Overlays/Dashboard/CurrentlyPlayingDisplay.cs +++ b/osu.Game/Overlays/Dashboard/CurrentlyPlayingDisplay.cs @@ -10,7 +10,6 @@ using osu.Framework.Bindables; using osu.Framework.Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; using osu.Framework.Localisation; using osu.Framework.Screens; @@ -30,8 +29,7 @@ namespace osu.Game.Overlays.Dashboard { internal class CurrentlyPlayingDisplay : CompositeDrawable { - private const float search_bar_height = 40; - private const float search_bar_width = 250; + private const float SEARCHBAR_HEIGHT = 40; private readonly IBindableList playingUsers = new BindableList(); @@ -53,7 +51,7 @@ namespace osu.Game.Overlays.Dashboard searchBarBackground = new Box { RelativeSizeAxes = Axes.X, - Height = 10*2 + search_bar_height, + Height = 10*2 + SEARCHBAR_HEIGHT, Colour = colourProvider.Background4, }; @@ -68,7 +66,7 @@ namespace osu.Game.Overlays.Dashboard { Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, - Height = search_bar_height, + Height = SEARCHBAR_HEIGHT, RelativeSizeAxes = Axes.X, @@ -81,7 +79,7 @@ namespace osu.Game.Overlays.Dashboard RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, Padding = new MarginPadding { - Top = 10*3 + search_bar_height, + Top = 10*3 + SEARCHBAR_HEIGHT, Bottom = 10, Right = 10, Left = 10, From e53c8518debd9bf30961c620f7dffd4d92c450dd Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 27 May 2022 19:18:37 +0900 Subject: [PATCH 217/395] Don't output beatmap parse failures in a visible manner The user can't do much about this. When a user reports a beatmap issue, the logs will still contain this useful information. Should be fine I think. As mentioned in https://github.com/ppy/osu/discussions/18426 https://github.com/ppy/osu/issues/750 https://github.com/ppy/osu/issues/18372 etc. --- osu.Game/Beatmaps/Formats/LegacyDecoder.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/Formats/LegacyDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyDecoder.cs index 0276abc3ff..ff13e61360 100644 --- a/osu.Game/Beatmaps/Formats/LegacyDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyDecoder.cs @@ -59,7 +59,7 @@ namespace osu.Game.Beatmaps.Formats } catch (Exception e) { - Logger.Log($"Failed to process line \"{line}\" into \"{output}\": {e.Message}", LoggingTarget.Runtime, LogLevel.Important); + Logger.Log($"Failed to process line \"{line}\" into \"{output}\": {e.Message}"); } } } From 6a0cf26722cfa9ab08de4f8f00156657c798afbd Mon Sep 17 00:00:00 2001 From: sw1tchbl4d3 Date: Fri, 27 May 2022 12:36:43 +0200 Subject: [PATCH 218/395] Make text localisable and add padding constant --- osu.Game/Overlays/Dashboard/CurrentlyPlayingDisplay.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/Dashboard/CurrentlyPlayingDisplay.cs b/osu.Game/Overlays/Dashboard/CurrentlyPlayingDisplay.cs index c7b08a1ffc..d9418a303e 100644 --- a/osu.Game/Overlays/Dashboard/CurrentlyPlayingDisplay.cs +++ b/osu.Game/Overlays/Dashboard/CurrentlyPlayingDisplay.cs @@ -30,6 +30,7 @@ namespace osu.Game.Overlays.Dashboard internal class CurrentlyPlayingDisplay : CompositeDrawable { private const float SEARCHBAR_HEIGHT = 40; + private const float CONTAINER_PADDING = 10; private readonly IBindableList playingUsers = new BindableList(); @@ -51,7 +52,7 @@ namespace osu.Game.Overlays.Dashboard searchBarBackground = new Box { RelativeSizeAxes = Axes.X, - Height = 10*2 + SEARCHBAR_HEIGHT, + Height = CONTAINER_PADDING * 2 + SEARCHBAR_HEIGHT, Colour = colourProvider.Background4, }; @@ -70,7 +71,7 @@ namespace osu.Game.Overlays.Dashboard RelativeSizeAxes = Axes.X, - PlaceholderText = "type to search", + PlaceholderText = Resources.Localisation.Web.HomeStrings.SearchPlaceholder, }, }; @@ -79,7 +80,7 @@ namespace osu.Game.Overlays.Dashboard RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, Padding = new MarginPadding { - Top = 10*3 + SEARCHBAR_HEIGHT, + Top = CONTAINER_PADDING * 3 + SEARCHBAR_HEIGHT, Bottom = 10, Right = 10, Left = 10, From e1fd37fd4481a7bbee4e2bd20e4a65da943382f3 Mon Sep 17 00:00:00 2001 From: sw1tchbl4d3 Date: Fri, 27 May 2022 12:40:31 +0200 Subject: [PATCH 219/395] Use new padding constant where appropriate --- osu.Game/Overlays/Dashboard/CurrentlyPlayingDisplay.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Overlays/Dashboard/CurrentlyPlayingDisplay.cs b/osu.Game/Overlays/Dashboard/CurrentlyPlayingDisplay.cs index d9418a303e..a9e9932b5b 100644 --- a/osu.Game/Overlays/Dashboard/CurrentlyPlayingDisplay.cs +++ b/osu.Game/Overlays/Dashboard/CurrentlyPlayingDisplay.cs @@ -61,7 +61,7 @@ namespace osu.Game.Overlays.Dashboard Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, RelativeSizeAxes = Axes.X, - Padding = new MarginPadding(10), + Padding = new MarginPadding(CONTAINER_PADDING), Child = searchBar = new BasicSearchTextBox { @@ -81,9 +81,9 @@ namespace osu.Game.Overlays.Dashboard AutoSizeAxes = Axes.Y, Padding = new MarginPadding { Top = CONTAINER_PADDING * 3 + SEARCHBAR_HEIGHT, - Bottom = 10, - Right = 10, - Left = 10, + Bottom = CONTAINER_PADDING, + Right = CONTAINER_PADDING, + Left = CONTAINER_PADDING, }, Spacing = new Vector2(10), }; From d94315ee3f320c4edcc562c642dabcd63ecceebd Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 27 May 2022 20:03:30 +0900 Subject: [PATCH 220/395] Fix potential crash from unsafe drawable mutation in scoreboard update code --- .../Screens/OnlinePlay/Match/Components/MatchLeaderboard.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Match/Components/MatchLeaderboard.cs b/osu.Game/Screens/OnlinePlay/Match/Components/MatchLeaderboard.cs index ea7de917e2..b92c197f5a 100644 --- a/osu.Game/Screens/OnlinePlay/Match/Components/MatchLeaderboard.cs +++ b/osu.Game/Screens/OnlinePlay/Match/Components/MatchLeaderboard.cs @@ -38,13 +38,13 @@ namespace osu.Game.Screens.OnlinePlay.Match.Components var req = new GetRoomLeaderboardRequest(roomId.Value ?? 0); - req.Success += r => + req.Success += r => Schedule(() => { if (cancellationToken.IsCancellationRequested) return; SetScores(r.Leaderboard, r.UserScore); - }; + }); return req; } From ef47b380c602095a3d635017adb106b294f21e54 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 27 May 2022 20:18:46 +0900 Subject: [PATCH 221/395] Add featured artist playlist category --- .../Visual/Multiplayer/TestSceneDrawableRoom.cs | 6 ++++++ osu.Game/Online/Rooms/RoomCategory.cs | 5 +++++ .../OnlinePlay/Lounge/Components/DrawableRoom.cs | 2 +- .../OnlinePlay/Lounge/Components/RoomsContainer.cs | 2 +- .../OnlinePlay/Playlists/PlaylistsLoungeSubScreen.cs | 10 +++++++++- 5 files changed, 22 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoom.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoom.cs index 7d010592ae..7c1620a345 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoom.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoom.cs @@ -124,6 +124,12 @@ namespace osu.Game.Tests.Visual.Multiplayer Status = { Value = new RoomStatusOpen() }, Category = { Value = RoomCategory.Spotlight }, }), + createLoungeRoom(new Room + { + Name = { Value = "Featured artist room" }, + Status = { Value = new RoomStatusOpen() }, + Category = { Value = RoomCategory.FeaturedArtist }, + }), } }; }); diff --git a/osu.Game/Online/Rooms/RoomCategory.cs b/osu.Game/Online/Rooms/RoomCategory.cs index a1dcfa5fd9..bca4d78359 100644 --- a/osu.Game/Online/Rooms/RoomCategory.cs +++ b/osu.Game/Online/Rooms/RoomCategory.cs @@ -1,6 +1,8 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.ComponentModel; + namespace osu.Game.Online.Rooms { public enum RoomCategory @@ -8,5 +10,8 @@ namespace osu.Game.Online.Rooms // used for osu-web deserialization so names shouldn't be changed. Normal, Spotlight, + + [Description("Featured Artist")] + FeaturedArtist, } } diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs index 8e3aa77e7b..772232f6b4 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs @@ -237,7 +237,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components roomCategory.BindTo(Room.Category); roomCategory.BindValueChanged(c => { - if (c.NewValue == RoomCategory.Spotlight) + if (c.NewValue > RoomCategory.Normal) specialCategoryPill.Show(); else specialCategoryPill.Hide(); diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs index d61fbea387..0fd9290880 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs @@ -128,7 +128,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components { foreach (var room in roomFlow) { - roomFlow.SetLayoutPosition(room, room.Room.Category.Value == RoomCategory.Spotlight + roomFlow.SetLayoutPosition(room, room.Room.Category.Value > RoomCategory.Normal // Always show spotlight playlists at the top of the listing. ? float.MinValue : -(room.Room.RoomID.Value ?? 0)); diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsLoungeSubScreen.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsLoungeSubScreen.cs index dced9b8691..b36e162e3d 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsLoungeSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsLoungeSubScreen.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; +using System.ComponentModel; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Graphics; @@ -49,6 +50,10 @@ namespace osu.Game.Screens.OnlinePlay.Playlists case PlaylistsCategory.Spotlight: criteria.Category = @"spotlight"; break; + + case PlaylistsCategory.FeaturedArtist: + criteria.Category = @"featured_artist"; + break; } return criteria; @@ -73,7 +78,10 @@ namespace osu.Game.Screens.OnlinePlay.Playlists { Any, Normal, - Spotlight + Spotlight, + + [Description("Featured Artist")] + FeaturedArtist, } } } From 883c6f1eb30f460790d4d74fbe16e96f08e866e6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 27 May 2022 20:19:03 +0900 Subject: [PATCH 222/395] Update colour of spotlights playlist to match new specs --- .../Components/RoomSpecialCategoryPill.cs | 26 ++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomSpecialCategoryPill.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomSpecialCategoryPill.cs index 6cdbeb2af4..00e4fbf5eb 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomSpecialCategoryPill.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomSpecialCategoryPill.cs @@ -2,10 +2,12 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; +using osu.Framework.Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; +using osu.Game.Online.Rooms; using osuTK.Graphics; namespace osu.Game.Screens.OnlinePlay.Lounge.Components @@ -13,6 +15,10 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components public class RoomSpecialCategoryPill : OnlinePlayComposite { private SpriteText text; + private PillContainer pill; + + [Resolved] + private OsuColour colours { get; set; } public RoomSpecialCategoryPill() { @@ -20,9 +26,9 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components } [BackgroundDependencyLoader] - private void load(OsuColour colours) + private void load() { - InternalChild = new PillContainer + InternalChild = pill = new PillContainer { Background = { @@ -43,7 +49,21 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components { base.LoadComplete(); - Category.BindValueChanged(c => text.Text = c.NewValue.ToString(), true); + Category.BindValueChanged(c => + { + text.Text = c.NewValue.GetLocalisableDescription(); + + switch (c.NewValue) + { + case RoomCategory.Spotlight: + pill.Background.Colour = colours.Green2; + break; + + case RoomCategory.FeaturedArtist: + pill.Background.Colour = colours.Blue2; + break; + } + }, true); } } } From 2f57849bcba348d5eeb24c731d30234d465cab1a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 27 May 2022 20:58:13 +0900 Subject: [PATCH 223/395] Fix unexpected assertion failure --- osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoom.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoom.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoom.cs index 7c1620a345..3172a68b81 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoom.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoom.cs @@ -134,9 +134,9 @@ namespace osu.Game.Tests.Visual.Multiplayer }; }); - AddUntilStep("wait for panel load", () => rooms.Count == 5); + AddUntilStep("wait for panel load", () => rooms.Count == 6); AddUntilStep("correct status text", () => rooms.ChildrenOfType().Count(s => s.Text.ToString().StartsWith("Currently playing", StringComparison.Ordinal)) == 2); - AddUntilStep("correct status text", () => rooms.ChildrenOfType().Count(s => s.Text.ToString().StartsWith("Ready to play", StringComparison.Ordinal)) == 3); + AddUntilStep("correct status text", () => rooms.ChildrenOfType().Count(s => s.Text.ToString().StartsWith("Ready to play", StringComparison.Ordinal)) == 4); } [Test] From d9ecf69d208e8754cec9dae644a827180e473722 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 27 May 2022 23:29:39 +0900 Subject: [PATCH 224/395] Update framework --- osu.Android.props | 2 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index aaea784852..c51919894e 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -52,7 +52,7 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 5bc65ca507..8cf530ea3c 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -35,7 +35,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/osu.iOS.props b/osu.iOS.props index 3597e7e5c0..79bd6bed9e 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -61,7 +61,7 @@ - + @@ -84,7 +84,7 @@ - + From 6960bec52d8824a086e96823c49a6a9f571a07c1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 27 May 2022 23:29:48 +0900 Subject: [PATCH 225/395] Update resources --- 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 d5a77c6349..5688fd8cc4 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -51,7 +51,7 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 32a0adb859..5de0f4346c 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -36,7 +36,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 112b5b4615..c1a473ff75 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -62,7 +62,7 @@ - + From e2951d70d1eac5ee0f2291c2192ac9d8f8598269 Mon Sep 17 00:00:00 2001 From: sw1tchbl4d3 Date: Fri, 27 May 2022 16:38:54 +0200 Subject: [PATCH 226/395] Address code style issues --- .../TestSceneCurrentlyPlayingDisplay.cs | 4 +- .../Dashboard/CurrentlyPlayingDisplay.cs | 143 ++++++++---------- 2 files changed, 68 insertions(+), 79 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneCurrentlyPlayingDisplay.cs b/osu.Game.Tests/Visual/Online/TestSceneCurrentlyPlayingDisplay.cs index 35a4f8cf2d..edee26c081 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneCurrentlyPlayingDisplay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneCurrentlyPlayingDisplay.cs @@ -11,6 +11,7 @@ using osu.Framework.Testing; using osu.Game.Database; using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Spectator; +using osu.Game.Overlays; using osu.Game.Overlays.Dashboard; using osu.Game.Tests.Visual.Spectator; using osu.Game.Users; @@ -42,7 +43,8 @@ namespace osu.Game.Tests.Visual.Online CachedDependencies = new (Type, object)[] { (typeof(SpectatorClient), spectatorClient), - (typeof(UserLookupCache), lookupCache) + (typeof(UserLookupCache), lookupCache), + (typeof(OverlayColourProvider), new OverlayColourProvider(OverlayColourScheme.Purple)), }, Child = currentlyPlaying = new CurrentlyPlayingDisplay { diff --git a/osu.Game/Overlays/Dashboard/CurrentlyPlayingDisplay.cs b/osu.Game/Overlays/Dashboard/CurrentlyPlayingDisplay.cs index a9e9932b5b..3020b37351 100644 --- a/osu.Game/Overlays/Dashboard/CurrentlyPlayingDisplay.cs +++ b/osu.Game/Overlays/Dashboard/CurrentlyPlayingDisplay.cs @@ -14,7 +14,6 @@ using osu.Framework.Graphics.Shapes; using osu.Framework.Localisation; using osu.Framework.Screens; using osu.Game.Database; -using osu.Game.Graphics; using osu.Game.Graphics.UserInterface; using osu.Game.Online.API; using osu.Game.Online.API.Requests.Responses; @@ -29,16 +28,13 @@ namespace osu.Game.Overlays.Dashboard { internal class CurrentlyPlayingDisplay : CompositeDrawable { - private const float SEARCHBAR_HEIGHT = 40; - private const float CONTAINER_PADDING = 10; + private const float searchbox_height = 40f; + private const float container_padding = 10f; private readonly IBindableList playingUsers = new BindableList(); private SearchContainer userFlow; - - private Box searchBarBackground; - private BasicSearchTextBox searchBar; - private Container searchBarContainer; + private BasicSearchTextBox userFlowSearchTextBox; [Resolved] private SpectatorClient spectatorClient { get; set; } @@ -49,53 +45,48 @@ namespace osu.Game.Overlays.Dashboard RelativeSizeAxes = Axes.X; AutoSizeAxes = Axes.Y; - searchBarBackground = new Box - { - RelativeSizeAxes = Axes.X, - Height = CONTAINER_PADDING * 2 + SEARCHBAR_HEIGHT, - Colour = colourProvider.Background4, - }; - - searchBarContainer = new Container - { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - RelativeSizeAxes = Axes.X, - Padding = new MarginPadding(CONTAINER_PADDING), - - Child = searchBar = new BasicSearchTextBox - { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - Height = SEARCHBAR_HEIGHT, - - RelativeSizeAxes = Axes.X, - - PlaceholderText = Resources.Localisation.Web.HomeStrings.SearchPlaceholder, - }, - }; - - userFlow = new SearchContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Padding = new MarginPadding { - Top = CONTAINER_PADDING * 3 + SEARCHBAR_HEIGHT, - Bottom = CONTAINER_PADDING, - Right = CONTAINER_PADDING, - Left = CONTAINER_PADDING, - }, - Spacing = new Vector2(10), - }; - InternalChildren = new Drawable[] { - searchBarBackground, - searchBarContainer, - userFlow, + new Box + { + RelativeSizeAxes = Axes.X, + Height = container_padding * 2 + searchbox_height, + Colour = colourProvider.Background4, + }, + + new Container + { + RelativeSizeAxes = Axes.X, + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Padding = new MarginPadding(container_padding), + + Child = userFlowSearchTextBox = new BasicSearchTextBox + { + RelativeSizeAxes = Axes.X, + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Height = searchbox_height, + PlaceholderText = Resources.Localisation.Web.HomeStrings.SearchPlaceholder, + }, + }, + + userFlow = new SearchContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Padding = new MarginPadding + { + Top = container_padding * 3 + searchbox_height, + Bottom = container_padding, + Right = container_padding, + Left = container_padding, + }, + Spacing = new Vector2(10), + }, }; - searchBar.Current.ValueChanged += onSearchBarValueChanged; + userFlowSearchTextBox.Current.ValueChanged += onSearchBarValueChanged; } [Resolved] @@ -109,27 +100,7 @@ namespace osu.Game.Overlays.Dashboard playingUsers.BindCollectionChanged(onPlayingUsersChanged, true); } - private void addCard(int userId) - { - users.GetUserAsync(userId).ContinueWith(task => - { - var user = task.GetResultSafely(); - - if (user == null) - return; - - Schedule(() => - { - // user may no longer be playing. - if (!playingUsers.Contains(user.Id)) - return; - - userFlow.Add(createUserPanel(user)); - }); - }); - } - - private void onSearchBarValueChanged(ValueChangedEvent change) => userFlow.SearchTerm = searchBar.Text; + private void onSearchBarValueChanged(ValueChangedEvent change) => userFlow.SearchTerm = userFlowSearchTextBox.Text; private void onPlayingUsersChanged(object sender, NotifyCollectionChangedEventArgs e) => Schedule(() => { @@ -139,7 +110,24 @@ namespace osu.Game.Overlays.Dashboard Debug.Assert(e.NewItems != null); foreach (int userId in e.NewItems) - addCard(userId); + { + users.GetUserAsync(userId).ContinueWith(task => + { + var user = task.GetResultSafely(); + + if (user == null) + return; + + Schedule(() => + { + // user may no longer be playing. + if (!playingUsers.Contains(user.Id)) + return; + + userFlow.Add(createUserPanel(user)); + }); + }); + } break; @@ -159,14 +147,15 @@ namespace osu.Game.Overlays.Dashboard panel.Origin = Anchor.TopCentre; }); - private class PlayingUserPanel : CompositeDrawable, IFilterable + public class PlayingUserPanel : CompositeDrawable, IFilterable { public readonly APIUser User; - public IEnumerable FilterTerms => filterTerm; + public IEnumerable FilterTerms { get; set; } [Resolved(canBeNull: true)] private IPerformFromScreenRunner performer { get; set; } - private IEnumerable filterTerm; + + public bool FilteringActive { set; get; } public bool MatchingFilter { @@ -179,12 +168,10 @@ namespace osu.Game.Overlays.Dashboard } } - public bool FilteringActive { set; get; } - public PlayingUserPanel(APIUser user) { User = user; - filterTerm = new LocalisableString[] { User.Username }; + FilterTerms = new LocalisableString[] { User.Username }; AutoSizeAxes = Axes.Both; } From a94432f3bd743ec4c98f8d45a2e095f6f5f85e16 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 27 May 2022 16:40:15 +0200 Subject: [PATCH 227/395] Fix drawable room border colour not matching badge --- osu.Game/Graphics/OsuColour.cs | 19 +++++++++++++++++++ .../Components/StatusColouredContainer.cs | 3 +-- .../Components/RoomSpecialCategoryPill.cs | 14 +++----------- 3 files changed, 23 insertions(+), 13 deletions(-) diff --git a/osu.Game/Graphics/OsuColour.cs b/osu.Game/Graphics/OsuColour.cs index afedf36cad..7a6a677940 100644 --- a/osu.Game/Graphics/OsuColour.cs +++ b/osu.Game/Graphics/OsuColour.cs @@ -4,6 +4,7 @@ using System; using osu.Framework.Extensions.Color4Extensions; using osu.Game.Beatmaps; +using osu.Game.Online.Rooms; using osu.Game.Overlays; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Scoring; @@ -188,6 +189,24 @@ namespace osu.Game.Graphics } } + /// + /// Retrieves the main accent colour for a . + /// + public Color4? ForRoomCategory(RoomCategory roomCategory) + { + switch (roomCategory) + { + case RoomCategory.Spotlight: + return Green2; + + case RoomCategory.FeaturedArtist: + return Blue2; + + default: + return null; + } + } + /// /// Returns a foreground text colour that is supposed to contrast well with /// the supplied . diff --git a/osu.Game/Screens/OnlinePlay/Components/StatusColouredContainer.cs b/osu.Game/Screens/OnlinePlay/Components/StatusColouredContainer.cs index 760de354dc..a7ea32ee7c 100644 --- a/osu.Game/Screens/OnlinePlay/Components/StatusColouredContainer.cs +++ b/osu.Game/Screens/OnlinePlay/Components/StatusColouredContainer.cs @@ -30,8 +30,7 @@ namespace osu.Game.Screens.OnlinePlay.Components { status.BindValueChanged(s => { - this.FadeColour(category.Value == RoomCategory.Spotlight ? colours.Pink : s.NewValue.GetAppropriateColour(colours) - , transitionDuration); + this.FadeColour(colours.ForRoomCategory(category.Value) ?? s.NewValue.GetAppropriateColour(colours), transitionDuration); }, true); } } diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomSpecialCategoryPill.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomSpecialCategoryPill.cs index 00e4fbf5eb..539af2ebaf 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomSpecialCategoryPill.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomSpecialCategoryPill.cs @@ -7,7 +7,6 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; -using osu.Game.Online.Rooms; using osuTK.Graphics; namespace osu.Game.Screens.OnlinePlay.Lounge.Components @@ -53,16 +52,9 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components { text.Text = c.NewValue.GetLocalisableDescription(); - switch (c.NewValue) - { - case RoomCategory.Spotlight: - pill.Background.Colour = colours.Green2; - break; - - case RoomCategory.FeaturedArtist: - pill.Background.Colour = colours.Blue2; - break; - } + var backgroundColour = colours.ForRoomCategory(Category.Value); + if (backgroundColour != null) + pill.Background.Colour = backgroundColour.Value; }, true); } } From 320b6ca6317139acb9b75df645c4bf4722266c9d Mon Sep 17 00:00:00 2001 From: Jai Sharma Date: Fri, 27 May 2022 16:06:23 +0100 Subject: [PATCH 228/395] Display Announce type channels separately in new chat overlay --- .../Visual/Online/TestSceneChannelList.cs | 19 +++++++++- .../Visual/Online/TestSceneChatOverlayV2.cs | 16 +++++++++ .../Overlays/Chat/ChannelList/ChannelList.cs | 36 +++++++++++++++---- osu.Game/Overlays/Chat/ChatTextBar.cs | 6 ++-- 4 files changed, 66 insertions(+), 11 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneChannelList.cs b/osu.Game.Tests/Visual/Online/TestSceneChannelList.cs index e4bc5645b6..39a4f1a8a1 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneChannelList.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneChannelList.cs @@ -87,7 +87,7 @@ namespace osu.Game.Tests.Visual.Online { leaveText.Text = $"OnRequestLeave: {channel.Name}"; leaveText.FadeOutFromOne(1000, Easing.InQuint); - selected.Value = null; + selected.Value = channelList.ChannelListingChannel; channelList.RemoveChannel(channel); }; @@ -112,6 +112,12 @@ namespace osu.Game.Tests.Visual.Online for (int i = 0; i < 10; i++) channelList.AddChannel(createRandomPrivateChannel()); }); + + AddStep("Add Announce Channels", () => + { + for (int i = 0; i < 2; i++) + channelList.AddChannel(createRandomAnnounceChannel()); + }); } [Test] @@ -170,5 +176,16 @@ namespace osu.Game.Tests.Visual.Online Username = $"test user {id}", }); } + + private Channel createRandomAnnounceChannel() + { + int id = RNG.Next(0, 10000); + return new Channel + { + Name = $"Announce {id}", + Type = ChannelType.Announce, + Id = id, + }; + } } } diff --git a/osu.Game.Tests/Visual/Online/TestSceneChatOverlayV2.cs b/osu.Game.Tests/Visual/Online/TestSceneChatOverlayV2.cs index 0580d20171..ed6e2bb2c0 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneChatOverlayV2.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneChatOverlayV2.cs @@ -452,6 +452,7 @@ namespace osu.Game.Tests.Visual.Online [Test] public void TestKeyboardNextChannel() { + Channel announceChannel = createAnnounceChannel(); Channel pmChannel1 = createPrivateChannel(); Channel pmChannel2 = createPrivateChannel(); @@ -461,6 +462,7 @@ namespace osu.Game.Tests.Visual.Online channelManager.JoinChannel(testChannel2); channelManager.JoinChannel(pmChannel1); channelManager.JoinChannel(pmChannel2); + channelManager.JoinChannel(announceChannel); chatOverlay.Show(); }); @@ -475,6 +477,9 @@ namespace osu.Game.Tests.Visual.Online AddStep("Press document next keys", () => InputManager.Keys(PlatformAction.DocumentNext)); AddAssert("PM Channel 2 displayed", () => channelIsVisible && currentDrawableChannel.Channel == pmChannel2); + AddStep("Press document next keys", () => InputManager.Keys(PlatformAction.DocumentNext)); + AddAssert("Announce channel displayed", () => channelIsVisible && currentDrawableChannel.Channel == announceChannel); + AddStep("Press document next keys", () => InputManager.Keys(PlatformAction.DocumentNext)); AddAssert("Channel 1 displayed", () => channelIsVisible && currentDrawableChannel.Channel == testChannel1); } @@ -539,6 +544,17 @@ namespace osu.Game.Tests.Visual.Online }); } + private Channel createAnnounceChannel() + { + int id = RNG.Next(0, 10000); + return new Channel + { + Name = $"Announce {id}", + Type = ChannelType.Announce, + Id = id, + }; + } + private class TestChatOverlayV2 : ChatOverlayV2 { public bool SlowLoading { get; set; } diff --git a/osu.Game/Overlays/Chat/ChannelList/ChannelList.cs b/osu.Game/Overlays/Chat/ChannelList/ChannelList.cs index ce1ed2b4d7..881491d2af 100644 --- a/osu.Game/Overlays/Chat/ChannelList/ChannelList.cs +++ b/osu.Game/Overlays/Chat/ChannelList/ChannelList.cs @@ -26,13 +26,16 @@ namespace osu.Game.Overlays.Chat.ChannelList public Action? OnRequestSelect; public Action? OnRequestLeave; - public IEnumerable Channels => publicChannelFlow.Channels.Concat(privateChannelFlow.Channels); + public IEnumerable Channels => + announceChannelFlow.Channels.Concat(publicChannelFlow.Channels).Concat(privateChannelFlow.Channels); public readonly ChannelListing.ChannelListingChannel ChannelListingChannel = new ChannelListing.ChannelListingChannel(); private readonly Dictionary channelMap = new Dictionary(); private OsuScrollContainer scroll = null!; + private ChannelListLabel announceChannelLabel = null!; + private ChannelListItemFlow announceChannelFlow = null!; private ChannelListItemFlow publicChannelFlow = null!; private ChannelListItemFlow privateChannelFlow = null!; private ChannelListItem selector = null!; @@ -49,7 +52,6 @@ namespace osu.Game.Overlays.Chat.ChannelList }, scroll = new OsuScrollContainer { - Padding = new MarginPadding { Vertical = 7 }, RelativeSizeAxes = Axes.Both, ScrollbarAnchor = Anchor.TopRight, ScrollDistance = 35f, @@ -60,12 +62,11 @@ namespace osu.Game.Overlays.Chat.ChannelList AutoSizeAxes = Axes.Y, Children = new Drawable[] { + announceChannelLabel = new ChannelListLabel(ChatStrings.ChannelsListTitleANNOUNCE.ToUpper()), + announceChannelFlow = new ChannelListItemFlow(), new ChannelListLabel(ChatStrings.ChannelsListTitlePUBLIC.ToUpper()), publicChannelFlow = new ChannelListItemFlow(), - selector = new ChannelListItem(ChannelListingChannel) - { - Margin = new MarginPadding { Bottom = 10 }, - }, + selector = new ChannelListItem(ChannelListingChannel), new ChannelListLabel(ChatStrings.ChannelsListTitlePM.ToUpper()), privateChannelFlow = new ChannelListItemFlow(), }, @@ -88,6 +89,8 @@ namespace osu.Game.Overlays.Chat.ChannelList ChannelListItemFlow flow = getFlowForChannel(channel); channelMap.Add(channel, item); flow.Add(item); + + updateVisibility(); } public void RemoveChannel(Channel channel) @@ -100,6 +103,8 @@ namespace osu.Game.Overlays.Chat.ChannelList channelMap.Remove(channel); flow.Remove(item); + + updateVisibility(); } public ChannelListItem GetItem(Channel channel) @@ -122,17 +127,34 @@ namespace osu.Game.Overlays.Chat.ChannelList case ChannelType.PM: return privateChannelFlow; + case ChannelType.Announce: + return announceChannelFlow; + default: return publicChannelFlow; } } + private void updateVisibility() + { + if (announceChannelFlow.Channels.Count() == 0) + { + announceChannelLabel.Hide(); + announceChannelFlow.Hide(); + } + else + { + announceChannelLabel.Show(); + announceChannelFlow.Show(); + } + } + private class ChannelListLabel : OsuSpriteText { public ChannelListLabel(LocalisableString label) { Text = label; - Margin = new MarginPadding { Left = 18, Bottom = 5 }; + Margin = new MarginPadding { Left = 18, Bottom = 5, Top = 8 }; Font = OsuFont.Torus.With(size: 12, weight: FontWeight.SemiBold); } } diff --git a/osu.Game/Overlays/Chat/ChatTextBar.cs b/osu.Game/Overlays/Chat/ChatTextBar.cs index 4dc609acd1..5100959eeb 100644 --- a/osu.Game/Overlays/Chat/ChatTextBar.cs +++ b/osu.Game/Overlays/Chat/ChatTextBar.cs @@ -141,8 +141,8 @@ namespace osu.Game.Overlays.Chat switch (newChannel?.Type) { - case ChannelType.Public: - chattingText.Text = ChatStrings.TalkingIn(newChannel.Name); + case null: + chattingText.Text = string.Empty; break; case ChannelType.PM: @@ -150,7 +150,7 @@ namespace osu.Game.Overlays.Chat break; default: - chattingText.Text = string.Empty; + chattingText.Text = ChatStrings.TalkingIn(newChannel.Name); break; } }, true); From e950c8c1d032ac0aaae54cb35baab53a834e0acd Mon Sep 17 00:00:00 2001 From: Jai Sharma Date: Fri, 27 May 2022 19:46:53 +0100 Subject: [PATCH 229/395] Refactor `ChannelList` to use new `ChannelGroup` class for each type of channel --- .../Overlays/Chat/ChannelList/ChannelList.cs | 84 ++++++++++--------- 1 file changed, 43 insertions(+), 41 deletions(-) diff --git a/osu.Game/Overlays/Chat/ChannelList/ChannelList.cs b/osu.Game/Overlays/Chat/ChannelList/ChannelList.cs index 881491d2af..a46ae98dfa 100644 --- a/osu.Game/Overlays/Chat/ChannelList/ChannelList.cs +++ b/osu.Game/Overlays/Chat/ChannelList/ChannelList.cs @@ -26,18 +26,20 @@ namespace osu.Game.Overlays.Chat.ChannelList public Action? OnRequestSelect; public Action? OnRequestLeave; - public IEnumerable Channels => - announceChannelFlow.Channels.Concat(publicChannelFlow.Channels).Concat(privateChannelFlow.Channels); + public IEnumerable Channels => groupFlow.Children.Where(child => child is ChannelGroup) + .Cast() + .SelectMany(channelGroup => channelGroup.ItemFlow) + .Select(item => item.Channel); public readonly ChannelListing.ChannelListingChannel ChannelListingChannel = new ChannelListing.ChannelListingChannel(); private readonly Dictionary channelMap = new Dictionary(); private OsuScrollContainer scroll = null!; - private ChannelListLabel announceChannelLabel = null!; - private ChannelListItemFlow announceChannelFlow = null!; - private ChannelListItemFlow publicChannelFlow = null!; - private ChannelListItemFlow privateChannelFlow = null!; + private FillFlowContainer groupFlow = null!; + private ChannelGroup announceChannelGroup = null!; + private ChannelGroup publicChannelGroup = null!; + private ChannelGroup privateChannelGroup = null!; private ChannelListItem selector = null!; [BackgroundDependencyLoader] @@ -55,20 +57,17 @@ namespace osu.Game.Overlays.Chat.ChannelList RelativeSizeAxes = Axes.Both, ScrollbarAnchor = Anchor.TopRight, ScrollDistance = 35f, - Child = new FillFlowContainer + Child = groupFlow = new FillFlowContainer { Direction = FillDirection.Vertical, RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, Children = new Drawable[] { - announceChannelLabel = new ChannelListLabel(ChatStrings.ChannelsListTitleANNOUNCE.ToUpper()), - announceChannelFlow = new ChannelListItemFlow(), - new ChannelListLabel(ChatStrings.ChannelsListTitlePUBLIC.ToUpper()), - publicChannelFlow = new ChannelListItemFlow(), + announceChannelGroup = new ChannelGroup(ChatStrings.ChannelsListTitleANNOUNCE.ToUpper()), + publicChannelGroup = new ChannelGroup(ChatStrings.ChannelsListTitlePUBLIC.ToUpper()), selector = new ChannelListItem(ChannelListingChannel), - new ChannelListLabel(ChatStrings.ChannelsListTitlePM.ToUpper()), - privateChannelFlow = new ChannelListItemFlow(), + privateChannelGroup = new ChannelGroup(ChatStrings.ChannelsListTitlePM.ToUpper()), }, }, }, @@ -86,7 +85,7 @@ namespace osu.Game.Overlays.Chat.ChannelList item.OnRequestSelect += chan => OnRequestSelect?.Invoke(chan); item.OnRequestLeave += chan => OnRequestLeave?.Invoke(chan); - ChannelListItemFlow flow = getFlowForChannel(channel); + FillFlowContainer flow = getFlowForChannel(channel); channelMap.Add(channel, item); flow.Add(item); @@ -99,7 +98,7 @@ namespace osu.Game.Overlays.Chat.ChannelList return; ChannelListItem item = channelMap[channel]; - ChannelListItemFlow flow = getFlowForChannel(channel); + FillFlowContainer flow = getFlowForChannel(channel); channelMap.Remove(channel); flow.Remove(item); @@ -117,57 +116,60 @@ namespace osu.Game.Overlays.Chat.ChannelList public void ScrollChannelIntoView(Channel channel) => scroll.ScrollIntoView(GetItem(channel)); - private ChannelListItemFlow getFlowForChannel(Channel channel) + private FillFlowContainer getFlowForChannel(Channel channel) { switch (channel.Type) { case ChannelType.Public: - return publicChannelFlow; + return publicChannelGroup.ItemFlow; case ChannelType.PM: - return privateChannelFlow; + return privateChannelGroup.ItemFlow; case ChannelType.Announce: - return announceChannelFlow; + return announceChannelGroup.ItemFlow; default: - return publicChannelFlow; + return publicChannelGroup.ItemFlow; } } private void updateVisibility() { - if (announceChannelFlow.Channels.Count() == 0) - { - announceChannelLabel.Hide(); - announceChannelFlow.Hide(); - } + if (announceChannelGroup.ItemFlow.Children.Count == 0) + announceChannelGroup.Hide(); else - { - announceChannelLabel.Show(); - announceChannelFlow.Show(); - } + announceChannelGroup.Show(); } - private class ChannelListLabel : OsuSpriteText + private class ChannelGroup : FillFlowContainer { - public ChannelListLabel(LocalisableString label) - { - Text = label; - Margin = new MarginPadding { Left = 18, Bottom = 5, Top = 8 }; - Font = OsuFont.Torus.With(size: 12, weight: FontWeight.SemiBold); - } - } + public FillFlowContainer ItemFlow => flow; - private class ChannelListItemFlow : FillFlowContainer - { - public IEnumerable Channels => Children.Select(c => c.Channel); + private readonly FillFlowContainer flow; - public ChannelListItemFlow() + public ChannelGroup(LocalisableString label) { Direction = FillDirection.Vertical; RelativeSizeAxes = Axes.X; AutoSizeAxes = Axes.Y; + Padding = new MarginPadding { Top = 8 }; + + Children = new Drawable[] + { + new OsuSpriteText + { + Text = label, + Margin = new MarginPadding { Left = 18, Bottom = 5 }, + Font = OsuFont.Torus.With(size: 12, weight: FontWeight.SemiBold), + }, + flow = new FillFlowContainer + { + Direction = FillDirection.Vertical, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + }, + }; } } } From 254039b8fe9fa21ae584663b792aa7b81d650a29 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 28 May 2022 04:01:51 +0900 Subject: [PATCH 230/395] Address remaining code quality concerns --- .../Dashboard/CurrentlyPlayingDisplay.cs | 40 +++++++++---------- 1 file changed, 18 insertions(+), 22 deletions(-) diff --git a/osu.Game/Overlays/Dashboard/CurrentlyPlayingDisplay.cs b/osu.Game/Overlays/Dashboard/CurrentlyPlayingDisplay.cs index 3020b37351..0a1cf5524f 100644 --- a/osu.Game/Overlays/Dashboard/CurrentlyPlayingDisplay.cs +++ b/osu.Game/Overlays/Dashboard/CurrentlyPlayingDisplay.cs @@ -18,6 +18,7 @@ using osu.Game.Graphics.UserInterface; using osu.Game.Online.API; using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Spectator; +using osu.Game.Resources.Localisation.Web; using osu.Game.Screens; using osu.Game.Screens.OnlinePlay.Match.Components; using osu.Game.Screens.Play; @@ -28,13 +29,13 @@ namespace osu.Game.Overlays.Dashboard { internal class CurrentlyPlayingDisplay : CompositeDrawable { - private const float searchbox_height = 40f; - private const float container_padding = 10f; + private const float search_textbox_height = 40; + private const float padding = 10; private readonly IBindableList playingUsers = new BindableList(); private SearchContainer userFlow; - private BasicSearchTextBox userFlowSearchTextBox; + private BasicSearchTextBox searchTextBox; [Resolved] private SpectatorClient spectatorClient { get; set; } @@ -50,43 +51,38 @@ namespace osu.Game.Overlays.Dashboard new Box { RelativeSizeAxes = Axes.X, - Height = container_padding * 2 + searchbox_height, + Height = padding * 2 + search_textbox_height, Colour = colourProvider.Background4, }, - new Container { RelativeSizeAxes = Axes.X, - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - Padding = new MarginPadding(container_padding), - - Child = userFlowSearchTextBox = new BasicSearchTextBox + Padding = new MarginPadding(padding), + Child = searchTextBox = new BasicSearchTextBox { RelativeSizeAxes = Axes.X, Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, - Height = searchbox_height, - PlaceholderText = Resources.Localisation.Web.HomeStrings.SearchPlaceholder, + Height = search_textbox_height, + PlaceholderText = HomeStrings.SearchPlaceholder, }, }, - userFlow = new SearchContainer { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, + Spacing = new Vector2(10), Padding = new MarginPadding { - Top = container_padding * 3 + searchbox_height, - Bottom = container_padding, - Right = container_padding, - Left = container_padding, + Top = padding * 3 + search_textbox_height, + Bottom = padding, + Right = padding, + Left = padding, }, - Spacing = new Vector2(10), }, }; - userFlowSearchTextBox.Current.ValueChanged += onSearchBarValueChanged; + searchTextBox.Current.ValueChanged += text => userFlow.SearchTerm = text.NewValue; } [Resolved] @@ -100,8 +96,6 @@ namespace osu.Game.Overlays.Dashboard playingUsers.BindCollectionChanged(onPlayingUsersChanged, true); } - private void onSearchBarValueChanged(ValueChangedEvent change) => userFlow.SearchTerm = userFlowSearchTextBox.Text; - private void onPlayingUsersChanged(object sender, NotifyCollectionChangedEventArgs e) => Schedule(() => { switch (e.Action) @@ -150,7 +144,8 @@ namespace osu.Game.Overlays.Dashboard public class PlayingUserPanel : CompositeDrawable, IFilterable { public readonly APIUser User; - public IEnumerable FilterTerms { get; set; } + + public IEnumerable FilterTerms { get; } [Resolved(canBeNull: true)] private IPerformFromScreenRunner performer { get; set; } @@ -171,6 +166,7 @@ namespace osu.Game.Overlays.Dashboard public PlayingUserPanel(APIUser user) { User = user; + FilterTerms = new LocalisableString[] { User.Username }; AutoSizeAxes = Axes.Both; From 5eb16ff46c604417f4249eb4c7ebe03cd4d74b4a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 28 May 2022 04:02:35 +0900 Subject: [PATCH 231/395] Update framework --- osu.Android.props | 2 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index 96a99cfdd2..ecc929a66d 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -52,7 +52,7 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 1ae8275444..950d4ceae1 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -35,7 +35,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/osu.iOS.props b/osu.iOS.props index 0a3bfa7ae8..bcff0d3c44 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -61,7 +61,7 @@ - + @@ -84,7 +84,7 @@ - + From c5845f307a7cac07a13f39b52ef9ac71fd7e261c Mon Sep 17 00:00:00 2001 From: Jai Sharma Date: Fri, 27 May 2022 20:12:00 +0100 Subject: [PATCH 232/395] Code quality --- osu.Game/Overlays/Chat/ChannelList/ChannelList.cs | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/osu.Game/Overlays/Chat/ChannelList/ChannelList.cs b/osu.Game/Overlays/Chat/ChannelList/ChannelList.cs index a46ae98dfa..0f3d8574c1 100644 --- a/osu.Game/Overlays/Chat/ChannelList/ChannelList.cs +++ b/osu.Game/Overlays/Chat/ChannelList/ChannelList.cs @@ -27,9 +27,9 @@ namespace osu.Game.Overlays.Chat.ChannelList public Action? OnRequestLeave; public IEnumerable Channels => groupFlow.Children.Where(child => child is ChannelGroup) - .Cast() - .SelectMany(channelGroup => channelGroup.ItemFlow) - .Select(item => item.Channel); + .Cast() + .SelectMany(channelGroup => channelGroup.ItemFlow) + .Select(item => item.Channel); public readonly ChannelListing.ChannelListingChannel ChannelListingChannel = new ChannelListing.ChannelListingChannel(); @@ -144,9 +144,7 @@ namespace osu.Game.Overlays.Chat.ChannelList private class ChannelGroup : FillFlowContainer { - public FillFlowContainer ItemFlow => flow; - - private readonly FillFlowContainer flow; + public readonly FillFlowContainer ItemFlow; public ChannelGroup(LocalisableString label) { @@ -163,7 +161,7 @@ namespace osu.Game.Overlays.Chat.ChannelList Margin = new MarginPadding { Left = 18, Bottom = 5 }, Font = OsuFont.Torus.With(size: 12, weight: FontWeight.SemiBold), }, - flow = new FillFlowContainer + ItemFlow = new FillFlowContainer { Direction = FillDirection.Vertical, RelativeSizeAxes = Axes.X, From cd84200ad9486ad9b4f1e62941244750a1dc593b Mon Sep 17 00:00:00 2001 From: sw1tchbl4d3 Date: Fri, 27 May 2022 21:23:37 +0200 Subject: [PATCH 233/395] Add HoldFocus and ReleaseFocusOnCommit attributes to searchbar --- osu.Game/Overlays/Dashboard/CurrentlyPlayingDisplay.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Overlays/Dashboard/CurrentlyPlayingDisplay.cs b/osu.Game/Overlays/Dashboard/CurrentlyPlayingDisplay.cs index 0a1cf5524f..c8c53ec507 100644 --- a/osu.Game/Overlays/Dashboard/CurrentlyPlayingDisplay.cs +++ b/osu.Game/Overlays/Dashboard/CurrentlyPlayingDisplay.cs @@ -64,6 +64,8 @@ namespace osu.Game.Overlays.Dashboard Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, Height = search_textbox_height, + ReleaseFocusOnCommit = false, + HoldFocus = true, PlaceholderText = HomeStrings.SearchPlaceholder, }, }, From 06832a4baf0cb8f6804aa31fcb4d540769be45e4 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 28 May 2022 01:05:07 +0300 Subject: [PATCH 234/395] Fix beatmap badge colours not updated inline with recent changes --- osu.Game/Graphics/OsuColour.cs | 7 +++++-- osu.Game/Overlays/BeatmapSet/FeaturedArtistBeatmapBadge.cs | 2 +- osu.Game/Overlays/BeatmapSet/SpotlightBeatmapBadge.cs | 2 +- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/osu.Game/Graphics/OsuColour.cs b/osu.Game/Graphics/OsuColour.cs index 7a6a677940..7fd94b57f3 100644 --- a/osu.Game/Graphics/OsuColour.cs +++ b/osu.Game/Graphics/OsuColour.cs @@ -197,10 +197,10 @@ namespace osu.Game.Graphics switch (roomCategory) { case RoomCategory.Spotlight: - return Green2; + return SpotlightColour; case RoomCategory.FeaturedArtist: - return Blue2; + return FeaturedArtistColour; default: return null; @@ -379,5 +379,8 @@ namespace osu.Game.Graphics public readonly Color4 ChatBlue = Color4Extensions.FromHex(@"17292e"); public readonly Color4 ContextMenuGray = Color4Extensions.FromHex(@"223034"); + + public Color4 SpotlightColour => Green2; + public Color4 FeaturedArtistColour => Blue2; } } diff --git a/osu.Game/Overlays/BeatmapSet/FeaturedArtistBeatmapBadge.cs b/osu.Game/Overlays/BeatmapSet/FeaturedArtistBeatmapBadge.cs index 4f336d85fc..20ee11c7f6 100644 --- a/osu.Game/Overlays/BeatmapSet/FeaturedArtistBeatmapBadge.cs +++ b/osu.Game/Overlays/BeatmapSet/FeaturedArtistBeatmapBadge.cs @@ -15,7 +15,7 @@ namespace osu.Game.Overlays.BeatmapSet private void load(OsuColour colours) { BadgeText = BeatmapsetsStrings.FeaturedArtistBadgeLabel; - BadgeColour = colours.Blue1; + BadgeColour = colours.FeaturedArtistColour; // todo: add linking support to allow redirecting featured artist badge to corresponding track. } } diff --git a/osu.Game/Overlays/BeatmapSet/SpotlightBeatmapBadge.cs b/osu.Game/Overlays/BeatmapSet/SpotlightBeatmapBadge.cs index 3204f79b21..9c5378a967 100644 --- a/osu.Game/Overlays/BeatmapSet/SpotlightBeatmapBadge.cs +++ b/osu.Game/Overlays/BeatmapSet/SpotlightBeatmapBadge.cs @@ -15,7 +15,7 @@ namespace osu.Game.Overlays.BeatmapSet private void load(OsuColour colours) { BadgeText = BeatmapsetsStrings.SpotlightBadgeLabel; - BadgeColour = colours.Pink1; + BadgeColour = colours.SpotlightColour; // todo: add linking support to allow redirecting spotlight badge to https://osu.ppy.sh/wiki/en/Beatmap_Spotlights. } } From aee3e2a4ed0c66f5e5ec2257d2ac11e9cbbcb950 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 28 May 2022 01:58:17 +0300 Subject: [PATCH 235/395] Add shadow effect to editor's bottom bar --- osu.Game/Screens/Edit/BottomBar.cs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/osu.Game/Screens/Edit/BottomBar.cs b/osu.Game/Screens/Edit/BottomBar.cs index 5994184c11..62caaced89 100644 --- a/osu.Game/Screens/Edit/BottomBar.cs +++ b/osu.Game/Screens/Edit/BottomBar.cs @@ -2,13 +2,16 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; +using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; using osu.Game.Overlays; using osu.Game.Screens.Edit.Components; using osu.Game.Screens.Edit.Components.Timelines.Summary; using osuTK; +using osuTK.Graphics; namespace osu.Game.Screens.Edit { @@ -26,6 +29,14 @@ namespace osu.Game.Screens.Edit Height = 60; + Masking = true; + EdgeEffect = new EdgeEffectParameters + { + Colour = Color4.Black.Opacity(0.2f), + Type = EdgeEffectType.Shadow, + Radius = 10f, + }; + InternalChildren = new Drawable[] { new Box From eec9248cdee0257912d936086147344d66eb2983 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 28 May 2022 02:10:18 +0300 Subject: [PATCH 236/395] Darken background colour in "setup" and "verify" screens --- osu.Game/Screens/Edit/Setup/SetupScreen.cs | 2 +- osu.Game/Screens/Edit/Verify/IssueList.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Edit/Setup/SetupScreen.cs b/osu.Game/Screens/Edit/Setup/SetupScreen.cs index b95aabc1c4..e0fc5f1aff 100644 --- a/osu.Game/Screens/Edit/Setup/SetupScreen.cs +++ b/osu.Game/Screens/Edit/Setup/SetupScreen.cs @@ -41,7 +41,7 @@ namespace osu.Game.Screens.Edit.Setup Add(new Box { - Colour = colourProvider.Background2, + Colour = colourProvider.Background3, RelativeSizeAxes = Axes.Both, }); diff --git a/osu.Game/Screens/Edit/Verify/IssueList.cs b/osu.Game/Screens/Edit/Verify/IssueList.cs index 415acc0e22..84b2609a61 100644 --- a/osu.Game/Screens/Edit/Verify/IssueList.cs +++ b/osu.Game/Screens/Edit/Verify/IssueList.cs @@ -51,7 +51,7 @@ namespace osu.Game.Screens.Edit.Verify { new Box { - Colour = colours.Background2, + Colour = colours.Background3, RelativeSizeAxes = Axes.Both, }, new OsuScrollContainer From e71d90716401b8e2bff5fd8c34a34f0cbee513ec Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 28 May 2022 02:20:04 +0300 Subject: [PATCH 237/395] Update row attribute background colour to fit screen --- osu.Game/Screens/Edit/Timing/RowAttribute.cs | 8 +++++--- .../Edit/Timing/RowAttributes/TimingRowAttribute.cs | 5 ++++- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Edit/Timing/RowAttribute.cs b/osu.Game/Screens/Edit/Timing/RowAttribute.cs index 74d43628e1..46bb62c9e0 100644 --- a/osu.Game/Screens/Edit/Timing/RowAttribute.cs +++ b/osu.Game/Screens/Edit/Timing/RowAttribute.cs @@ -19,6 +19,8 @@ namespace osu.Game.Screens.Edit.Timing private readonly string label; + protected Drawable Background { get; private set; } + protected FillFlowContainer Content { get; private set; } public RowAttribute(ControlPoint point, string label) @@ -41,11 +43,11 @@ namespace osu.Game.Screens.Edit.Timing Masking = true; CornerRadius = 3; - InternalChildren = new Drawable[] + InternalChildren = new[] { - new Box + Background = new Box { - Colour = overlayColours.Background4, + Colour = overlayColours.Background5, RelativeSizeAxes = Axes.Both, }, Content = new FillFlowContainer diff --git a/osu.Game/Screens/Edit/Timing/RowAttributes/TimingRowAttribute.cs b/osu.Game/Screens/Edit/Timing/RowAttributes/TimingRowAttribute.cs index f8ec4aef25..8a07088545 100644 --- a/osu.Game/Screens/Edit/Timing/RowAttributes/TimingRowAttribute.cs +++ b/osu.Game/Screens/Edit/Timing/RowAttributes/TimingRowAttribute.cs @@ -7,6 +7,7 @@ using osu.Framework.Extensions; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Beatmaps.Timing; using osu.Game.Graphics.Sprites; +using osu.Game.Overlays; namespace osu.Game.Screens.Edit.Timing.RowAttributes { @@ -24,10 +25,12 @@ namespace osu.Game.Screens.Edit.Timing.RowAttributes } [BackgroundDependencyLoader] - private void load() + private void load(OverlayColourProvider colourProvider) { Content.Add(text = new AttributeText(Point)); + Background.Colour = colourProvider.Background4; + timeSignature.BindValueChanged(_ => updateText()); beatLength.BindValueChanged(_ => updateText(), true); } From d12e4928e62023e26aa90ce32bbf84f087886e7d Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 28 May 2022 03:09:04 +0300 Subject: [PATCH 238/395] Increase editor verify settings width to give more breathing space --- osu.Game/Screens/Edit/Verify/VerifyScreen.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Verify/VerifyScreen.cs b/osu.Game/Screens/Edit/Verify/VerifyScreen.cs index 9dc5a53907..036d7760b8 100644 --- a/osu.Game/Screens/Edit/Verify/VerifyScreen.cs +++ b/osu.Game/Screens/Edit/Verify/VerifyScreen.cs @@ -41,7 +41,7 @@ namespace osu.Game.Screens.Edit.Verify ColumnDimensions = new[] { new Dimension(), - new Dimension(GridSizeMode.Absolute, 200), + new Dimension(GridSizeMode.Absolute, 225), }, Content = new[] { From e12d188dad2c1bc47e77ae96222b36bb7d735a34 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 28 May 2022 04:05:24 +0300 Subject: [PATCH 239/395] Fix metronome speed not adjusted on different playback rates --- osu.Game/Screens/Edit/Timing/MetronomeDisplay.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/osu.Game/Screens/Edit/Timing/MetronomeDisplay.cs b/osu.Game/Screens/Edit/Timing/MetronomeDisplay.cs index 4dd7a75d4a..57fcff6a4c 100644 --- a/osu.Game/Screens/Edit/Timing/MetronomeDisplay.cs +++ b/osu.Game/Screens/Edit/Timing/MetronomeDisplay.cs @@ -10,6 +10,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; +using osu.Framework.Timing; using osu.Framework.Utils; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Graphics.Containers; @@ -28,6 +29,8 @@ namespace osu.Game.Screens.Edit.Timing private Drawable weight; private Drawable stick; + private IAdjustableClock metronomeClock; + [Resolved] private OverlayColourProvider overlayColourProvider { get; set; } @@ -192,6 +195,8 @@ namespace osu.Game.Screens.Edit.Timing Y = -3, }, }; + + Clock = new FramedClock(metronomeClock = new StopwatchClock(true)); } private double beatLength; @@ -216,6 +221,8 @@ namespace osu.Game.Screens.Edit.Timing if (BeatSyncSource.ControlPoints == null || BeatSyncSource.Clock == null) return; + metronomeClock.Rate = IsBeatSyncedWithTrack ? BeatSyncSource.Clock.Rate : 1; + timingPoint = BeatSyncSource.ControlPoints.TimingPointAt(BeatSyncSource.Clock.CurrentTime); if (beatLength != timingPoint.BeatLength) From 2fac710d4b728aff4b16a173408be1688c5e1739 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 28 May 2022 04:05:33 +0300 Subject: [PATCH 240/395] Add step for adjusting editor clock rate in test scene --- osu.Game/Tests/Visual/EditorClockTestScene.cs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/osu.Game/Tests/Visual/EditorClockTestScene.cs b/osu.Game/Tests/Visual/EditorClockTestScene.cs index 2d3e1960d9..15e4fc4d8f 100644 --- a/osu.Game/Tests/Visual/EditorClockTestScene.cs +++ b/osu.Game/Tests/Visual/EditorClockTestScene.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; +using osu.Framework.Audio; using osu.Framework.Bindables; using osu.Framework.Input.Events; using osu.Game.Beatmaps; @@ -24,6 +25,8 @@ namespace osu.Game.Tests.Visual [Cached] protected new readonly EditorClock Clock; + private readonly Bindable frequencyAdjustment = new BindableDouble(1); + protected virtual bool ScrollUsingMouseWheel => true; protected EditorClockTestScene() @@ -44,14 +47,21 @@ namespace osu.Game.Tests.Visual protected override void LoadComplete() { base.LoadComplete(); + Beatmap.BindValueChanged(beatmapChanged, true); + + AddSliderStep("editor clock rate", 0.0, 2.0, 1.0, v => frequencyAdjustment.Value = v); } private void beatmapChanged(ValueChangedEvent e) { + e.OldValue?.Track.RemoveAdjustment(AdjustableProperty.Frequency, frequencyAdjustment); + Clock.Beatmap = e.NewValue.Beatmap; Clock.ChangeSource(e.NewValue.Track); Clock.ProcessFrame(); + + e.NewValue.Track.AddAdjustment(AdjustableProperty.Frequency, frequencyAdjustment); } protected override void Update() From 93a8092da6504019b871ba4f219e8b9634715b28 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 28 May 2022 10:51:39 +0900 Subject: [PATCH 241/395] Increase usable width slightly further --- osu.Game/Screens/Edit/Verify/VerifyScreen.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Verify/VerifyScreen.cs b/osu.Game/Screens/Edit/Verify/VerifyScreen.cs index 036d7760b8..56e16bb746 100644 --- a/osu.Game/Screens/Edit/Verify/VerifyScreen.cs +++ b/osu.Game/Screens/Edit/Verify/VerifyScreen.cs @@ -41,7 +41,7 @@ namespace osu.Game.Screens.Edit.Verify ColumnDimensions = new[] { new Dimension(), - new Dimension(GridSizeMode.Absolute, 225), + new Dimension(GridSizeMode.Absolute, 250), }, Content = new[] { From ede3ab9dc00692559786b6e5691e07d8af3c12ee Mon Sep 17 00:00:00 2001 From: sw1tchbl4d3 Date: Sat, 28 May 2022 12:04:25 +0200 Subject: [PATCH 242/395] Add OnFocus handler to CurrentlyPlayingDisplay --- osu.Game/Overlays/Dashboard/CurrentlyPlayingDisplay.cs | 8 ++++++++ osu.Game/Overlays/DashboardOverlay.cs | 2 ++ 2 files changed, 10 insertions(+) diff --git a/osu.Game/Overlays/Dashboard/CurrentlyPlayingDisplay.cs b/osu.Game/Overlays/Dashboard/CurrentlyPlayingDisplay.cs index c8c53ec507..23f67a06cb 100644 --- a/osu.Game/Overlays/Dashboard/CurrentlyPlayingDisplay.cs +++ b/osu.Game/Overlays/Dashboard/CurrentlyPlayingDisplay.cs @@ -11,6 +11,7 @@ using osu.Framework.Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; +using osu.Framework.Input.Events; using osu.Framework.Localisation; using osu.Framework.Screens; using osu.Game.Database; @@ -98,6 +99,13 @@ namespace osu.Game.Overlays.Dashboard playingUsers.BindCollectionChanged(onPlayingUsersChanged, true); } + protected override void OnFocus(FocusEvent e) + { + base.OnFocus(e); + + searchTextBox.TakeFocus(); + } + private void onPlayingUsersChanged(object sender, NotifyCollectionChangedEventArgs e) => Schedule(() => { switch (e.Action) diff --git a/osu.Game/Overlays/DashboardOverlay.cs b/osu.Game/Overlays/DashboardOverlay.cs index 83ad8faf1c..79d972bdcc 100644 --- a/osu.Game/Overlays/DashboardOverlay.cs +++ b/osu.Game/Overlays/DashboardOverlay.cs @@ -16,6 +16,8 @@ namespace osu.Game.Overlays protected override DashboardOverlayHeader CreateHeader() => new DashboardOverlayHeader(); + public override bool AcceptsFocus => false; + protected override void CreateDisplayToLoad(DashboardOverlayTabs tab) { switch (tab) From e18fec3d896d8e0325614a65b4dc5e2f50af5b92 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 28 May 2022 13:59:10 +0200 Subject: [PATCH 243/395] Fix unobserved exceptions due to unimplemented beatmap converter `TestSceneStatisticsPanel` intends to check the operation of statistics panels using dummy ruleset classes. However, `StatisticsPanel` relies on being able to retrieve the playable beatmap, which requires a converter. One was not provided by the dummy rulesets, therefore the retrieval would fail with an unobserved exception. To fix, add a barebones converter implementation that is enough for the test to pass. --- .../Ranking/TestSceneStatisticsPanel.cs | 25 ++++++++++++++++--- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneStatisticsPanel.cs b/osu.Game.Tests/Visual/Ranking/TestSceneStatisticsPanel.cs index 35281a85eb..1efe6d7380 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneStatisticsPanel.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneStatisticsPanel.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Threading; using NUnit.Framework; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -13,6 +14,7 @@ using osu.Game.Graphics.Sprites; using osu.Game.Rulesets; using osu.Game.Rulesets.Difficulty; using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Objects; using osu.Game.Scoring; using osu.Game.Screens.Ranking.Statistics; using osu.Game.Rulesets.Osu.Objects; @@ -114,10 +116,7 @@ namespace osu.Game.Tests.Visual.Ranking throw new NotImplementedException(); } - public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) - { - throw new NotImplementedException(); - } + public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new TestBeatmapConverter(beatmap); public override DifficultyCalculator CreateDifficultyCalculator(IWorkingBeatmap beatmap) { @@ -151,6 +150,24 @@ namespace osu.Game.Tests.Visual.Ranking } } }; + + private class TestBeatmapConverter : IBeatmapConverter + { +#pragma warning disable CS0067 // The event is never used + public event Action> ObjectConverted; +#pragma warning restore CS0067 + + public IBeatmap Beatmap { get; } + + public TestBeatmapConverter(IBeatmap beatmap) + { + Beatmap = beatmap; + } + + public bool CanConvert() => true; + + public IBeatmap Convert(CancellationToken cancellationToken = default) => Beatmap.Clone(); + } } private class TestRulesetAllStatsRequireHitEvents : TestRuleset From 1641918c515a49530f6c5ce921b40c52b2418463 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 28 May 2022 14:25:59 +0200 Subject: [PATCH 244/395] Revert "Avoid throwing unobserved exception when `PerformancePointsCounter` requests timed attributes" This reverts commit 19b655d75bd589f25fa76057fe3e66ec5d98eb96. --- osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs b/osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs index 019a9f9730..bdc98e53f9 100644 --- a/osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs +++ b/osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading; +using System.Threading.Tasks; using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Audio.Track; @@ -80,16 +81,13 @@ namespace osu.Game.Screens.Play.HUD difficultyCache.GetTimedDifficultyAttributesAsync(gameplayWorkingBeatmap, gameplayState.Ruleset, clonedMods, loadCancellationSource.Token) .ContinueWith(task => Schedule(() => { - if (task.Exception != null) - return; - timedAttributes = task.GetResultSafely(); IsValid = true; if (lastJudgement != null) onJudgementChanged(lastJudgement); - })); + }), TaskContinuationOptions.OnlyOnRanToCompletion); } } From dcf3d7695484a7bc92b1892a5cf66af5ea0ba208 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 28 May 2022 13:33:09 +0200 Subject: [PATCH 245/395] Fix unobserved exceptions due to using unconverted beatmap `TestSceneBeatmapSkinFallbacks` was locally caching a `GameplayState` instance to fulfill dependencies of a `SkinnableTargetComponentsContainer`. However, it was doing so using `new TestBeatmap()`, which is a raw decoded beatmap that hasn't been converted to any ruleset yet, which was causing failures in `BeatmapDifficultyCache.GetTimedDifficultyAttributesAsync()` as that method is expecting to receive a post-conversion, ready-for-gameplay beatmap. Resolve by proxying forward dependency instances from the already-known-to-be-working `actualComponentsContainer`. --- .../Gameplay/TestSceneBeatmapSkinFallbacks.cs | 24 +++++++------------ 1 file changed, 9 insertions(+), 15 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs index 53364b6d89..e9aa85f4ce 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs @@ -6,7 +6,6 @@ using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Audio; -using osu.Framework.Graphics.Containers; using osu.Framework.Lists; using osu.Framework.Testing; using osu.Framework.Timing; @@ -22,7 +21,6 @@ using osu.Game.Screens.Play; using osu.Game.Screens.Play.HUD; using osu.Game.Skinning; using osu.Game.Storyboards; -using osu.Game.Tests.Beatmaps; namespace osu.Game.Tests.Visual.Gameplay { @@ -33,18 +31,6 @@ namespace osu.Game.Tests.Visual.Gameplay [Resolved] private SkinManager skinManager { get; set; } - [Cached] - private ScoreProcessor scoreProcessor = new ScoreProcessor(new OsuRuleset()); - - [Cached(typeof(HealthProcessor))] - private HealthProcessor healthProcessor = new DrainingHealthProcessor(0); - - [Cached] - private GameplayState gameplayState = new GameplayState(new TestBeatmap(new OsuRuleset().RulesetInfo), new OsuRuleset()); - - [Cached] - private readonly GameplayClock gameplayClock = new GameplayClock(new FramedClock()); - protected override bool HasCustomSteps => true; [Test] @@ -81,11 +67,19 @@ namespace osu.Game.Tests.Visual.Gameplay if (expectedComponentsContainer == null) return false; - var expectedComponentsAdjustmentContainer = new Container + var expectedComponentsAdjustmentContainer = new DependencyProvidingContainer { Position = actualComponentsContainer.Parent.ToSpaceOfOtherDrawable(actualComponentsContainer.DrawPosition, Content), Size = actualComponentsContainer.DrawSize, Child = expectedComponentsContainer, + // proxy the same required dependencies that `actualComponentsContainer` is using. + CachedDependencies = new (Type, object)[] + { + (typeof(ScoreProcessor), actualComponentsContainer.Dependencies.Get()), + (typeof(HealthProcessor), actualComponentsContainer.Dependencies.Get()), + (typeof(GameplayState), actualComponentsContainer.Dependencies.Get()), + (typeof(GameplayClock), actualComponentsContainer.Dependencies.Get()) + }, }; Add(expectedComponentsAdjustmentContainer); From a8e1c5ba87f53dbe2a30bd81bad67f029dc02234 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 28 May 2022 14:55:57 +0200 Subject: [PATCH 246/395] Fix remaining cases of incorrect `GameplayState` construction in tests Manual attempts to initialise replaced by a new `TestGameplayState` helper for ease of use. --- .../TestSceneGameplayCursor.cs | 4 +-- .../Visual/Gameplay/TestSceneHUDOverlay.cs | 4 +-- .../Gameplay/TestSceneReplayRecorder.cs | 6 ++-- .../TestSceneSkinEditorMultipleSkins.cs | 4 +-- .../Gameplay/TestSceneSkinnableHUDOverlay.cs | 4 +-- .../Visual/Gameplay/TestSceneSpectator.cs | 9 ++++-- .../Gameplay/TestSceneSpectatorPlayback.cs | 4 +-- osu.Game/Tests/Gameplay/TestGameplayState.cs | 32 +++++++++++++++++++ 8 files changed, 50 insertions(+), 17 deletions(-) create mode 100644 osu.Game/Tests/Gameplay/TestGameplayState.cs diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneGameplayCursor.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneGameplayCursor.cs index a36f07ff7b..496d495b43 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneGameplayCursor.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneGameplayCursor.cs @@ -17,11 +17,11 @@ using osu.Framework.Testing.Input; using osu.Framework.Utils; using osu.Game.Audio; using osu.Game.Configuration; -using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu.Skinning; using osu.Game.Rulesets.Osu.UI.Cursor; using osu.Game.Screens.Play; using osu.Game.Skinning; +using osu.Game.Tests.Gameplay; using osuTK; namespace osu.Game.Rulesets.Osu.Tests @@ -42,7 +42,7 @@ namespace osu.Game.Rulesets.Osu.Tests public TestSceneGameplayCursor() { var ruleset = new OsuRuleset(); - gameplayState = new GameplayState(CreateBeatmap(ruleset.RulesetInfo), ruleset, Array.Empty()); + gameplayState = TestGameplayState.Create(ruleset); AddStep("change background colour", () => { diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs index 2d12645811..83c557ee51 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs @@ -15,7 +15,7 @@ using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Scoring; using osu.Game.Screens.Play; using osu.Game.Skinning; -using osu.Game.Tests.Beatmaps; +using osu.Game.Tests.Gameplay; using osuTK.Input; namespace osu.Game.Tests.Visual.Gameplay @@ -33,7 +33,7 @@ namespace osu.Game.Tests.Visual.Gameplay private HealthProcessor healthProcessor = new DrainingHealthProcessor(0); [Cached] - private GameplayState gameplayState = new GameplayState(new TestBeatmap(new OsuRuleset().RulesetInfo), new OsuRuleset()); + private GameplayState gameplayState = TestGameplayState.Create(new OsuRuleset()); [Cached] private readonly GameplayClock gameplayClock = new GameplayClock(new FramedClock()); diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecorder.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecorder.cs index 81763564fa..8362739d3b 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecorder.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecorder.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.Collections.Generic; using System.Linq; using NUnit.Framework; @@ -14,16 +13,15 @@ using osu.Framework.Input.Events; using osu.Framework.Input.StateChanges; using osu.Framework.Testing; using osu.Framework.Threading; -using osu.Game.Beatmaps; using osu.Game.Graphics.Sprites; using osu.Game.Replays; using osu.Game.Rulesets; -using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Replays; using osu.Game.Rulesets.UI; using osu.Game.Scoring; using osu.Game.Screens.Play; +using osu.Game.Tests.Gameplay; using osu.Game.Tests.Mods; using osuTK; using osuTK.Graphics; @@ -41,7 +39,7 @@ namespace osu.Game.Tests.Visual.Gameplay private TestReplayRecorder recorder; [Cached] - private GameplayState gameplayState = new GameplayState(new Beatmap(), new OsuRuleset(), Array.Empty()); + private GameplayState gameplayState = TestGameplayState.Create(new OsuRuleset()); [SetUpSteps] public void SetUpSteps() diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditorMultipleSkins.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditorMultipleSkins.cs index 8150252d45..5f838b8813 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditorMultipleSkins.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditorMultipleSkins.cs @@ -11,7 +11,7 @@ using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Scoring; using osu.Game.Screens.Play; using osu.Game.Skinning.Editor; -using osu.Game.Tests.Beatmaps; +using osu.Game.Tests.Gameplay; using osuTK.Input; namespace osu.Game.Tests.Visual.Gameplay @@ -25,7 +25,7 @@ namespace osu.Game.Tests.Visual.Gameplay private HealthProcessor healthProcessor = new DrainingHealthProcessor(0); [Cached] - private GameplayState gameplayState = new GameplayState(new TestBeatmap(new OsuRuleset().RulesetInfo), new OsuRuleset()); + private GameplayState gameplayState = TestGameplayState.Create(new OsuRuleset()); [Cached] private readonly GameplayClock gameplayClock = new GameplayClock(new FramedClock()); diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHUDOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHUDOverlay.cs index ac5e408d90..5f2d9ee9e8 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHUDOverlay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHUDOverlay.cs @@ -16,7 +16,7 @@ using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Scoring; using osu.Game.Screens.Play; -using osu.Game.Tests.Beatmaps; +using osu.Game.Tests.Gameplay; using osuTK.Input; namespace osu.Game.Tests.Visual.Gameplay @@ -32,7 +32,7 @@ namespace osu.Game.Tests.Visual.Gameplay private HealthProcessor healthProcessor = new DrainingHealthProcessor(0); [Cached] - private GameplayState gameplayState = new GameplayState(new TestBeatmap(new OsuRuleset().RulesetInfo), new OsuRuleset()); + private GameplayState gameplayState = TestGameplayState.Create(new OsuRuleset()); [Cached] private readonly GameplayClock gameplayClock = new GameplayClock(new FramedClock()); diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs index 8b420cebc8..b5cdd61ee5 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs @@ -18,8 +18,8 @@ using osu.Game.Rulesets.UI; using osu.Game.Scoring; using osu.Game.Screens; using osu.Game.Screens.Play; -using osu.Game.Tests.Beatmaps; using osu.Game.Tests.Beatmaps.IO; +using osu.Game.Tests.Gameplay; using osu.Game.Tests.Visual.Multiplayer; using osu.Game.Tests.Visual.Spectator; using osuTK; @@ -259,12 +259,15 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestFinalFramesPurgedBeforeEndingPlay() { - AddStep("begin playing", () => spectatorClient.BeginPlaying(new GameplayState(new TestBeatmap(new OsuRuleset().RulesetInfo), new OsuRuleset()), new Score())); + AddStep("begin playing", () => spectatorClient.BeginPlaying(TestGameplayState.Create(new OsuRuleset()), new Score())); AddStep("send frames and finish play", () => { spectatorClient.HandleFrame(new OsuReplayFrame(1000, Vector2.Zero)); - spectatorClient.EndPlaying(new GameplayState(new TestBeatmap(new OsuRuleset().RulesetInfo), new OsuRuleset()) { HasPassed = true }); + + var completedGameplayState = TestGameplayState.Create(new OsuRuleset()); + completedGameplayState.HasPassed = true; + spectatorClient.EndPlaying(completedGameplayState); }); // We can't access API because we're an "online" test. diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs index f8748922cf..2d2e05c4c9 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs @@ -20,13 +20,13 @@ using osu.Game.Online.Spectator; using osu.Game.Replays; using osu.Game.Replays.Legacy; using osu.Game.Rulesets; -using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Replays; using osu.Game.Rulesets.Replays.Types; using osu.Game.Rulesets.UI; using osu.Game.Scoring; using osu.Game.Screens.Play; +using osu.Game.Tests.Gameplay; using osu.Game.Tests.Mods; using osu.Game.Tests.Visual.Spectator; using osuTK; @@ -65,7 +65,7 @@ namespace osu.Game.Tests.Visual.Gameplay CachedDependencies = new[] { (typeof(SpectatorClient), (object)(spectatorClient = new TestSpectatorClient())), - (typeof(GameplayState), new GameplayState(new Beatmap(), new OsuRuleset(), Array.Empty())) + (typeof(GameplayState), TestGameplayState.Create(new OsuRuleset())) }, Children = new Drawable[] { diff --git a/osu.Game/Tests/Gameplay/TestGameplayState.cs b/osu.Game/Tests/Gameplay/TestGameplayState.cs new file mode 100644 index 0000000000..0d00f52d15 --- /dev/null +++ b/osu.Game/Tests/Gameplay/TestGameplayState.cs @@ -0,0 +1,32 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +#nullable enable + +using System.Collections.Generic; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Mods; +using osu.Game.Scoring; +using osu.Game.Screens.Play; +using osu.Game.Tests.Beatmaps; + +namespace osu.Game.Tests.Gameplay +{ + /// + /// Static class providing a convenience method to retrieve a correctly-initialised instance in testing scenarios. + /// + public static class TestGameplayState + { + /// + /// Creates a correctly-initialised instance for use in testing. + /// + public static GameplayState Create(Ruleset ruleset, IReadOnlyList? mods = null, Score? score = null) + { + var beatmap = new TestBeatmap(ruleset.RulesetInfo); + var workingBeatmap = new TestWorkingBeatmap(beatmap); + var playableBeatmap = workingBeatmap.GetPlayableBeatmap(ruleset.RulesetInfo, mods); + + return new GameplayState(playableBeatmap, ruleset, mods, score); + } + } +} From 0349d92f8b0b6802423efc5304df52f6db5af81c Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 28 May 2022 17:45:00 +0300 Subject: [PATCH 247/395] Add failing test case --- .../Visual/Editing/TestSceneTimelineZoom.cs | 48 +++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 osu.Game.Tests/Visual/Editing/TestSceneTimelineZoom.cs diff --git a/osu.Game.Tests/Visual/Editing/TestSceneTimelineZoom.cs b/osu.Game.Tests/Visual/Editing/TestSceneTimelineZoom.cs new file mode 100644 index 0000000000..10f1f2fceb --- /dev/null +++ b/osu.Game.Tests/Visual/Editing/TestSceneTimelineZoom.cs @@ -0,0 +1,48 @@ +// 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.Graphics; +using osu.Framework.Utils; + +namespace osu.Game.Tests.Visual.Editing +{ + public class TestSceneTimelineZoom : TimelineTestScene + { + public override Drawable CreateTestComponent() => Empty(); + + [Test] + public void TestVisibleRangeViaZoom() + { + double initialVisibleRange = 0; + + AddStep("reset zoom", () => TimelineArea.Timeline.Zoom = 100); + AddStep("get initial range", () => initialVisibleRange = TimelineArea.Timeline.VisibleRange); + + AddStep("scale zoom", () => TimelineArea.Timeline.Zoom = 200); + AddAssert("range halved", () => Precision.AlmostEquals(TimelineArea.Timeline.VisibleRange, initialVisibleRange / 2, 1)); + AddStep("descale zoom", () => TimelineArea.Timeline.Zoom = 50); + AddAssert("range doubled", () => Precision.AlmostEquals(TimelineArea.Timeline.VisibleRange, initialVisibleRange * 2, 1)); + + AddStep("restore zoom", () => TimelineArea.Timeline.Zoom = 100); + AddAssert("range restored", () => Precision.AlmostEquals(TimelineArea.Timeline.VisibleRange, initialVisibleRange, 1)); + } + + [Test] + public void TestVisibleRangeViaTimelineSize() + { + double initialVisibleRange = 0; + + AddStep("reset timeline size", () => TimelineArea.Timeline.Width = 1); + AddStep("get initial range", () => initialVisibleRange = TimelineArea.Timeline.VisibleRange); + + AddStep("scale timeline size", () => TimelineArea.Timeline.Width = 2); + AddAssert("range doubled", () => TimelineArea.Timeline.VisibleRange == initialVisibleRange * 2); + AddStep("descale timeline size", () => TimelineArea.Timeline.Width = 0.5f); + AddAssert("range halved", () => TimelineArea.Timeline.VisibleRange == initialVisibleRange / 2); + + AddStep("restore timeline size", () => TimelineArea.Timeline.Width = 1); + AddAssert("range restored", () => TimelineArea.Timeline.VisibleRange == initialVisibleRange); + } + } +} From 02baf9a97abe0fb6e94af53b83708a15a508d437 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 28 May 2022 17:45:33 +0300 Subject: [PATCH 248/395] Fix timeline objects disappearing prematurely on wide-screens --- osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs index 6812bbb72d..2a6e8f5453 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs @@ -302,7 +302,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline /// /// The total amount of time visible on the timeline. /// - public double VisibleRange => track.Length / Zoom; + public double VisibleRange => (DisplayableContent / Content.DrawWidth) * track.Length; public SnapResult FindSnappedPositionAndTime(Vector2 screenSpacePosition, SnapType snapType = SnapType.All) => new SnapResult(screenSpacePosition, beatSnapProvider.SnapTime(getTimeFromPosition(Content.ToLocalSpace(screenSpacePosition)))); From c7570fbce516ad0b35def7d696c885fd80f347f1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 29 May 2022 15:15:03 +0900 Subject: [PATCH 249/395] Fix potential `ObjectDisposedException` on realm notification in `SkinSection` --- osu.Game/Overlays/Settings/Sections/SkinSection.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/Settings/Sections/SkinSection.cs b/osu.Game/Overlays/Settings/Sections/SkinSection.cs index a34776ddf0..264a05b4a7 100644 --- a/osu.Game/Overlays/Settings/Sections/SkinSection.cs +++ b/osu.Game/Overlays/Settings/Sections/SkinSection.cs @@ -111,7 +111,7 @@ namespace osu.Game.Overlays.Settings.Sections configBindable.Value = skin.NewValue.ID.ToString(); } - private void skinsChanged(IRealmCollection sender, ChangeSet changes, Exception error) + private void skinsChanged(IRealmCollection sender, ChangeSet changes, Exception error) => Schedule(() => { // This can only mean that realm is recycling, else we would see the protected skins. // Because we are using `Live<>` in this class, we don't need to worry about this scenario too much. @@ -130,7 +130,7 @@ namespace osu.Game.Overlays.Settings.Sections skinDropdown.Items = dropdownItems; updateSelectedSkinFromConfig(); - } + }); private void updateSelectedSkinFromConfig() { From 12d5b355e4d0385a0e0512adfc9a3d602c27c452 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 29 May 2022 15:22:23 +0300 Subject: [PATCH 250/395] Add failing test coverage --- .../TestSceneTimelineHitObjectBlueprint.cs | 24 +++++++++++++ .../Visual/Editing/TimelineTestScene.cs | 35 +++++++++++-------- 2 files changed, 44 insertions(+), 15 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneTimelineHitObjectBlueprint.cs b/osu.Game.Tests/Visual/Editing/TestSceneTimelineHitObjectBlueprint.cs index e6fad33a51..4bd90a0730 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneTimelineHitObjectBlueprint.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneTimelineHitObjectBlueprint.cs @@ -4,7 +4,9 @@ using System.Linq; using NUnit.Framework; using osu.Framework.Graphics; +using osu.Framework.Graphics.UserInterface; using osu.Framework.Testing; +using osu.Game.Graphics.UserInterface; using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Screens.Edit.Compose.Components.Timeline; @@ -18,6 +20,28 @@ namespace osu.Game.Tests.Visual.Editing { public override Drawable CreateTestComponent() => new TimelineBlueprintContainer(Composer); + [Test] + public void TestContextMenu() + { + TimelineHitObjectBlueprint blueprint = null; + + AddStep("add object", () => + { + EditorBeatmap.Clear(); + EditorBeatmap.Add(new HitCircle { StartTime = 3000 }); + }); + + AddStep("click object", () => + { + blueprint = this.ChildrenOfType().Single(); + InputManager.MoveMouseTo(blueprint); + InputManager.Click(MouseButton.Left); + }); + + AddStep("right click", () => InputManager.Click(MouseButton.Right)); + AddAssert("context menu open", () => this.ChildrenOfType().SingleOrDefault()?.State == MenuState.Open); + } + [Test] public void TestDisallowZeroDurationObjects() { diff --git a/osu.Game.Tests/Visual/Editing/TimelineTestScene.cs b/osu.Game.Tests/Visual/Editing/TimelineTestScene.cs index 4aed445d9d..93bfb288d2 100644 --- a/osu.Game.Tests/Visual/Editing/TimelineTestScene.cs +++ b/osu.Game.Tests/Visual/Editing/TimelineTestScene.cs @@ -8,6 +8,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Game.Beatmaps; +using osu.Game.Graphics.Cursor; using osu.Game.Graphics.UserInterface; using osu.Game.Rulesets.Edit; using osu.Game.Screens.Edit; @@ -38,25 +39,29 @@ namespace osu.Game.Tests.Visual.Editing Composer = playable.BeatmapInfo.Ruleset.CreateInstance().CreateHitObjectComposer().With(d => d.Alpha = 0); - AddRange(new Drawable[] + Add(new OsuContextMenuContainer { - EditorBeatmap, - Composer, - new FillFlowContainer + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] { - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Vertical, - Spacing = new Vector2(0, 5), - Children = new Drawable[] + EditorBeatmap, + Composer, + new FillFlowContainer { - new StartStopButton(), - new AudioVisualiser(), + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, + Spacing = new Vector2(0, 5), + Children = new Drawable[] + { + new StartStopButton(), + new AudioVisualiser(), + } + }, + TimelineArea = new TimelineArea(CreateTestComponent()) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, } - }, - TimelineArea = new TimelineArea(CreateTestComponent()) - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, } }); } From 9cc5df9b133450e987afce2187b01e73f0ba8c03 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 29 May 2022 14:56:51 +0300 Subject: [PATCH 251/395] Fix context menu no longer open on hitobjects in timeline --- osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs index 6812bbb72d..89e9fb2404 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs @@ -16,6 +16,7 @@ using osu.Game.Configuration; using osu.Game.Graphics; using osu.Game.Rulesets.Edit; using osuTK; +using osuTK.Input; namespace osu.Game.Screens.Edit.Compose.Components.Timeline { @@ -273,7 +274,8 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline if (base.OnMouseDown(e)) beginUserDrag(); - return true; + // handling right button as well breaks context menus inside the timeline, only handle left button for now. + return e.Button == MouseButton.Left; } protected override void OnMouseUp(MouseUpEvent e) From 7c97719db1fa10a03ec3e5769b8c8cff4e67f4d6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 29 May 2022 21:28:09 +0900 Subject: [PATCH 252/395] Move `Schedule` to only drawable pieces --- osu.Game/Overlays/Settings/Sections/SkinSection.cs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/osu.Game/Overlays/Settings/Sections/SkinSection.cs b/osu.Game/Overlays/Settings/Sections/SkinSection.cs index 264a05b4a7..a87e65b735 100644 --- a/osu.Game/Overlays/Settings/Sections/SkinSection.cs +++ b/osu.Game/Overlays/Settings/Sections/SkinSection.cs @@ -111,7 +111,7 @@ namespace osu.Game.Overlays.Settings.Sections configBindable.Value = skin.NewValue.ID.ToString(); } - private void skinsChanged(IRealmCollection sender, ChangeSet changes, Exception error) => Schedule(() => + private void skinsChanged(IRealmCollection sender, ChangeSet changes, Exception error) { // This can only mean that realm is recycling, else we would see the protected skins. // Because we are using `Live<>` in this class, we don't need to worry about this scenario too much. @@ -127,10 +127,13 @@ namespace osu.Game.Overlays.Settings.Sections dropdownItems.Add(skin.ToLive(realm)); dropdownItems.Insert(protectedCount, random_skin_info); - skinDropdown.Items = dropdownItems; + Schedule(() => + { + skinDropdown.Items = dropdownItems; - updateSelectedSkinFromConfig(); - }); + updateSelectedSkinFromConfig(); + }); + } private void updateSelectedSkinFromConfig() { From 70c8a439619910541cf4748d165efc3704103e05 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 29 May 2022 21:31:20 +0900 Subject: [PATCH 253/395] Update framework --- osu.Android.props | 2 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index ecc929a66d..c28085557e 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -52,7 +52,7 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 950d4ceae1..79cfd7c917 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -35,7 +35,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/osu.iOS.props b/osu.iOS.props index bcff0d3c44..b1ba64beba 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -61,7 +61,7 @@ - + @@ -84,7 +84,7 @@ - + From c078c06902014195534d24991d8e7168ddd4badc Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 29 May 2022 22:55:53 +0900 Subject: [PATCH 254/395] Fix unsafe config fetching --- osu.Game.Tests/Visual/Online/TestSceneChatOverlayV2.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneChatOverlayV2.cs b/osu.Game.Tests/Visual/Online/TestSceneChatOverlayV2.cs index 0580d20171..4127b5f19b 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneChatOverlayV2.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneChatOverlayV2.cs @@ -150,10 +150,14 @@ namespace osu.Game.Tests.Visual.Online public void TestChatHeight() { BindableFloat configChatHeight = new BindableFloat(); - config.BindWith(OsuSetting.ChatDisplayHeight, configChatHeight); + float newHeight = 0; - AddStep("Reset config chat height", () => configChatHeight.SetDefault()); + AddStep("Reset config chat height", () => + { + config.BindWith(OsuSetting.ChatDisplayHeight, configChatHeight); + configChatHeight.SetDefault(); + }); AddStep("Show overlay", () => chatOverlay.Show()); AddAssert("Overlay uses config height", () => chatOverlay.Height == configChatHeight.Default); AddStep("Click top bar", () => From 18f75d8c58e3aeb21011d1392ab8899d35ec9406 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 29 May 2022 16:06:01 +0200 Subject: [PATCH 255/395] Remove redundant initialiser --- .../Visual/Editing/TestSceneTimelineHitObjectBlueprint.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneTimelineHitObjectBlueprint.cs b/osu.Game.Tests/Visual/Editing/TestSceneTimelineHitObjectBlueprint.cs index 4bd90a0730..d55852ec43 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneTimelineHitObjectBlueprint.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneTimelineHitObjectBlueprint.cs @@ -23,7 +23,7 @@ namespace osu.Game.Tests.Visual.Editing [Test] public void TestContextMenu() { - TimelineHitObjectBlueprint blueprint = null; + TimelineHitObjectBlueprint blueprint; AddStep("add object", () => { From caee9e67b6687f94c1fa4e40b115c67a61a442b0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 29 May 2022 23:03:34 +0900 Subject: [PATCH 256/395] Centralise `DrawableChannel` checks and account for async load delays --- .../Visual/Online/TestSceneChatOverlayV2.cs | 51 ++++++++++--------- 1 file changed, 28 insertions(+), 23 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneChatOverlayV2.cs b/osu.Game.Tests/Visual/Online/TestSceneChatOverlayV2.cs index 4127b5f19b..b333eee21b 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneChatOverlayV2.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneChatOverlayV2.cs @@ -134,7 +134,7 @@ namespace osu.Game.Tests.Visual.Online channelManager.CurrentChannel.Value = joinedChannel; }); AddAssert("Overlay is visible", () => chatOverlay.State.Value == Visibility.Visible); - AddUntilStep("Channel is visible", () => channelIsVisible && currentDrawableChannel.Channel == testChannel1); + waitForChannel1Visible(); } [Test] @@ -181,7 +181,7 @@ namespace osu.Game.Tests.Visual.Online AddAssert("Listing is visible", () => listingIsVisible); AddStep("Join channel 1", () => channelManager.JoinChannel(testChannel1)); AddStep("Select channel 1", () => clickDrawable(getChannelListItem(testChannel1))); - AddUntilStep("Channel 1 is visible", () => channelIsVisible && currentDrawableChannel.Channel == testChannel1); + waitForChannel1Visible(); } [Test] @@ -190,7 +190,7 @@ namespace osu.Game.Tests.Visual.Online AddStep("Show overlay", () => chatOverlay.Show()); AddAssert("Listing is visible", () => listingIsVisible); AddStep("Search for 'number 2'", () => chatOverlayTextBox.Text = "number 2"); - AddUntilStep("Only channel 2 visibile", () => + AddUntilStep("Only channel 2 visible", () => { IEnumerable listingItems = chatOverlay.ChildrenOfType() .Where(item => item.IsPresent); @@ -280,7 +280,7 @@ namespace osu.Game.Tests.Visual.Online }); }); AddStep("Highlight message", () => chatOverlay.HighlightMessage(message, testChannel1)); - AddUntilStep("Channel 1 is visible", () => channelIsVisible && currentDrawableChannel.Channel == testChannel1); + waitForChannel1Visible(); } [Test] @@ -303,7 +303,7 @@ namespace osu.Game.Tests.Visual.Online }); }); AddStep("Highlight message", () => chatOverlay.HighlightMessage(message, testChannel2)); - AddUntilStep("Channel 2 is visible", () => channelIsVisible && currentDrawableChannel.Channel == testChannel2); + waitForChannel2Visible(); } [Test] @@ -327,7 +327,7 @@ namespace osu.Game.Tests.Visual.Online }); AddStep("Leave channel 2", () => channelManager.LeaveChannel(testChannel2)); AddStep("Highlight message", () => chatOverlay.HighlightMessage(message, testChannel2)); - AddUntilStep("Channel 2 is visible", () => channelIsVisible && currentDrawableChannel.Channel == testChannel2); + waitForChannel2Visible(); } [Test] @@ -347,7 +347,7 @@ namespace osu.Game.Tests.Visual.Online }); }); AddStep("Highlight message", () => chatOverlay.HighlightMessage(message, testChannel1)); - AddUntilStep("Channel 1 is visible", () => channelIsVisible && currentDrawableChannel.Channel == testChannel1); + waitForChannel1Visible(); } [Test] @@ -368,7 +368,7 @@ namespace osu.Game.Tests.Visual.Online }); AddStep("Set null channel", () => channelManager.CurrentChannel.Value = null); AddStep("Highlight message", () => chatOverlay.HighlightMessage(message, testChannel1)); - AddUntilStep("Channel 1 is visible", () => channelIsVisible && currentDrawableChannel.Channel == testChannel1); + waitForChannel1Visible(); } [Test] @@ -378,6 +378,7 @@ namespace osu.Game.Tests.Visual.Online AddAssert("TextBox is focused", () => InputManager.FocusedDrawable == chatOverlayTextBox); AddStep("Join channel 1", () => channelManager.JoinChannel(testChannel1)); AddStep("Select channel 1", () => clickDrawable(getChannelListItem(testChannel1))); + waitForChannel1Visible(); AddAssert("TextBox is focused", () => InputManager.FocusedDrawable == chatOverlayTextBox); AddStep("Click drawable channel", () => clickDrawable(currentDrawableChannel)); AddAssert("TextBox is focused", () => InputManager.FocusedDrawable == chatOverlayTextBox); @@ -415,11 +416,11 @@ namespace osu.Game.Tests.Visual.Online AddStep("Finish channel 2 load", () => chatOverlay.GetSlowLoadingChannel(testChannel2).LoadEvent.Set()); AddAssert("Channel 2 loaded", () => chatOverlay.GetSlowLoadingChannel(testChannel2).IsLoaded); - AddAssert("Channel 2 displayed", () => channelIsVisible && currentDrawableChannel.Channel == testChannel2); + waitForChannel2Visible(); AddStep("Select channel 1", () => clickDrawable(getChannelListItem(testChannel1))); AddAssert("Channel 1 loaded", () => chatOverlay.GetSlowLoadingChannel(testChannel1).IsLoaded); - AddAssert("Channel 1 displayed", () => channelIsVisible && currentDrawableChannel.Channel == testChannel1); + waitForChannel1Visible(); } [Test] @@ -430,13 +431,12 @@ namespace osu.Game.Tests.Visual.Online channelManager.JoinChannel(testChannel1); chatOverlay.Show(); }); - AddAssert("Channel 1 displayed", () => channelIsVisible && currentDrawableChannel.Channel == testChannel1); - + waitForChannel1Visible(); AddStep("Press document close keys", () => InputManager.Keys(PlatformAction.DocumentClose)); AddAssert("Listing is visible", () => listingIsVisible); AddStep("Press tab restore keys", () => InputManager.Keys(PlatformAction.TabRestore)); - AddAssert("Channel 1 displayed", () => channelIsVisible && currentDrawableChannel.Channel == testChannel1); + waitForChannel1Visible(); } [Test] @@ -447,8 +447,7 @@ namespace osu.Game.Tests.Visual.Online channelManager.JoinChannel(testChannel1); chatOverlay.Show(); }); - AddAssert("Channel 1 displayed", () => channelIsVisible && currentDrawableChannel.Channel == testChannel1); - + waitForChannel1Visible(); AddStep("Press tab new keys", () => InputManager.Keys(PlatformAction.TabNew)); AddAssert("Listing is visible", () => listingIsVisible); } @@ -468,21 +467,26 @@ namespace osu.Game.Tests.Visual.Online chatOverlay.Show(); }); - AddAssert("Channel 1 displayed", () => channelIsVisible && currentDrawableChannel.Channel == testChannel1); + waitForChannel1Visible(); + AddStep("Press document next keys", () => InputManager.Keys(PlatformAction.DocumentNext)); + waitForChannel2Visible(); AddStep("Press document next keys", () => InputManager.Keys(PlatformAction.DocumentNext)); - AddAssert("Channel 2 displayed", () => channelIsVisible && currentDrawableChannel.Channel == testChannel2); + AddUntilStep("PM Channel 1 displayed", () => channelIsVisible && currentDrawableChannel?.Channel == pmChannel1); AddStep("Press document next keys", () => InputManager.Keys(PlatformAction.DocumentNext)); - AddAssert("PM Channel 1 displayed", () => channelIsVisible && currentDrawableChannel.Channel == pmChannel1); + AddUntilStep("PM Channel 2 displayed", () => channelIsVisible && currentDrawableChannel?.Channel == pmChannel2); AddStep("Press document next keys", () => InputManager.Keys(PlatformAction.DocumentNext)); - AddAssert("PM Channel 2 displayed", () => channelIsVisible && currentDrawableChannel.Channel == pmChannel2); - - AddStep("Press document next keys", () => InputManager.Keys(PlatformAction.DocumentNext)); - AddAssert("Channel 1 displayed", () => channelIsVisible && currentDrawableChannel.Channel == testChannel1); + waitForChannel1Visible(); } + private void waitForChannel1Visible() => + AddUntilStep("Channel 1 is visible", () => channelIsVisible && currentDrawableChannel?.Channel == testChannel1); + + private void waitForChannel2Visible() => + AddUntilStep("Channel 2 is visible", () => channelIsVisible && currentDrawableChannel?.Channel == testChannel2); + private bool listingIsVisible => chatOverlay.ChildrenOfType().Single().State.Value == Visibility.Visible; @@ -492,8 +496,9 @@ namespace osu.Game.Tests.Visual.Online private bool channelIsVisible => !listingIsVisible && !loadingIsVisible; + [CanBeNull] private DrawableChannel currentDrawableChannel => - chatOverlay.ChildrenOfType().Single(); + chatOverlay.ChildrenOfType().SingleOrDefault(); private ChannelListItem getChannelListItem(Channel channel) => chatOverlay.ChildrenOfType().Single(item => item.Channel == channel); From e69f8716cd05105e029f31ff3600200dec58c8c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 29 May 2022 18:56:37 +0200 Subject: [PATCH 257/395] Fix incorrect indent size --- osu.Game/Overlays/Chat/ChannelList/ChannelList.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/Chat/ChannelList/ChannelList.cs b/osu.Game/Overlays/Chat/ChannelList/ChannelList.cs index 0f3d8574c1..20c8b6d32d 100644 --- a/osu.Game/Overlays/Chat/ChannelList/ChannelList.cs +++ b/osu.Game/Overlays/Chat/ChannelList/ChannelList.cs @@ -27,9 +27,9 @@ namespace osu.Game.Overlays.Chat.ChannelList public Action? OnRequestLeave; public IEnumerable Channels => groupFlow.Children.Where(child => child is ChannelGroup) - .Cast() - .SelectMany(channelGroup => channelGroup.ItemFlow) - .Select(item => item.Channel); + .Cast() + .SelectMany(channelGroup => channelGroup.ItemFlow) + .Select(item => item.Channel); public readonly ChannelListing.ChannelListingChannel ChannelListingChannel = new ChannelListing.ChannelListingChannel(); From e329c160b37bbccbd0231b67db2dd0ae6fa40be5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 29 May 2022 18:58:27 +0200 Subject: [PATCH 258/395] Simplify channel retrieval expression --- osu.Game/Overlays/Chat/ChannelList/ChannelList.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/Chat/ChannelList/ChannelList.cs b/osu.Game/Overlays/Chat/ChannelList/ChannelList.cs index 20c8b6d32d..47a2d234d1 100644 --- a/osu.Game/Overlays/Chat/ChannelList/ChannelList.cs +++ b/osu.Game/Overlays/Chat/ChannelList/ChannelList.cs @@ -26,8 +26,8 @@ namespace osu.Game.Overlays.Chat.ChannelList public Action? OnRequestSelect; public Action? OnRequestLeave; - public IEnumerable Channels => groupFlow.Children.Where(child => child is ChannelGroup) - .Cast() + public IEnumerable Channels => groupFlow.Children + .OfType() .SelectMany(channelGroup => channelGroup.ItemFlow) .Select(item => item.Channel); From c892ec83efb478c12c617bc191a7b964bc4d672b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 29 May 2022 20:15:17 +0200 Subject: [PATCH 259/395] Fix wrong sizing of editor timeline ticks --- .../Edit/Compose/Components/Timeline/TimelineTickDisplay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineTickDisplay.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineTickDisplay.cs index fda8416ecd..9904d91653 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineTickDisplay.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineTickDisplay.cs @@ -135,7 +135,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline Vector2 size = Vector2.One; - if (indexInBar != 1) + if (indexInBar != 0) size = BindableBeatDivisor.GetSize(divisor); var line = getNextUsableLine(); From 8f596520f3ea360cd5398fa42c762a9a3ee37e66 Mon Sep 17 00:00:00 2001 From: Jai Sharma Date: Sun, 29 May 2022 20:07:42 +0100 Subject: [PATCH 260/395] Remove old chat overlay components --- .../Online/TestSceneChannelTabControl.cs | 129 ---- .../Visual/Online/TestSceneChatOverlay.cs | 663 ------------------ osu.Game/Online/Chat/ChannelManager.cs | 3 +- .../Chat/Selection/ChannelListItem.cs | 191 ----- .../Overlays/Chat/Selection/ChannelSection.cs | 58 -- .../Chat/Selection/ChannelSelectionOverlay.cs | 194 ----- .../Chat/Tabs/ChannelSelectorTabItem.cs | 46 -- .../Overlays/Chat/Tabs/ChannelTabControl.cs | 114 --- osu.Game/Overlays/Chat/Tabs/ChannelTabItem.cs | 238 ------- .../Chat/Tabs/PrivateChannelTabItem.cs | 95 --- osu.Game/Overlays/Chat/Tabs/TabCloseButton.cs | 55 -- osu.Game/Overlays/ChatOverlay.cs | 525 -------------- 12 files changed, 1 insertion(+), 2310 deletions(-) delete mode 100644 osu.Game.Tests/Visual/Online/TestSceneChannelTabControl.cs delete mode 100644 osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs delete mode 100644 osu.Game/Overlays/Chat/Selection/ChannelListItem.cs delete mode 100644 osu.Game/Overlays/Chat/Selection/ChannelSection.cs delete mode 100644 osu.Game/Overlays/Chat/Selection/ChannelSelectionOverlay.cs delete mode 100644 osu.Game/Overlays/Chat/Tabs/ChannelSelectorTabItem.cs delete mode 100644 osu.Game/Overlays/Chat/Tabs/ChannelTabControl.cs delete mode 100644 osu.Game/Overlays/Chat/Tabs/ChannelTabItem.cs delete mode 100644 osu.Game/Overlays/Chat/Tabs/PrivateChannelTabItem.cs delete mode 100644 osu.Game/Overlays/Chat/Tabs/TabCloseButton.cs delete mode 100644 osu.Game/Overlays/ChatOverlay.cs diff --git a/osu.Game.Tests/Visual/Online/TestSceneChannelTabControl.cs b/osu.Game.Tests/Visual/Online/TestSceneChannelTabControl.cs deleted file mode 100644 index e6eaffc4c1..0000000000 --- a/osu.Game.Tests/Visual/Online/TestSceneChannelTabControl.cs +++ /dev/null @@ -1,129 +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.Collections.Generic; -using System.Linq; -using osu.Framework.Extensions.Color4Extensions; -using osu.Framework.Extensions.IEnumerableExtensions; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; -using osu.Framework.Graphics.Sprites; -using osu.Framework.Utils; -using osu.Game.Graphics.Sprites; -using osu.Game.Online.API.Requests.Responses; -using osu.Game.Online.Chat; -using osu.Game.Overlays.Chat.Tabs; -using osuTK.Graphics; - -namespace osu.Game.Tests.Visual.Online -{ - public class TestSceneChannelTabControl : OsuTestScene - { - private readonly TestTabControl channelTabControl; - - public TestSceneChannelTabControl() - { - SpriteText currentText; - Add(new Container - { - RelativeSizeAxes = Axes.X, - Origin = Anchor.Centre, - Anchor = Anchor.Centre, - Children = new Drawable[] - { - channelTabControl = new TestTabControl - { - RelativeSizeAxes = Axes.X, - Origin = Anchor.Centre, - Anchor = Anchor.Centre, - Height = 50 - }, - new Box - { - Colour = Color4.Black.Opacity(0.1f), - RelativeSizeAxes = Axes.X, - Height = 50, - Depth = -1, - Origin = Anchor.Centre, - Anchor = Anchor.Centre, - } - } - }); - - Add(new Container - { - Origin = Anchor.TopLeft, - Anchor = Anchor.TopLeft, - Children = new Drawable[] - { - currentText = new OsuSpriteText - { - Text = "Currently selected channel:" - } - } - }); - - channelTabControl.OnRequestLeave += channel => channelTabControl.RemoveChannel(channel); - channelTabControl.Current.ValueChanged += channel => currentText.Text = "Currently selected channel: " + channel.NewValue; - - AddStep("Add random private channel", addRandomPrivateChannel); - AddAssert("There is only one channels", () => channelTabControl.Items.Count == 2); - AddRepeatStep("Add 3 random private channels", addRandomPrivateChannel, 3); - AddAssert("There are four channels", () => channelTabControl.Items.Count == 5); - AddStep("Add random public channel", () => addChannel(RNG.Next().ToString())); - - AddRepeatStep("Select a random channel", () => - { - List validChannels = channelTabControl.Items.Where(c => !(c is ChannelSelectorTabItem.ChannelSelectorTabChannel)).ToList(); - channelTabControl.SelectChannel(validChannels[RNG.Next(0, validChannels.Count)]); - }, 20); - - Channel channelBefore = null; - AddStep("set first channel", () => channelTabControl.SelectChannel(channelBefore = channelTabControl.Items.First(c => !(c is ChannelSelectorTabItem.ChannelSelectorTabChannel)))); - - AddStep("select selector tab", () => channelTabControl.SelectChannel(channelTabControl.Items.Single(c => c is ChannelSelectorTabItem.ChannelSelectorTabChannel))); - AddAssert("selector tab is active", () => channelTabControl.ChannelSelectorActive.Value); - - AddAssert("check channel unchanged", () => channelBefore == channelTabControl.Current.Value); - - AddStep("set second channel", () => channelTabControl.SelectChannel(channelTabControl.Items.GetNext(channelBefore))); - AddAssert("selector tab is inactive", () => !channelTabControl.ChannelSelectorActive.Value); - - AddUntilStep("remove all channels", () => - { - foreach (var item in channelTabControl.Items.ToList()) - { - if (item is ChannelSelectorTabItem.ChannelSelectorTabChannel) - continue; - - channelTabControl.RemoveChannel(item); - return false; - } - - return true; - }); - - AddAssert("selector tab is active", () => channelTabControl.ChannelSelectorActive.Value); - } - - private void addRandomPrivateChannel() => - channelTabControl.AddChannel(new Channel(new APIUser - { - Id = RNG.Next(1000, 10000000), - Username = "Test User " + RNG.Next(1000) - })); - - private void addChannel(string name) => - channelTabControl.AddChannel(new Channel - { - Type = ChannelType.Public, - Name = name - }); - - private class TestTabControl : ChannelTabControl - { - public void SelectChannel(Channel channel) => base.SelectTab(TabMap[channel]); - } - } -} diff --git a/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs deleted file mode 100644 index 4d1dee1650..0000000000 --- a/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs +++ /dev/null @@ -1,663 +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 System.Net; -using JetBrains.Annotations; -using NUnit.Framework; -using osu.Framework.Allocation; -using osu.Framework.Bindables; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.UserInterface; -using osu.Framework.Input; -using osu.Framework.Testing; -using osu.Game.Graphics.UserInterface; -using osu.Game.Online.API; -using osu.Game.Online.API.Requests; -using osu.Game.Online.API.Requests.Responses; -using osu.Game.Online.Chat; -using osu.Game.Overlays; -using osu.Game.Overlays.Chat; -using osu.Game.Overlays.Chat.Selection; -using osu.Game.Overlays.Chat.Tabs; -using osuTK.Input; - -namespace osu.Game.Tests.Visual.Online -{ - public class TestSceneChatOverlay : OsuManualInputManagerTestScene - { - private TestChatOverlay chatOverlay; - private ChannelManager channelManager; - - private IEnumerable visibleChannels => chatOverlay.ChannelTabControl.VisibleItems.Where(channel => channel.Name != "+"); - private IEnumerable joinedChannels => chatOverlay.ChannelTabControl.Items.Where(channel => channel.Name != "+"); - private readonly List channels; - - private Channel currentChannel => channelManager.CurrentChannel.Value; - private Channel nextChannel => joinedChannels.ElementAt(joinedChannels.ToList().IndexOf(currentChannel) + 1); - private Channel previousChannel => joinedChannels.ElementAt(joinedChannels.ToList().IndexOf(currentChannel) - 1); - private Channel channel1 => channels[0]; - private Channel channel2 => channels[1]; - private Channel channel3 => channels[2]; - - [CanBeNull] - private Func> onGetMessages; - - public TestSceneChatOverlay() - { - channels = Enumerable.Range(1, 10) - .Select(index => new Channel(new APIUser()) - { - Name = $"Channel no. {index}", - Topic = index == 3 ? null : $"We talk about the number {index} here", - Type = index % 2 == 0 ? ChannelType.PM : ChannelType.Temporary, - Id = index - }) - .ToList(); - } - - [SetUp] - public void Setup() - { - Schedule(() => - { - ChannelManagerContainer container; - - Child = container = new ChannelManagerContainer(channels) - { - RelativeSizeAxes = Axes.Both, - }; - - chatOverlay = container.ChatOverlay; - channelManager = container.ChannelManager; - }); - } - - [SetUpSteps] - public void SetUpSteps() - { - AddStep("register request handling", () => - { - onGetMessages = null; - - ((DummyAPIAccess)API).HandleRequest = req => - { - switch (req) - { - case JoinChannelRequest joinChannel: - joinChannel.TriggerSuccess(); - return true; - - case GetUserRequest getUser: - if (getUser.Lookup.Equals("some body", StringComparison.OrdinalIgnoreCase)) - { - getUser.TriggerSuccess(new APIUser - { - Username = "some body", - Id = 1, - }); - } - else - { - getUser.TriggerFailure(new WebException()); - } - - return true; - - case GetMessagesRequest getMessages: - var messages = onGetMessages?.Invoke(getMessages.Channel); - if (messages != null) - getMessages.TriggerSuccess(messages); - return true; - } - - return false; - }; - }); - } - - [Test] - public void TestHideOverlay() - { - AddStep("Open chat overlay", () => chatOverlay.Show()); - - AddAssert("Chat overlay is visible", () => chatOverlay.State.Value == Visibility.Visible); - AddAssert("Selector is visible", () => chatOverlay.SelectionOverlayState == Visibility.Visible); - - AddStep("Close chat overlay", () => chatOverlay.Hide()); - - AddAssert("Chat overlay was hidden", () => chatOverlay.State.Value == Visibility.Hidden); - AddAssert("Channel selection overlay was hidden", () => chatOverlay.SelectionOverlayState == Visibility.Hidden); - } - - [Test] - public void TestChannelSelection() - { - AddStep("Open chat overlay", () => chatOverlay.Show()); - AddAssert("Selector is visible", () => chatOverlay.SelectionOverlayState == Visibility.Visible); - AddStep("Setup get message response", () => onGetMessages = channel => - { - if (channel == channel1) - { - return new List - { - new Message(1) - { - ChannelId = channel1.Id, - Content = "hello from channel 1!", - Sender = new APIUser - { - Id = 2, - Username = "test_user" - } - } - }; - } - - return null; - }); - - AddStep("Join channel 1", () => channelManager.JoinChannel(channel1)); - AddStep("Switch to channel 1", () => clickDrawable(chatOverlay.TabMap[channel1])); - - AddAssert("Current channel is channel 1", () => currentChannel == channel1); - AddUntilStep("Loading spinner hidden", () => chatOverlay.ChildrenOfType().All(spinner => !spinner.IsPresent)); - AddAssert("Channel message shown", () => chatOverlay.ChildrenOfType().Count() == 1); - AddAssert("Channel selector was closed", () => chatOverlay.SelectionOverlayState == Visibility.Hidden); - } - - [Test] - public void TestSearchInSelector() - { - AddStep("Open chat overlay", () => chatOverlay.Show()); - AddStep("Search for 'no. 2'", () => chatOverlay.ChildrenOfType().First().Text = "no. 2"); - AddUntilStep("Only channel 2 visible", () => - { - var listItems = chatOverlay.ChildrenOfType().Where(c => c.IsPresent); - return listItems.Count() == 1 && listItems.Single().Channel == channel2; - }); - } - - [Test] - public void TestChannelShortcutKeys() - { - AddStep("Open chat overlay", () => chatOverlay.Show()); - AddStep("Join channels", () => channels.ForEach(channel => channelManager.JoinChannel(channel))); - AddStep("Close channel selector", () => InputManager.Key(Key.Escape)); - AddUntilStep("Wait for close", () => chatOverlay.SelectionOverlayState == Visibility.Hidden); - - for (int zeroBasedIndex = 0; zeroBasedIndex < 10; ++zeroBasedIndex) - { - int oneBasedIndex = zeroBasedIndex + 1; - int targetNumberKey = oneBasedIndex % 10; - var targetChannel = channels[zeroBasedIndex]; - AddStep($"Press Alt+{targetNumberKey}", () => pressChannelHotkey(targetNumberKey)); - AddAssert($"Channel #{oneBasedIndex} is selected", () => currentChannel == targetChannel); - } - } - - private Channel expectedChannel; - - [Test] - public void TestCloseChannelBehaviour() - { - AddStep("Open chat overlay", () => chatOverlay.Show()); - AddUntilStep("Join until dropdown has channels", () => - { - if (visibleChannels.Count() < joinedChannels.Count()) - return true; - - // Using temporary channels because they don't hide their names when not active - channelManager.JoinChannel(new Channel - { - Name = $"Channel no. {joinedChannels.Count() + 11}", - Type = ChannelType.Temporary - }); - - return false; - }); - - AddStep("Switch to last tab", () => clickDrawable(chatOverlay.TabMap[visibleChannels.Last()])); - AddAssert("Last visible selected", () => currentChannel == visibleChannels.Last()); - - // Closing the last channel before dropdown - AddStep("Close current channel", () => - { - expectedChannel = nextChannel; - chatOverlay.ChannelTabControl.RemoveChannel(currentChannel); - }); - AddAssert("Next channel selected", () => currentChannel == expectedChannel); - AddAssert("Selector remained closed", () => chatOverlay.SelectionOverlayState == Visibility.Hidden); - - // Depending on the window size, one more channel might need to be closed for the selectorTab to appear - AddUntilStep("Close channels until selector visible", () => - { - if (chatOverlay.ChannelTabControl.VisibleItems.Last().Name == "+") - return true; - - chatOverlay.ChannelTabControl.RemoveChannel(visibleChannels.Last()); - return false; - }); - AddAssert("Last visible selected", () => currentChannel == visibleChannels.Last()); - - // Closing the last channel with dropdown no longer present - AddStep("Close last when selector next", () => - { - expectedChannel = previousChannel; - chatOverlay.ChannelTabControl.RemoveChannel(currentChannel); - }); - AddAssert("Previous channel selected", () => currentChannel == expectedChannel); - - // Standard channel closing - AddStep("Switch to previous channel", () => chatOverlay.ChannelTabControl.SwitchTab(-1)); - AddStep("Close current channel", () => - { - expectedChannel = nextChannel; - chatOverlay.ChannelTabControl.RemoveChannel(currentChannel); - }); - AddAssert("Next channel selected", () => currentChannel == expectedChannel); - - // Selector reappearing after all channels closed - AddUntilStep("Close all channels", () => - { - if (!joinedChannels.Any()) - return true; - - chatOverlay.ChannelTabControl.RemoveChannel(joinedChannels.Last()); - return false; - }); - AddAssert("Selector is visible", () => chatOverlay.SelectionOverlayState == Visibility.Visible); - } - - [Test] - public void TestChannelCloseButton() - { - AddStep("Open chat overlay", () => chatOverlay.Show()); - AddStep("Join 2 channels", () => - { - channelManager.JoinChannel(channel1); - channelManager.JoinChannel(channel2); - }); - - // PM channel close button only appears when active - AddStep("Select PM channel", () => clickDrawable(chatOverlay.TabMap[channel2])); - AddStep("Click PM close button", () => clickDrawable(((TestPrivateChannelTabItem)chatOverlay.TabMap[channel2]).CloseButton.Child)); - AddAssert("PM channel closed", () => !channelManager.JoinedChannels.Contains(channel2)); - - // Non-PM chat channel close button only appears when hovered - AddStep("Hover normal channel tab", () => InputManager.MoveMouseTo(chatOverlay.TabMap[channel1])); - AddStep("Click normal close button", () => clickDrawable(((TestChannelTabItem)chatOverlay.TabMap[channel1]).CloseButton.Child)); - AddAssert("All channels closed", () => !channelManager.JoinedChannels.Any()); - } - - [Test] - public void TestCloseTabShortcut() - { - AddStep("Open chat overlay", () => chatOverlay.Show()); - AddStep("Join 2 channels", () => - { - channelManager.JoinChannel(channel1); - channelManager.JoinChannel(channel2); - }); - - // Want to close channel 2 - AddStep("Select channel 2", () => clickDrawable(chatOverlay.TabMap[channel2])); - AddStep("Close tab via shortcut", pressCloseDocumentKeys); - - // Channel 2 should be closed - AddAssert("Channel 1 open", () => channelManager.JoinedChannels.Contains(channel1)); - AddAssert("Channel 2 closed", () => !channelManager.JoinedChannels.Contains(channel2)); - - // Want to close channel 1 - AddStep("Select channel 1", () => clickDrawable(chatOverlay.TabMap[channel1])); - - AddStep("Close tab via shortcut", pressCloseDocumentKeys); - // Channel 1 and channel 2 should be closed - AddAssert("All channels closed", () => !channelManager.JoinedChannels.Any()); - } - - [Test] - public void TestNewTabShortcut() - { - AddStep("Open chat overlay", () => chatOverlay.Show()); - AddStep("Join 2 channels", () => - { - channelManager.JoinChannel(channel1); - channelManager.JoinChannel(channel2); - }); - - // Want to join another channel - AddStep("Press new tab shortcut", pressNewTabKeys); - - // Selector should be visible - AddAssert("Selector is visible", () => chatOverlay.SelectionOverlayState == Visibility.Visible); - } - - [Test] - public void TestRestoreTabShortcut() - { - AddStep("Open chat overlay", () => chatOverlay.Show()); - AddStep("Join 3 channels", () => - { - channelManager.JoinChannel(channel1); - channelManager.JoinChannel(channel2); - channelManager.JoinChannel(channel3); - }); - - // Should do nothing - AddStep("Restore tab via shortcut", pressRestoreTabKeys); - AddAssert("All channels still open", () => channelManager.JoinedChannels.Count == 3); - - // Close channel 1 - AddStep("Select channel 1", () => clickDrawable(chatOverlay.TabMap[channel1])); - AddStep("Click normal close button", () => clickDrawable(((TestChannelTabItem)chatOverlay.TabMap[channel1]).CloseButton.Child)); - AddAssert("Channel 1 closed", () => !channelManager.JoinedChannels.Contains(channel1)); - AddAssert("Other channels still open", () => channelManager.JoinedChannels.Count == 2); - - // Reopen channel 1 - AddStep("Restore tab via shortcut", pressRestoreTabKeys); - AddAssert("All channels now open", () => channelManager.JoinedChannels.Count == 3); - AddAssert("Current channel is channel 1", () => currentChannel == channel1); - - // Close two channels - AddStep("Select channel 1", () => clickDrawable(chatOverlay.TabMap[channel1])); - AddStep("Close channel 1", () => clickDrawable(((TestChannelTabItem)chatOverlay.TabMap[channel1]).CloseButton.Child)); - AddStep("Select channel 2", () => clickDrawable(chatOverlay.TabMap[channel2])); - AddStep("Close channel 2", () => clickDrawable(((TestPrivateChannelTabItem)chatOverlay.TabMap[channel2]).CloseButton.Child)); - AddAssert("Only one channel open", () => channelManager.JoinedChannels.Count == 1); - AddAssert("Current channel is channel 3", () => currentChannel == channel3); - - // Should first re-open channel 2 - AddStep("Restore tab via shortcut", pressRestoreTabKeys); - AddAssert("Channel 1 still closed", () => !channelManager.JoinedChannels.Contains(channel1)); - AddAssert("Channel 2 now open", () => channelManager.JoinedChannels.Contains(channel2)); - AddAssert("Current channel is channel 2", () => currentChannel == channel2); - - // Should then re-open channel 1 - AddStep("Restore tab via shortcut", pressRestoreTabKeys); - AddAssert("All channels now open", () => channelManager.JoinedChannels.Count == 3); - AddAssert("Current channel is channel 1", () => currentChannel == channel1); - } - - [Test] - public void TestChatCommand() - { - AddStep("Open chat overlay", () => chatOverlay.Show()); - AddStep("Join channel 1", () => channelManager.JoinChannel(channel1)); - AddStep("Select channel 1", () => clickDrawable(chatOverlay.TabMap[channel1])); - - AddStep("Open chat with user", () => channelManager.PostCommand("chat some body")); - AddAssert("PM channel is selected", () => - channelManager.CurrentChannel.Value.Type == ChannelType.PM && channelManager.CurrentChannel.Value.Users.Single().Username == "some body"); - - AddStep("Open chat with non-existent user", () => channelManager.PostCommand("chat nobody")); - AddAssert("Last message is error", () => channelManager.CurrentChannel.Value.Messages.Last() is ErrorMessage); - - // Make sure no unnecessary requests are made when the PM channel is already open. - AddStep("Select channel 1", () => clickDrawable(chatOverlay.TabMap[channel1])); - AddStep("Unregister request handling", () => ((DummyAPIAccess)API).HandleRequest = null); - AddStep("Open chat with user", () => channelManager.PostCommand("chat some body")); - AddAssert("PM channel is selected", () => - channelManager.CurrentChannel.Value.Type == ChannelType.PM && channelManager.CurrentChannel.Value.Users.Single().Username == "some body"); - } - - [Test] - public void TestMultiplayerChannelIsNotShown() - { - Channel multiplayerChannel = null; - - AddStep("open chat overlay", () => chatOverlay.Show()); - - AddStep("join multiplayer channel", () => channelManager.JoinChannel(multiplayerChannel = new Channel(new APIUser()) - { - Name = "#mp_1", - Type = ChannelType.Multiplayer, - })); - - AddAssert("channel joined", () => channelManager.JoinedChannels.Contains(multiplayerChannel)); - AddAssert("channel not present in overlay", () => !chatOverlay.TabMap.ContainsKey(multiplayerChannel)); - AddAssert("multiplayer channel is not current", () => channelManager.CurrentChannel.Value != multiplayerChannel); - - AddStep("leave channel", () => channelManager.LeaveChannel(multiplayerChannel)); - AddAssert("channel left", () => !channelManager.JoinedChannels.Contains(multiplayerChannel)); - } - - [Test] - public void TestHighlightOnCurrentChannel() - { - Message message = null; - - AddStep("Open chat overlay", () => chatOverlay.Show()); - AddStep("Join channel 1", () => channelManager.JoinChannel(channel1)); - AddStep("Select channel 1", () => clickDrawable(chatOverlay.TabMap[channel1])); - - AddStep("Send message in channel 1", () => - { - channel1.AddNewMessages(message = new Message - { - ChannelId = channel1.Id, - Content = "Message to highlight!", - Timestamp = DateTimeOffset.Now, - Sender = new APIUser - { - Id = 2, - Username = "Someone", - } - }); - }); - - AddStep("Highlight message", () => chatOverlay.HighlightMessage(message, channel1)); - } - - [Test] - public void TestHighlightOnAnotherChannel() - { - Message message = null; - - AddStep("Open chat overlay", () => chatOverlay.Show()); - AddStep("Join channel 1", () => channelManager.JoinChannel(channel1)); - AddStep("Select channel 1", () => clickDrawable(chatOverlay.TabMap[channel1])); - - AddStep("Join channel 2", () => channelManager.JoinChannel(channel2)); - AddStep("Send message in channel 2", () => - { - channel2.AddNewMessages(message = new Message - { - ChannelId = channel2.Id, - Content = "Message to highlight!", - Timestamp = DateTimeOffset.Now, - Sender = new APIUser - { - Id = 2, - Username = "Someone", - } - }); - }); - - AddStep("Highlight message", () => chatOverlay.HighlightMessage(message, channel2)); - AddAssert("Switched to channel 2", () => channelManager.CurrentChannel.Value == channel2); - } - - [Test] - public void TestHighlightOnLeftChannel() - { - Message message = null; - - AddStep("Open chat overlay", () => chatOverlay.Show()); - - AddStep("Join channel 1", () => channelManager.JoinChannel(channel1)); - AddStep("Select channel 1", () => clickDrawable(chatOverlay.TabMap[channel1])); - - AddStep("Join channel 2", () => channelManager.JoinChannel(channel2)); - AddStep("Send message in channel 2", () => - { - channel2.AddNewMessages(message = new Message - { - ChannelId = channel2.Id, - Content = "Message to highlight!", - Timestamp = DateTimeOffset.Now, - Sender = new APIUser - { - Id = 2, - Username = "Someone", - } - }); - }); - AddStep("Leave channel 2", () => channelManager.LeaveChannel(channel2)); - - AddStep("Highlight message", () => chatOverlay.HighlightMessage(message, channel2)); - AddAssert("Switched to channel 2", () => channelManager.CurrentChannel.Value == channel2); - } - - [Test] - public void TestHighlightWhileChatNeverOpen() - { - Message message = null; - - AddStep("Join channel 1", () => channelManager.JoinChannel(channel1)); - - AddStep("Send message in channel 1", () => - { - channel1.AddNewMessages(message = new Message - { - ChannelId = channel1.Id, - Content = "Message to highlight!", - Timestamp = DateTimeOffset.Now, - Sender = new APIUser - { - Id = 2, - Username = "Someone", - } - }); - }); - - AddStep("Highlight message", () => chatOverlay.HighlightMessage(message, channel1)); - } - - [Test] - public void TestHighlightWithNullChannel() - { - Message message = null; - - AddStep("Join channel 1", () => channelManager.JoinChannel(channel1)); - - AddStep("Send message in channel 1", () => - { - channel1.AddNewMessages(message = new Message - { - ChannelId = channel1.Id, - Content = "Message to highlight!", - Timestamp = DateTimeOffset.Now, - Sender = new APIUser - { - Id = 2, - Username = "Someone", - } - }); - }); - - AddStep("Set null channel", () => channelManager.CurrentChannel.Value = null); - AddStep("Highlight message", () => chatOverlay.HighlightMessage(message, channel1)); - } - - private void pressChannelHotkey(int number) - { - var channelKey = Key.Number0 + number; - InputManager.PressKey(Key.AltLeft); - InputManager.Key(channelKey); - InputManager.ReleaseKey(Key.AltLeft); - } - - private void pressCloseDocumentKeys() => InputManager.Keys(PlatformAction.DocumentClose); - - private void pressNewTabKeys() => InputManager.Keys(PlatformAction.TabNew); - - private void pressRestoreTabKeys() => InputManager.Keys(PlatformAction.TabRestore); - - private void clickDrawable(Drawable d) - { - InputManager.MoveMouseTo(d); - InputManager.Click(MouseButton.Left); - } - - private class ChannelManagerContainer : Container - { - public TestChatOverlay ChatOverlay { get; private set; } - - [Cached] - public ChannelManager ChannelManager { get; } = new ChannelManager(); - - private readonly List channels; - - public ChannelManagerContainer(List channels) - { - this.channels = channels; - } - - [BackgroundDependencyLoader] - private void load() - { - ((BindableList)ChannelManager.AvailableChannels).AddRange(channels); - - InternalChildren = new Drawable[] - { - ChannelManager, - ChatOverlay = new TestChatOverlay { RelativeSizeAxes = Axes.Both, }, - }; - } - } - - private class TestChatOverlay : ChatOverlay - { - public Visibility SelectionOverlayState => ChannelSelectionOverlay.State.Value; - - public new ChannelTabControl ChannelTabControl => base.ChannelTabControl; - - public new ChannelSelectionOverlay ChannelSelectionOverlay => base.ChannelSelectionOverlay; - - protected override ChannelTabControl CreateChannelTabControl() => new TestTabControl(); - - public IReadOnlyDictionary> TabMap => ((TestTabControl)ChannelTabControl).TabMap; - } - - private class TestTabControl : ChannelTabControl - { - protected override TabItem CreateTabItem(Channel value) - { - switch (value.Type) - { - case ChannelType.PM: - return new TestPrivateChannelTabItem(value); - - default: - return new TestChannelTabItem(value); - } - } - - public new IReadOnlyDictionary> TabMap => base.TabMap; - } - - private class TestChannelTabItem : ChannelTabItem - { - public TestChannelTabItem(Channel channel) - : base(channel) - { - } - - public new ClickableContainer CloseButton => base.CloseButton; - } - - private class TestPrivateChannelTabItem : PrivateChannelTabItem - { - public TestPrivateChannelTabItem(Channel channel) - : base(channel) - { - } - - public new ClickableContainer CloseButton => base.CloseButton; - } - } -} diff --git a/osu.Game/Online/Chat/ChannelManager.cs b/osu.Game/Online/Chat/ChannelManager.cs index b7d67de04d..31f67bcecc 100644 --- a/osu.Game/Online/Chat/ChannelManager.cs +++ b/osu.Game/Online/Chat/ChannelManager.cs @@ -15,7 +15,6 @@ using osu.Game.Online.API; using osu.Game.Online.API.Requests; using osu.Game.Online.API.Requests.Responses; using osu.Game.Overlays.Chat.Listing; -using osu.Game.Overlays.Chat.Tabs; namespace osu.Game.Online.Chat { @@ -134,7 +133,7 @@ namespace osu.Game.Online.Chat private void currentChannelChanged(ValueChangedEvent e) { - bool isSelectorChannel = e.NewValue is ChannelSelectorTabItem.ChannelSelectorTabChannel || e.NewValue is ChannelListing.ChannelListingChannel; + bool isSelectorChannel = e.NewValue is ChannelListing.ChannelListingChannel; if (!isSelectorChannel) JoinChannel(e.NewValue); diff --git a/osu.Game/Overlays/Chat/Selection/ChannelListItem.cs b/osu.Game/Overlays/Chat/Selection/ChannelListItem.cs deleted file mode 100644 index 59989ade7b..0000000000 --- a/osu.Game/Overlays/Chat/Selection/ChannelListItem.cs +++ /dev/null @@ -1,191 +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 osuTK; -using osuTK.Graphics; -using osu.Framework.Allocation; -using osu.Framework.Bindables; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Sprites; -using osu.Framework.Input.Events; -using osu.Framework.Localisation; -using osu.Game.Graphics; -using osu.Game.Graphics.Sprites; -using osu.Game.Online.Chat; -using osu.Game.Graphics.Containers; - -namespace osu.Game.Overlays.Chat.Selection -{ - public class ChannelListItem : OsuClickableContainer, IFilterable - { - private const float width_padding = 5; - private const float channel_width = 150; - private const float text_size = 15; - private const float transition_duration = 100; - - public readonly Channel Channel; - - private readonly Bindable joinedBind = new Bindable(); - private readonly OsuSpriteText name; - private readonly OsuSpriteText topic; - private readonly SpriteIcon joinedCheckmark; - - private Color4 joinedColour; - private Color4 topicColour; - private Color4 hoverColour; - - public IEnumerable FilterTerms => new LocalisableString[] { Channel.Name, Channel.Topic ?? string.Empty }; - - public bool MatchingFilter - { - set => this.FadeTo(value ? 1f : 0f, 100); - } - - public bool FilteringActive { get; set; } - - public Action OnRequestJoin; - public Action OnRequestLeave; - - public ChannelListItem(Channel channel) - { - Channel = channel; - - RelativeSizeAxes = Axes.X; - AutoSizeAxes = Axes.Y; - - Action = () => { (channel.Joined.Value ? OnRequestLeave : OnRequestJoin)?.Invoke(channel); }; - - Children = new Drawable[] - { - new FillFlowContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Direction = FillDirection.Horizontal, - Children = new Drawable[] - { - new Container - { - Children = new[] - { - joinedCheckmark = new SpriteIcon - { - Anchor = Anchor.TopRight, - Origin = Anchor.TopRight, - Icon = FontAwesome.Solid.CheckCircle, - Size = new Vector2(text_size), - Shadow = false, - Margin = new MarginPadding { Right = 10f }, - }, - }, - }, - new Container - { - Width = channel_width, - AutoSizeAxes = Axes.Y, - Children = new[] - { - name = new OsuSpriteText - { - Text = channel.ToString(), - Font = OsuFont.GetFont(size: text_size, weight: FontWeight.Bold), - Shadow = false, - }, - }, - }, - new Container - { - RelativeSizeAxes = Axes.X, - Width = 0.7f, - AutoSizeAxes = Axes.Y, - Margin = new MarginPadding { Left = width_padding }, - Children = new[] - { - topic = new OsuSpriteText - { - Text = channel.Topic, - Font = OsuFont.GetFont(size: text_size, weight: FontWeight.SemiBold), - Shadow = false, - }, - }, - }, - new FillFlowContainer - { - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Horizontal, - Margin = new MarginPadding { Left = width_padding }, - Spacing = new Vector2(3f, 0f), - Children = new Drawable[] - { - new SpriteIcon - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Icon = FontAwesome.Solid.User, - Size = new Vector2(text_size - 2), - Shadow = false, - }, - new OsuSpriteText - { - Text = @"0", - Font = OsuFont.GetFont(size: text_size, weight: FontWeight.SemiBold), - Shadow = false, - }, - }, - }, - }, - }, - }; - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - topicColour = colours.Gray9; - joinedColour = colours.Blue; - hoverColour = colours.Yellow; - - joinedBind.ValueChanged += joined => updateColour(joined.NewValue); - joinedBind.BindTo(Channel.Joined); - - joinedBind.TriggerChange(); - FinishTransforms(true); - } - - protected override bool OnHover(HoverEvent e) - { - if (!Channel.Joined.Value) - name.FadeColour(hoverColour, 50, Easing.OutQuint); - - return base.OnHover(e); - } - - protected override void OnHoverLost(HoverLostEvent e) - { - if (!Channel.Joined.Value) - name.FadeColour(Color4.White, transition_duration); - } - - private void updateColour(bool joined) - { - if (joined) - { - name.FadeColour(Color4.White, transition_duration); - joinedCheckmark.FadeTo(1f, transition_duration); - topic.FadeTo(0.8f, transition_duration); - topic.FadeColour(Color4.White, transition_duration); - this.FadeColour(joinedColour, transition_duration); - } - else - { - joinedCheckmark.FadeTo(0f, transition_duration); - topic.FadeTo(1f, transition_duration); - topic.FadeColour(topicColour, transition_duration); - this.FadeColour(Color4.White, transition_duration); - } - } - } -} diff --git a/osu.Game/Overlays/Chat/Selection/ChannelSection.cs b/osu.Game/Overlays/Chat/Selection/ChannelSection.cs deleted file mode 100644 index 070332180c..0000000000 --- a/osu.Game/Overlays/Chat/Selection/ChannelSection.cs +++ /dev/null @@ -1,58 +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.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Localisation; -using osu.Game.Graphics; -using osu.Game.Graphics.Sprites; -using osu.Game.Online.Chat; -using osuTK; - -namespace osu.Game.Overlays.Chat.Selection -{ - public class ChannelSection : Container, IHasFilterableChildren - { - public readonly FillFlowContainer ChannelFlow; - - public IEnumerable FilterableChildren => ChannelFlow.Children; - public IEnumerable FilterTerms => Array.Empty(); - - public bool MatchingFilter - { - set => this.FadeTo(value ? 1f : 0f, 100); - } - - public bool FilteringActive { get; set; } - - public IEnumerable Channels - { - set => ChannelFlow.ChildrenEnumerable = value.Select(c => new ChannelListItem(c)); - } - - public ChannelSection() - { - RelativeSizeAxes = Axes.X; - AutoSizeAxes = Axes.Y; - - Children = new Drawable[] - { - new OsuSpriteText - { - Font = OsuFont.GetFont(size: 15, weight: FontWeight.Bold), - Text = "All Channels".ToUpperInvariant() - }, - ChannelFlow = new FillFlowContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Margin = new MarginPadding { Top = 25 }, - Spacing = new Vector2(0f, 5f), - }, - }; - } - } -} diff --git a/osu.Game/Overlays/Chat/Selection/ChannelSelectionOverlay.cs b/osu.Game/Overlays/Chat/Selection/ChannelSelectionOverlay.cs deleted file mode 100644 index 9b0354e264..0000000000 --- a/osu.Game/Overlays/Chat/Selection/ChannelSelectionOverlay.cs +++ /dev/null @@ -1,194 +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 osuTK; -using osuTK.Graphics; -using osu.Framework.Allocation; -using osu.Framework.Extensions.Color4Extensions; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; -using osu.Framework.Input.Events; -using osu.Game.Graphics; -using osu.Game.Graphics.Backgrounds; -using osu.Game.Graphics.Sprites; -using osu.Game.Graphics.UserInterface; -using osu.Game.Online.Chat; -using osu.Game.Graphics.Containers; - -namespace osu.Game.Overlays.Chat.Selection -{ - public class ChannelSelectionOverlay : WaveOverlayContainer - { - public new const float WIDTH_PADDING = 170; - - private const float transition_duration = 500; - - private readonly Box bg; - private readonly Triangles triangles; - private readonly Box headerBg; - private readonly SearchTextBox search; - private readonly SearchContainer sectionsFlow; - - protected override bool DimMainContent => false; - - public Action OnRequestJoin; - public Action OnRequestLeave; - - public ChannelSelectionOverlay() - { - RelativeSizeAxes = Axes.X; - - Waves.FirstWaveColour = Color4Extensions.FromHex("353535"); - Waves.SecondWaveColour = Color4Extensions.FromHex("434343"); - Waves.ThirdWaveColour = Color4Extensions.FromHex("515151"); - Waves.FourthWaveColour = Color4Extensions.FromHex("595959"); - - Children = new Drawable[] - { - new Container - { - RelativeSizeAxes = Axes.Both, - Masking = true, - Children = new Drawable[] - { - bg = new Box - { - RelativeSizeAxes = Axes.Both, - }, - triangles = new Triangles - { - RelativeSizeAxes = Axes.Both, - TriangleScale = 5, - }, - }, - }, - new Container - { - RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding { Top = 85, Right = WIDTH_PADDING }, - Children = new[] - { - new OsuScrollContainer - { - RelativeSizeAxes = Axes.Both, - Children = new[] - { - sectionsFlow = new SearchContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Direction = FillDirection.Vertical, - LayoutDuration = 200, - LayoutEasing = Easing.OutQuint, - Spacing = new Vector2(0f, 20f), - Padding = new MarginPadding { Vertical = 20, Left = WIDTH_PADDING }, - }, - }, - }, - }, - }, - new Container - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Children = new Drawable[] - { - headerBg = new Box - { - RelativeSizeAxes = Axes.Both, - }, - new FillFlowContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Direction = FillDirection.Vertical, - Spacing = new Vector2(0f, 10f), - Padding = new MarginPadding { Top = 10f, Bottom = 10f, Left = WIDTH_PADDING, Right = WIDTH_PADDING }, - Children = new Drawable[] - { - new OsuSpriteText - { - Text = @"Chat Channels", - Font = OsuFont.GetFont(size: 20), - Shadow = false, - }, - search = new HeaderSearchTextBox { RelativeSizeAxes = Axes.X }, - }, - }, - }, - }, - }; - - search.Current.ValueChanged += term => sectionsFlow.SearchTerm = term.NewValue; - } - - public void UpdateAvailableChannels(IEnumerable channels) - { - Scheduler.Add(() => - { - sectionsFlow.ChildrenEnumerable = new[] - { - new ChannelSection { Channels = channels, }, - }; - - foreach (ChannelSection s in sectionsFlow.Children) - { - foreach (ChannelListItem c in s.ChannelFlow.Children) - { - c.OnRequestJoin = channel => { OnRequestJoin?.Invoke(channel); }; - c.OnRequestLeave = channel => { OnRequestLeave?.Invoke(channel); }; - } - } - }); - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - bg.Colour = colours.Gray3; - triangles.ColourDark = colours.Gray3; - triangles.ColourLight = Color4Extensions.FromHex(@"353535"); - - headerBg.Colour = colours.Gray2.Opacity(0.75f); - } - - protected override void OnFocus(FocusEvent e) - { - search.TakeFocus(); - base.OnFocus(e); - } - - protected override void PopIn() - { - if (Alpha == 0) this.MoveToY(DrawHeight); - - this.FadeIn(transition_duration, Easing.OutQuint); - this.MoveToY(0, transition_duration, Easing.OutQuint); - - search.HoldFocus = true; - base.PopIn(); - } - - protected override void PopOut() - { - this.FadeOut(transition_duration, Easing.InSine); - this.MoveToY(DrawHeight, transition_duration, Easing.InSine); - - search.HoldFocus = false; - base.PopOut(); - } - - private class HeaderSearchTextBox : BasicSearchTextBox - { - [BackgroundDependencyLoader] - private void load() - { - BackgroundFocused = Color4.Black.Opacity(0.2f); - BackgroundUnfocused = Color4.Black.Opacity(0.2f); - } - } - } -} diff --git a/osu.Game/Overlays/Chat/Tabs/ChannelSelectorTabItem.cs b/osu.Game/Overlays/Chat/Tabs/ChannelSelectorTabItem.cs deleted file mode 100644 index e3ede04edd..0000000000 --- a/osu.Game/Overlays/Chat/Tabs/ChannelSelectorTabItem.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.Graphics; -using osu.Game.Online.Chat; - -namespace osu.Game.Overlays.Chat.Tabs -{ - public class ChannelSelectorTabItem : ChannelTabItem - { - public override bool IsRemovable => false; - - public override bool IsSwitchable => false; - - protected override bool IsBoldWhenActive => false; - - public ChannelSelectorTabItem() - : base(new ChannelSelectorTabChannel()) - { - Depth = float.MaxValue; - Width = 45; - - Icon.Alpha = 0; - - Text.Font = Text.Font.With(size: 45); - Text.Truncate = false; - } - - [BackgroundDependencyLoader] - private void load(OsuColour colour) - { - BackgroundInactive = colour.Gray2; - BackgroundActive = colour.Gray3; - } - - public class ChannelSelectorTabChannel : Channel - { - public ChannelSelectorTabChannel() - { - Name = "+"; - Type = ChannelType.System; - } - } - } -} diff --git a/osu.Game/Overlays/Chat/Tabs/ChannelTabControl.cs b/osu.Game/Overlays/Chat/Tabs/ChannelTabControl.cs deleted file mode 100644 index c0de093425..0000000000 --- a/osu.Game/Overlays/Chat/Tabs/ChannelTabControl.cs +++ /dev/null @@ -1,114 +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.Graphics; -using osu.Framework.Graphics.UserInterface; -using osu.Game.Graphics.UserInterface; -using osu.Game.Online.Chat; -using osuTK; -using System; -using System.Linq; -using osu.Framework.Bindables; -using osu.Framework.Graphics.Containers; - -namespace osu.Game.Overlays.Chat.Tabs -{ - public class ChannelTabControl : OsuTabControl - { - public const float SHEAR_WIDTH = 10; - - public Action OnRequestLeave; - - public readonly Bindable ChannelSelectorActive = new Bindable(); - - private readonly ChannelSelectorTabItem selectorTab; - - public ChannelTabControl() - { - Padding = new MarginPadding { Left = 50 }; - - TabContainer.Spacing = new Vector2(-SHEAR_WIDTH, 0); - TabContainer.Masking = false; - - AddTabItem(selectorTab = new ChannelSelectorTabItem()); - - ChannelSelectorActive.BindTo(selectorTab.Active); - } - - protected override void AddTabItem(TabItem item, bool addToDropdown = true) - { - if (item != selectorTab && TabContainer.GetLayoutPosition(selectorTab) < float.MaxValue) - // performTabSort might've made selectorTab's position wonky, fix it - TabContainer.SetLayoutPosition(selectorTab, float.MaxValue); - - ((ChannelTabItem)item).OnRequestClose += channelItem => OnRequestLeave?.Invoke(channelItem.Value); - - base.AddTabItem(item, addToDropdown); - } - - protected override TabItem CreateTabItem(Channel value) - { - switch (value.Type) - { - default: - return new ChannelTabItem(value); - - case ChannelType.PM: - return new PrivateChannelTabItem(value); - } - } - - /// - /// Adds a channel to the ChannelTabControl. - /// The first channel added will automaticly selected. - /// - /// The channel that is going to be added. - public void AddChannel(Channel channel) - { - if (!Items.Contains(channel)) - AddItem(channel); - - Current.Value ??= channel; - } - - /// - /// Removes a channel from the ChannelTabControl. - /// If the selected channel is the one that is being removed, the next available channel will be selected. - /// - /// The channel that is going to be removed. - public void RemoveChannel(Channel channel) - { - RemoveItem(channel); - - if (SelectedTab == null) - SelectChannelSelectorTab(); - } - - public void SelectChannelSelectorTab() => SelectTab(selectorTab); - - protected override void SelectTab(TabItem tab) - { - if (tab is ChannelSelectorTabItem) - { - tab.Active.Value = true; - return; - } - - base.SelectTab(tab); - selectorTab.Active.Value = false; - } - - protected override TabFillFlowContainer CreateTabFlow() => new ChannelTabFillFlowContainer - { - Direction = FillDirection.Full, - RelativeSizeAxes = Axes.Both, - Depth = -1, - Masking = true - }; - - private class ChannelTabFillFlowContainer : TabFillFlowContainer - { - protected override int Compare(Drawable x, Drawable y) => CompareReverseChildID(x, y); - } - } -} diff --git a/osu.Game/Overlays/Chat/Tabs/ChannelTabItem.cs b/osu.Game/Overlays/Chat/Tabs/ChannelTabItem.cs deleted file mode 100644 index 9d2cd8a21d..0000000000 --- a/osu.Game/Overlays/Chat/Tabs/ChannelTabItem.cs +++ /dev/null @@ -1,238 +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.Audio; -using osu.Framework.Audio.Sample; -using osu.Framework.Extensions; -using osu.Framework.Extensions.Color4Extensions; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Effects; -using osu.Framework.Graphics.Shapes; -using osu.Framework.Graphics.Sprites; -using osu.Framework.Graphics.UserInterface; -using osu.Framework.Input.Events; -using osu.Game.Graphics; -using osu.Game.Graphics.Sprites; -using osu.Game.Graphics.UserInterface; -using osu.Game.Online.Chat; -using osuTK; -using osuTK.Graphics; -using osuTK.Input; - -namespace osu.Game.Overlays.Chat.Tabs -{ - public class ChannelTabItem : TabItem - { - protected Color4 BackgroundInactive; - private Color4 backgroundHover; - protected Color4 BackgroundActive; - - public override bool IsRemovable => !Pinned; - - protected readonly SpriteText Text; - protected readonly ClickableContainer CloseButton; - private readonly Box box; - private readonly Box highlightBox; - protected readonly SpriteIcon Icon; - - public Action OnRequestClose; - private readonly Container content; - - protected override Container Content => content; - - private Sample sampleTabSwitched; - - public ChannelTabItem(Channel value) - : base(value) - { - Width = 150; - - RelativeSizeAxes = Axes.Y; - - Anchor = Anchor.BottomLeft; - Origin = Anchor.BottomLeft; - - Shear = new Vector2(ChannelTabControl.SHEAR_WIDTH / ChatOverlay.TAB_AREA_HEIGHT, 0); - - Masking = true; - - InternalChildren = new Drawable[] - { - box = new Box - { - EdgeSmoothness = new Vector2(1, 0), - RelativeSizeAxes = Axes.Both, - }, - highlightBox = new Box - { - Width = 5, - Alpha = 0, - Anchor = Anchor.BottomRight, - Origin = Anchor.BottomRight, - EdgeSmoothness = new Vector2(1, 0), - RelativeSizeAxes = Axes.Y, - }, - content = new Container - { - Shear = new Vector2(-ChannelTabControl.SHEAR_WIDTH / ChatOverlay.TAB_AREA_HEIGHT, 0), - RelativeSizeAxes = Axes.Both, - Children = new Drawable[] - { - Icon = new SpriteIcon - { - Icon = DisplayIcon, - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Colour = Color4.Black, - X = -10, - Alpha = 0.2f, - Size = new Vector2(ChatOverlay.TAB_AREA_HEIGHT), - }, - Text = new OsuSpriteText - { - Origin = Anchor.CentreLeft, - Anchor = Anchor.CentreLeft, - Text = value.ToString(), - Font = OsuFont.GetFont(size: 18), - Padding = new MarginPadding(5) - { - Left = LeftTextPadding, - Right = RightTextPadding, - }, - RelativeSizeAxes = Axes.X, - Truncate = true, - }, - CloseButton = new TabCloseButton - { - Alpha = 0, - Margin = new MarginPadding { Right = 20 }, - Origin = Anchor.CentreRight, - Anchor = Anchor.CentreRight, - Action = delegate - { - if (IsRemovable) OnRequestClose?.Invoke(this); - }, - }, - }, - }, - new HoverSounds() - }; - } - - protected virtual float LeftTextPadding => 5; - - protected virtual float RightTextPadding => IsRemovable ? 40 : 5; - - protected virtual IconUsage DisplayIcon => FontAwesome.Solid.Hashtag; - - protected virtual bool ShowCloseOnHover => true; - - protected virtual bool IsBoldWhenActive => true; - - protected override bool OnHover(HoverEvent e) - { - if (IsRemovable && ShowCloseOnHover) - CloseButton.FadeIn(200, Easing.OutQuint); - - if (!Active.Value) - box.FadeColour(backgroundHover, TRANSITION_LENGTH, Easing.OutQuint); - return true; - } - - protected override void OnHoverLost(HoverLostEvent e) - { - CloseButton.FadeOut(200, Easing.OutQuint); - updateState(); - } - - protected override void OnMouseUp(MouseUpEvent e) - { - switch (e.Button) - { - case MouseButton.Middle: - CloseButton.TriggerClick(); - break; - } - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours, AudioManager audio) - { - BackgroundActive = colours.ChatBlue; - BackgroundInactive = colours.Gray4; - backgroundHover = colours.Gray7; - sampleTabSwitched = audio.Samples.Get($@"UI/{HoverSampleSet.Default.GetDescription()}-select"); - - highlightBox.Colour = colours.Yellow; - } - - protected override void LoadComplete() - { - base.LoadComplete(); - - updateState(); - FinishTransforms(true); - } - - private void updateState() - { - if (Active.Value) - FadeActive(); - else - FadeInactive(); - } - - protected const float TRANSITION_LENGTH = 400; - - private readonly EdgeEffectParameters activateEdgeEffect = new EdgeEffectParameters - { - Type = EdgeEffectType.Shadow, - Radius = 15, - Colour = Color4.Black.Opacity(0.4f), - }; - - private readonly EdgeEffectParameters deactivateEdgeEffect = new EdgeEffectParameters - { - Type = EdgeEffectType.Shadow, - Radius = 10, - Colour = Color4.Black.Opacity(0.2f), - }; - - protected virtual void FadeActive() - { - this.ResizeHeightTo(1.1f, TRANSITION_LENGTH, Easing.OutQuint); - - TweenEdgeEffectTo(activateEdgeEffect, TRANSITION_LENGTH); - - box.FadeColour(BackgroundActive, TRANSITION_LENGTH, Easing.OutQuint); - highlightBox.FadeIn(TRANSITION_LENGTH, Easing.OutQuint); - - if (IsBoldWhenActive) Text.Font = Text.Font.With(weight: FontWeight.Bold); - } - - protected virtual void FadeInactive() - { - this.ResizeHeightTo(1, TRANSITION_LENGTH, Easing.OutQuint); - - TweenEdgeEffectTo(deactivateEdgeEffect, TRANSITION_LENGTH); - - box.FadeColour(IsHovered ? backgroundHover : BackgroundInactive, TRANSITION_LENGTH, Easing.OutQuint); - highlightBox.FadeOut(TRANSITION_LENGTH, Easing.OutQuint); - - Text.Font = Text.Font.With(weight: FontWeight.Medium); - } - - protected override void OnActivated() - { - if (IsLoaded) - sampleTabSwitched?.Play(); - - updateState(); - } - - protected override void OnDeactivated() => updateState(); - } -} diff --git a/osu.Game/Overlays/Chat/Tabs/PrivateChannelTabItem.cs b/osu.Game/Overlays/Chat/Tabs/PrivateChannelTabItem.cs deleted file mode 100644 index d01aec630e..0000000000 --- a/osu.Game/Overlays/Chat/Tabs/PrivateChannelTabItem.cs +++ /dev/null @@ -1,95 +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.Linq; -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.Online.Chat; -using osu.Game.Users.Drawables; -using osuTK; - -namespace osu.Game.Overlays.Chat.Tabs -{ - public class PrivateChannelTabItem : ChannelTabItem - { - protected override IconUsage DisplayIcon => FontAwesome.Solid.At; - - public PrivateChannelTabItem(Channel value) - : base(value) - { - if (value.Type != ChannelType.PM) - throw new ArgumentException("Argument value needs to have the targettype user!"); - - DrawableAvatar avatar; - - AddRange(new Drawable[] - { - new Container - { - RelativeSizeAxes = Axes.Y, - AutoSizeAxes = Axes.X, - Margin = new MarginPadding - { - Horizontal = 3 - }, - Origin = Anchor.BottomLeft, - Anchor = Anchor.BottomLeft, - Children = new Drawable[] - { - new CircularContainer - { - Scale = new Vector2(0.95f), - Size = new Vector2(ChatOverlay.TAB_AREA_HEIGHT), - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Masking = true, - Child = new DelayedLoadWrapper(avatar = new DrawableAvatar(value.Users.First()) - { - RelativeSizeAxes = Axes.Both - }) - { - RelativeSizeAxes = Axes.Both, - } - }, - } - }, - }); - - avatar.OnLoadComplete += d => d.FadeInFromZero(300, Easing.OutQuint); - } - - protected override float LeftTextPadding => base.LeftTextPadding + ChatOverlay.TAB_AREA_HEIGHT; - - protected override bool ShowCloseOnHover => false; - - protected override void FadeActive() - { - base.FadeActive(); - - this.ResizeWidthTo(200, TRANSITION_LENGTH, Easing.OutQuint); - CloseButton.FadeIn(TRANSITION_LENGTH, Easing.OutQuint); - } - - protected override void FadeInactive() - { - base.FadeInactive(); - - this.ResizeWidthTo(ChatOverlay.TAB_AREA_HEIGHT + 10, TRANSITION_LENGTH, Easing.OutQuint); - CloseButton.FadeOut(TRANSITION_LENGTH, Easing.OutQuint); - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - var user = Value.Users.First(); - - BackgroundActive = user.Colour != null ? Color4Extensions.FromHex(user.Colour) : colours.BlueDark; - BackgroundInactive = BackgroundActive.Darken(0.5f); - } - } -} diff --git a/osu.Game/Overlays/Chat/Tabs/TabCloseButton.cs b/osu.Game/Overlays/Chat/Tabs/TabCloseButton.cs deleted file mode 100644 index 178afda5ac..0000000000 --- a/osu.Game/Overlays/Chat/Tabs/TabCloseButton.cs +++ /dev/null @@ -1,55 +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.Graphics; -using osu.Framework.Graphics.Sprites; -using osu.Framework.Input.Events; -using osu.Game.Graphics.Containers; -using osuTK; -using osuTK.Graphics; - -namespace osu.Game.Overlays.Chat.Tabs -{ - public class TabCloseButton : OsuClickableContainer - { - private readonly SpriteIcon icon; - - public TabCloseButton() - { - Size = new Vector2(20); - - Child = icon = new SpriteIcon - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Scale = new Vector2(0.75f), - Icon = FontAwesome.Solid.TimesCircle, - RelativeSizeAxes = Axes.Both, - }; - } - - protected override bool OnMouseDown(MouseDownEvent e) - { - icon.ScaleTo(0.5f, 1000, Easing.OutQuint); - return base.OnMouseDown(e); - } - - protected override void OnMouseUp(MouseUpEvent e) - { - icon.ScaleTo(0.75f, 1000, Easing.OutElastic); - base.OnMouseUp(e); - } - - protected override bool OnHover(HoverEvent e) - { - icon.FadeColour(Color4.Red, 200, Easing.OutQuint); - return base.OnHover(e); - } - - protected override void OnHoverLost(HoverLostEvent e) - { - icon.FadeColour(Color4.White, 200, Easing.OutQuint); - base.OnHoverLost(e); - } - } -} diff --git a/osu.Game/Overlays/ChatOverlay.cs b/osu.Game/Overlays/ChatOverlay.cs deleted file mode 100644 index 034670cf37..0000000000 --- a/osu.Game/Overlays/ChatOverlay.cs +++ /dev/null @@ -1,525 +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.Collections.Generic; -using System.Collections.Specialized; -using System.Diagnostics; -using System.Linq; -using osuTK; -using osuTK.Graphics; -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.UserInterface; -using osu.Framework.Input.Events; -using osu.Game.Configuration; -using osu.Game.Graphics; -using osu.Game.Graphics.Containers; -using osu.Game.Graphics.UserInterface; -using osu.Game.Online.Chat; -using osu.Game.Overlays.Chat; -using osu.Game.Overlays.Chat.Selection; -using osu.Game.Overlays.Chat.Tabs; -using osuTK.Input; -using osu.Framework.Graphics.Sprites; -using osu.Framework.Graphics.Textures; -using osu.Framework.Input; -using osu.Framework.Input.Bindings; -using osu.Framework.Localisation; -using osu.Game.Localisation; -using osu.Game.Online; - -namespace osu.Game.Overlays -{ - public class ChatOverlay : OsuFocusedOverlayContainer, INamedOverlayComponent, IKeyBindingHandler - { - public string IconTexture => "Icons/Hexacons/messaging"; - public LocalisableString Title => ChatStrings.HeaderTitle; - public LocalisableString Description => ChatStrings.HeaderDescription; - - private const float text_box_height = 60; - private const float channel_selection_min_height = 0.3f; - - [Resolved] - private ChannelManager channelManager { get; set; } - - private Container currentChannelContainer; - - private readonly List loadedChannels = new List(); - - private LoadingSpinner loading; - - private FocusedTextBox textBox; - - private const int transition_length = 500; - - public const float DEFAULT_HEIGHT = 0.4f; - - public const float TAB_AREA_HEIGHT = 50; - - protected ChannelTabControl ChannelTabControl; - - protected virtual ChannelTabControl CreateChannelTabControl() => new ChannelTabControl(); - - private Container chatContainer; - private TabsArea tabsArea; - private Box chatBackground; - private Box tabBackground; - - public Bindable ChatHeight { get; set; } - - private Container channelSelectionContainer; - protected ChannelSelectionOverlay ChannelSelectionOverlay; - - private readonly IBindableList availableChannels = new BindableList(); - private readonly IBindableList joinedChannels = new BindableList(); - private readonly Bindable currentChannel = new Bindable(); - - public override bool Contains(Vector2 screenSpacePos) => chatContainer.ReceivePositionalInputAt(screenSpacePos) - || (ChannelSelectionOverlay.State.Value == Visibility.Visible && ChannelSelectionOverlay.ReceivePositionalInputAt(screenSpacePos)); - - public ChatOverlay() - { - RelativeSizeAxes = Axes.Both; - RelativePositionAxes = Axes.Both; - Anchor = Anchor.BottomLeft; - Origin = Anchor.BottomLeft; - } - - [BackgroundDependencyLoader] - private void load(OsuConfigManager config, OsuColour colours, TextureStore textures) - { - const float padding = 5; - - Children = new Drawable[] - { - channelSelectionContainer = new Container - { - RelativeSizeAxes = Axes.Both, - Height = 1f - DEFAULT_HEIGHT, - Masking = true, - Children = new[] - { - ChannelSelectionOverlay = new ChannelSelectionOverlay - { - RelativeSizeAxes = Axes.Both, - }, - }, - }, - chatContainer = new Container - { - Name = @"chat container", - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - RelativeSizeAxes = Axes.Both, - Height = DEFAULT_HEIGHT, - Children = new[] - { - new Container - { - Name = @"chat area", - RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding { Top = TAB_AREA_HEIGHT }, - Children = new Drawable[] - { - chatBackground = new Box - { - RelativeSizeAxes = Axes.Both, - }, - new OnlineViewContainer("Sign in to chat") - { - RelativeSizeAxes = Axes.Both, - Children = new Drawable[] - { - currentChannelContainer = new Container - { - RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding - { - Bottom = text_box_height - }, - }, - new Container - { - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - RelativeSizeAxes = Axes.X, - Height = text_box_height, - Padding = new MarginPadding - { - Top = padding * 2, - Bottom = padding * 2, - Left = ChatLine.LEFT_PADDING + padding * 2, - Right = padding * 2, - }, - Children = new Drawable[] - { - textBox = new FocusedTextBox - { - RelativeSizeAxes = Axes.Both, - Height = 1, - PlaceholderText = Resources.Localisation.Web.ChatStrings.InputPlaceholder, - ReleaseFocusOnCommit = false, - HoldFocus = true, - } - } - }, - loading = new LoadingSpinner(), - }, - } - } - }, - tabsArea = new TabsArea - { - Children = new Drawable[] - { - tabBackground = new Box - { - RelativeSizeAxes = Axes.Both, - Colour = Color4.Black, - }, - new Sprite - { - Texture = textures.Get(IconTexture), - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Size = new Vector2(OverlayTitle.ICON_SIZE), - Margin = new MarginPadding { Left = 10 }, - }, - ChannelTabControl = CreateChannelTabControl().With(d => - { - d.Anchor = Anchor.BottomLeft; - d.Origin = Anchor.BottomLeft; - d.RelativeSizeAxes = Axes.Both; - d.OnRequestLeave = channelManager.LeaveChannel; - d.IsSwitchable = true; - }), - } - }, - }, - }, - }; - - availableChannels.BindTo(channelManager.AvailableChannels); - joinedChannels.BindTo(channelManager.JoinedChannels); - currentChannel.BindTo(channelManager.CurrentChannel); - - textBox.OnCommit += postMessage; - - ChannelTabControl.Current.ValueChanged += current => currentChannel.Value = current.NewValue; - ChannelTabControl.ChannelSelectorActive.ValueChanged += active => ChannelSelectionOverlay.State.Value = active.NewValue ? Visibility.Visible : Visibility.Hidden; - ChannelSelectionOverlay.State.ValueChanged += state => - { - // Propagate the visibility state to ChannelSelectorActive - ChannelTabControl.ChannelSelectorActive.Value = state.NewValue == Visibility.Visible; - - if (state.NewValue == Visibility.Visible) - { - textBox.HoldFocus = false; - if (1f - ChatHeight.Value < channel_selection_min_height) - this.TransformBindableTo(ChatHeight, 1f - channel_selection_min_height, 800, Easing.OutQuint); - } - else - textBox.HoldFocus = true; - }; - - ChannelSelectionOverlay.OnRequestJoin = channel => channelManager.JoinChannel(channel); - ChannelSelectionOverlay.OnRequestLeave = channelManager.LeaveChannel; - - ChatHeight = config.GetBindable(OsuSetting.ChatDisplayHeight); - ChatHeight.BindValueChanged(height => - { - chatContainer.Height = height.NewValue; - channelSelectionContainer.Height = 1f - height.NewValue; - tabBackground.FadeTo(height.NewValue == 1f ? 1f : 0.8f, 200); - }, true); - - chatBackground.Colour = colours.ChatBlue; - - loading.Show(); - - // This is a relatively expensive (and blocking) operation. - // Scheduling it ensures that it won't be performed unless the user decides to open chat. - // TODO: Refactor OsuFocusedOverlayContainer / OverlayContainer to support delayed content loading. - Schedule(() => - { - // TODO: consider scheduling bindable callbacks to not perform when overlay is not present. - joinedChannels.BindCollectionChanged(joinedChannelsChanged, true); - availableChannels.BindCollectionChanged(availableChannelsChanged, true); - currentChannel.BindValueChanged(currentChannelChanged, true); - }); - } - - private void currentChannelChanged(ValueChangedEvent e) - { - if (e.NewValue == null) - { - textBox.Current.Disabled = true; - currentChannelContainer.Clear(false); - ChannelSelectionOverlay.Show(); - return; - } - - if (e.NewValue is ChannelSelectorTabItem.ChannelSelectorTabChannel) - return; - - textBox.Current.Disabled = e.NewValue.ReadOnly; - - if (ChannelTabControl.Current.Value != e.NewValue) - Scheduler.Add(() => ChannelTabControl.Current.Value = e.NewValue); - - var loaded = loadedChannels.Find(d => d.Channel == e.NewValue); - - if (loaded == null) - { - currentChannelContainer.FadeOut(500, Easing.OutQuint); - loading.Show(); - - loaded = new DrawableChannel(e.NewValue); - loadedChannels.Add(loaded); - LoadComponentAsync(loaded, l => - { - if (currentChannel.Value != e.NewValue) - return; - - // check once more to ensure the channel hasn't since been removed from the loaded channels list (may have been left by some automated means). - if (!loadedChannels.Contains(loaded)) - return; - - loading.Hide(); - - currentChannelContainer.Clear(false); - currentChannelContainer.Add(loaded); - currentChannelContainer.FadeIn(500, Easing.OutQuint); - }); - } - else - { - currentChannelContainer.Clear(false); - currentChannelContainer.Add(loaded); - } - - // mark channel as read when channel switched - if (e.NewValue.Messages.Any()) - channelManager.MarkChannelAsRead(e.NewValue); - } - - /// - /// Highlights a certain message in the specified channel. - /// - /// The message to highlight. - /// The channel containing the message. - public void HighlightMessage(Message message, Channel channel) - { - Debug.Assert(channel.Id == message.ChannelId); - - if (currentChannel.Value?.Id != channel.Id) - { - if (!channel.Joined.Value) - channel = channelManager.JoinChannel(channel); - - currentChannel.Value = channel; - } - - channel.HighlightedMessage.Value = message; - - Show(); - } - - private float startDragChatHeight; - private bool isDragging; - - protected override bool OnDragStart(DragStartEvent e) - { - isDragging = tabsArea.IsHovered; - - if (!isDragging) - return base.OnDragStart(e); - - startDragChatHeight = ChatHeight.Value; - return true; - } - - protected override void OnDrag(DragEvent e) - { - if (isDragging) - { - float targetChatHeight = startDragChatHeight - (e.MousePosition.Y - e.MouseDownPosition.Y) / Parent.DrawSize.Y; - - // If the channel selection screen is shown, mind its minimum height - if (ChannelSelectionOverlay.State.Value == Visibility.Visible && targetChatHeight > 1f - channel_selection_min_height) - targetChatHeight = 1f - channel_selection_min_height; - - ChatHeight.Value = targetChatHeight; - } - } - - protected override void OnDragEnd(DragEndEvent e) - { - isDragging = false; - base.OnDragEnd(e); - } - - private void selectTab(int index) - { - var channel = ChannelTabControl.Items - .Where(tab => !(tab is ChannelSelectorTabItem.ChannelSelectorTabChannel)) - .ElementAtOrDefault(index); - if (channel != null) - ChannelTabControl.Current.Value = channel; - } - - protected override bool OnKeyDown(KeyDownEvent e) - { - if (e.AltPressed) - { - switch (e.Key) - { - case Key.Number1: - case Key.Number2: - case Key.Number3: - case Key.Number4: - case Key.Number5: - case Key.Number6: - case Key.Number7: - case Key.Number8: - case Key.Number9: - selectTab((int)e.Key - (int)Key.Number1); - return true; - - case Key.Number0: - selectTab(9); - return true; - } - } - - return base.OnKeyDown(e); - } - - public bool OnPressed(KeyBindingPressEvent e) - { - switch (e.Action) - { - case PlatformAction.TabNew: - ChannelTabControl.SelectChannelSelectorTab(); - return true; - - case PlatformAction.TabRestore: - channelManager.JoinLastClosedChannel(); - return true; - - case PlatformAction.DocumentClose: - channelManager.LeaveChannel(currentChannel.Value); - return true; - } - - return false; - } - - public void OnReleased(KeyBindingReleaseEvent e) - { - } - - public override bool AcceptsFocus => true; - - protected override void OnFocus(FocusEvent e) - { - // this is necessary as textbox is masked away and therefore can't get focus :( - textBox.TakeFocus(); - base.OnFocus(e); - } - - protected override void PopIn() - { - this.MoveToY(0, transition_length, Easing.OutQuint); - this.FadeIn(transition_length, Easing.OutQuint); - - textBox.HoldFocus = true; - - base.PopIn(); - } - - protected override void PopOut() - { - this.MoveToY(Height, transition_length, Easing.InSine); - this.FadeOut(transition_length, Easing.InSine); - - ChannelSelectionOverlay.Hide(); - - textBox.HoldFocus = false; - base.PopOut(); - } - - private void joinedChannelsChanged(object sender, NotifyCollectionChangedEventArgs args) - { - switch (args.Action) - { - case NotifyCollectionChangedAction.Add: - foreach (Channel channel in args.NewItems.Cast()) - { - if (channel.Type != ChannelType.Multiplayer) - ChannelTabControl.AddChannel(channel); - } - - break; - - case NotifyCollectionChangedAction.Remove: - foreach (Channel channel in args.OldItems.Cast()) - { - if (!ChannelTabControl.Items.Contains(channel)) - continue; - - ChannelTabControl.RemoveChannel(channel); - - var loaded = loadedChannels.Find(c => c.Channel == channel); - - if (loaded != null) - { - // Because the container is only cleared in the async load callback of a new channel, it is forcefully cleared - // to ensure that the previous channel doesn't get updated after it's disposed - loadedChannels.Remove(loaded); - currentChannelContainer.Remove(loaded); - loaded.Dispose(); - } - } - - break; - } - } - - private void availableChannelsChanged(object sender, NotifyCollectionChangedEventArgs args) - { - ChannelSelectionOverlay.UpdateAvailableChannels(availableChannels); - } - - private void postMessage(TextBox textBox, bool newText) - { - string text = textBox.Text.Trim(); - - if (string.IsNullOrWhiteSpace(text)) - return; - - if (text[0] == '/') - channelManager.PostCommand(text.Substring(1)); - else - channelManager.PostMessage(text); - - textBox.Text = string.Empty; - } - - private class TabsArea : Container - { - // IsHovered is used - public override bool HandlePositionalInput => true; - - public TabsArea() - { - Name = @"tabs area"; - RelativeSizeAxes = Axes.X; - Height = TAB_AREA_HEIGHT; - } - } - } -} From 03deb336b280aa3ccb86d0333186b871f17a8b95 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 30 May 2022 04:34:08 +0900 Subject: [PATCH 261/395] Fix some more chat tests failing intermittently MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ``` TearDown : osu.Framework.Testing.Drawables.Steps.AssertButton+TracedException : Channel 1 ready --TearDown at osu.Framework.Threading.ScheduledDelegate.RunTaskInternal() at osu.Framework.Threading.Scheduler.Update() at osu.Framework.Graphics.Drawable.UpdateSubTree() at osu.Framework.Graphics.Containers.CompositeDrawable.UpdateSubTree() at osu.Framework.Graphics.Containers.CompositeDrawable.UpdateSubTree() at osu.Framework.Graphics.Containers.CompositeDrawable.UpdateSubTree() at osu.Framework.Graphics.Containers.CompositeDrawable.UpdateSubTree() at osu.Framework.Graphics.Containers.CompositeDrawable.UpdateSubTree() at osu.Framework.Graphics.Containers.CompositeDrawable.UpdateSubTree() at osu.Framework.Graphics.Containers.CompositeDrawable.UpdateSubTree() at osu.Framework.Graphics.Containers.CompositeDrawable.UpdateSubTree() at osu.Framework.Graphics.Containers.CompositeDrawable.UpdateSubTree() at osu.Framework.Graphics.Containers.CompositeDrawable.UpdateSubTree() at osu.Framework.Graphics.Containers.CompositeDrawable.UpdateSubTree() at osu.Framework.Graphics.Containers.CompositeDrawable.UpdateSubTree() at osu.Framework.Graphics.Containers.CompositeDrawable.UpdateSubTree() at osu.Framework.Graphics.Containers.CompositeDrawable.UpdateSubTree() at osu.Framework.Graphics.Containers.CompositeDrawable.UpdateSubTree() at osu.Framework.Platform.GameHost.UpdateFrame() at osu.Framework.Threading.GameThread.processFrame() at osu.Framework.Threading.GameThread.RunSingleFrame() at osu.Framework.Threading.GameThread.g__runWork|66_0() at System.Threading.Thread.StartHelper.Callback(Object state) at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state) ----- One or more child tests had errors Exception doesn't have a stacktrace [runtime] 2022-05-29 19:29:09 [verbose]: 💨 Class: TestSceneChatOverlayV2 [runtime] 2022-05-29 19:29:09 [verbose]: 🔶 Test: TestSlowLoadingChannel [runtime] 2022-05-29 19:29:09 [verbose]: Chat is now polling every 60000 ms [runtime] 2022-05-29 19:29:09 [verbose]: 🔸 Step #1 Setup request handler [runtime] 2022-05-29 19:29:09 [verbose]: 🔸 Step #2 Add test channels [runtime] 2022-05-29 19:29:09 [verbose]: 🔸 Step #3 Show overlay (slow-loading) [runtime] 2022-05-29 19:29:09 [verbose]: 🔸 Step #4 Join channel 1 [runtime] 2022-05-29 19:29:09 [verbose]: 🔸 Step #5 Select channel 1 [runtime] 2022-05-29 19:29:09 [verbose]: 🔸 Step #6 Channel 1 loading [runtime] 2022-05-29 19:29:09 [verbose]: 🔸 Step #7 Join channel 2 [runtime] 2022-05-29 19:29:09 [verbose]: 🔸 Step #8 Select channel 2 [runtime] 2022-05-29 19:29:09 [verbose]: 🔸 Step #9 Channel 2 loading [runtime] 2022-05-29 19:29:09 [verbose]: 🔸 Step #10 Finish channel 1 load [runtime] 2022-05-29 19:29:09 [verbose]: 🔸 Step #11 Channel 1 ready [runtime] 2022-05-29 19:29:09 [verbose]: 💥 Failed [runtime] 2022-05-29 19:29:09 [verbose]: ⏳ Currently loading components (2) [runtime] 2022-05-29 19:29:09 [verbose]: TestSceneChatOverlayV2+SlowLoadingDrawableChannel [runtime] 2022-05-29 19:29:09 [verbose]: - thread: ThreadedTaskScheduler (LoadComponentsAsync (standard)) [runtime] 2022-05-29 19:29:09 [verbose]: - state: Loading [runtime] 2022-05-29 19:29:09 [verbose]: TestSceneChatOverlayV2+SlowLoadingDrawableChannel [runtime] 2022-05-29 19:29:09 [verbose]: - thread: ThreadedTaskScheduler (LoadComponentsAsync (standard)) [runtime] 2022-05-29 19:29:09 [verbose]: - state: Ready [runtime] 2022-05-29 19:29:09 [verbose]: 🧵 Task schedulers [runtime] 2022-05-29 19:29:09 [verbose]: LoadComponentsAsync (standard) concurrency:4 running:1 pending:0 [runtime] 2022-05-29 19:29:09 [verbose]: LoadComponentsAsync (long load) concurrency:4 running:0 pending:0 [runtime] 2022-05-29 19:29:09 [verbose]: 🎱 Thread pool [runtime] 2022-05-29 19:29:09 [verbose]: worker: min 32 max 32,767 available 32,765 [runtime] 2022-05-29 19:29:09 [verbose]: completion: min 32 max 1,000 available 1,000 ``` --- osu.Game.Tests/Visual/Online/TestSceneChatOverlayV2.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneChatOverlayV2.cs b/osu.Game.Tests/Visual/Online/TestSceneChatOverlayV2.cs index b333eee21b..ba4db50a11 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneChatOverlayV2.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneChatOverlayV2.cs @@ -404,22 +404,22 @@ namespace osu.Game.Tests.Visual.Online }); AddStep("Join channel 1", () => channelManager.JoinChannel(testChannel1)); AddStep("Select channel 1", () => clickDrawable(getChannelListItem(testChannel1))); - AddAssert("Channel 1 loading", () => !channelIsVisible && chatOverlay.GetSlowLoadingChannel(testChannel1).LoadState == LoadState.Loading); + AddUntilStep("Channel 1 loading", () => !channelIsVisible && chatOverlay.GetSlowLoadingChannel(testChannel1).LoadState == LoadState.Loading); AddStep("Join channel 2", () => channelManager.JoinChannel(testChannel2)); AddStep("Select channel 2", () => clickDrawable(getChannelListItem(testChannel2))); - AddAssert("Channel 2 loading", () => !channelIsVisible && chatOverlay.GetSlowLoadingChannel(testChannel2).LoadState == LoadState.Loading); + AddUntilStep("Channel 2 loading", () => !channelIsVisible && chatOverlay.GetSlowLoadingChannel(testChannel2).LoadState == LoadState.Loading); AddStep("Finish channel 1 load", () => chatOverlay.GetSlowLoadingChannel(testChannel1).LoadEvent.Set()); - AddAssert("Channel 1 ready", () => chatOverlay.GetSlowLoadingChannel(testChannel1).LoadState == LoadState.Ready); + AddUntilStep("Channel 1 ready", () => chatOverlay.GetSlowLoadingChannel(testChannel1).LoadState == LoadState.Ready); AddAssert("Channel 1 not displayed", () => !channelIsVisible); AddStep("Finish channel 2 load", () => chatOverlay.GetSlowLoadingChannel(testChannel2).LoadEvent.Set()); - AddAssert("Channel 2 loaded", () => chatOverlay.GetSlowLoadingChannel(testChannel2).IsLoaded); + AddUntilStep("Channel 2 loaded", () => chatOverlay.GetSlowLoadingChannel(testChannel2).IsLoaded); waitForChannel2Visible(); AddStep("Select channel 1", () => clickDrawable(getChannelListItem(testChannel1))); - AddAssert("Channel 1 loaded", () => chatOverlay.GetSlowLoadingChannel(testChannel1).IsLoaded); + AddUntilStep("Channel 1 loaded", () => chatOverlay.GetSlowLoadingChannel(testChannel1).IsLoaded); waitForChannel1Visible(); } From 45841673f6704da52cd689836950c8967e0cae57 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 30 May 2022 15:44:16 +0900 Subject: [PATCH 262/395] Update `OnlineLookupCache` to use async version of `Perform` call --- osu.Game/Database/OnlineLookupCache.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Database/OnlineLookupCache.cs b/osu.Game/Database/OnlineLookupCache.cs index 2f98aef58a..506103a2c0 100644 --- a/osu.Game/Database/OnlineLookupCache.cs +++ b/osu.Game/Database/OnlineLookupCache.cs @@ -91,7 +91,7 @@ namespace osu.Game.Database } } - private void performLookup() + private async Task performLookup() { // contains at most 50 unique IDs from tasks, which is used to perform the lookup. var nextTaskBatch = new Dictionary>>(); @@ -127,7 +127,7 @@ namespace osu.Game.Database // rather than queueing, we maintain our own single-threaded request stream. // todo: we probably want retry logic here. - api.Perform(request); + await api.PerformAsync(request).ConfigureAwait(false); finishPendingTask(); From a2a057440e87e5f494a8248f07f851e3a505d460 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 30 May 2022 16:05:32 +0900 Subject: [PATCH 263/395] Fail requests taretting the fake API with a more deliberate exception I think this feels better than relying on some other method to throw an exception. --- osu.Game/Online/API/DummyAPIAccess.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/osu.Game/Online/API/DummyAPIAccess.cs b/osu.Game/Online/API/DummyAPIAccess.cs index f292e95bd1..8614cc79cb 100644 --- a/osu.Game/Online/API/DummyAPIAccess.cs +++ b/osu.Game/Online/API/DummyAPIAccess.cs @@ -65,9 +65,7 @@ namespace osu.Game.Online.API { if (HandleRequest?.Invoke(request) != true) { - // this will fail due to not receiving an APIAccess, and trigger a failure on the request. - // this is intended - any request in testing that needs non-failures should use HandleRequest. - request.Perform(this); + request.Fail(new InvalidOperationException($@"{nameof(DummyAPIAccess)} cannot process this request.")); } } From c18dd8c8fb29fb84c9459a3bed7bb58e429e257e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 30 May 2022 16:32:44 +0900 Subject: [PATCH 264/395] Ensure `Queue` operations on `DummyAPIAccess` are performed on the update thread --- osu.Game/Online/API/DummyAPIAccess.cs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/osu.Game/Online/API/DummyAPIAccess.cs b/osu.Game/Online/API/DummyAPIAccess.cs index 8614cc79cb..d3707d977c 100644 --- a/osu.Game/Online/API/DummyAPIAccess.cs +++ b/osu.Game/Online/API/DummyAPIAccess.cs @@ -63,10 +63,13 @@ namespace osu.Game.Online.API public virtual void Queue(APIRequest request) { - if (HandleRequest?.Invoke(request) != true) + Schedule(() => { - request.Fail(new InvalidOperationException($@"{nameof(DummyAPIAccess)} cannot process this request.")); - } + if (HandleRequest?.Invoke(request) != true) + { + request.Fail(new InvalidOperationException($@"{nameof(DummyAPIAccess)} cannot process this request.")); + } + }); } public void Perform(APIRequest request) => HandleRequest?.Invoke(request); From f935f034c2a573bb5002b11d5f70b0987b2786a1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 30 May 2022 16:33:07 +0900 Subject: [PATCH 265/395] Ensure request handling for `OnlinePlayTestScene` runs in a scheduled fashion --- .../Visual/OnlinePlay/OnlinePlayTestScene.cs | 20 ++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestScene.cs b/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestScene.cs index b6a347a896..32e42a945d 100644 --- a/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestScene.cs +++ b/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestScene.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Threading.Tasks; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -67,7 +68,24 @@ namespace osu.Game.Tests.Visual.OnlinePlay // To get around this, the BeatmapManager is looked up from the dependencies provided to the children of the test scene instead. var beatmapManager = dependencies.Get(); - ((DummyAPIAccess)API).HandleRequest = request => handler.HandleRequest(request, API.LocalUser.Value, beatmapManager); + ((DummyAPIAccess)API).HandleRequest = request => + { + TaskCompletionSource tcs = new TaskCompletionSource(); + + // Because some of the handlers use realm, we need to ensure the game is still alive when firing. + // If we don't, a stray `PerformAsync` could hit an `ObjectDisposedException` if running too late. + Scheduler.Add(() => + { + bool result = handler.HandleRequest(request, API.LocalUser.Value, beatmapManager); + tcs.SetResult(result); + }, false); + +#pragma warning disable RS0030 + // We can't GetResultSafely() here (will fail with "Can't use GetResultSafely from inside an async operation."), but Wait is safe enough due to + // the task being a TaskCompletionSource. + return tcs.Task.Result; +#pragma warning restore RS0030 + }; }); /// From dea7a27553a25caab9104b2a78fa2abc548ce6e1 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 30 May 2022 17:18:29 +0900 Subject: [PATCH 266/395] Adjust wording, add text for capable state --- .../Overlays/Settings/Sections/Graphics/LayoutSettings.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs b/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs index 981f5dbac3..acbcdfcf76 100644 --- a/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs @@ -234,15 +234,15 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics { case FullscreenCapability.Unknown: if (host.Window is WindowsWindow) - windowModeDropdown.WarningText = "Checking for exclusive fullscreen..."; + windowModeDropdown.WarningText = "Checking for fullscreen capabilities..."; break; case FullscreenCapability.Capable: - windowModeDropdown.WarningText = default; + windowModeDropdown.WarningText = "osu! is running exclusive fullscreen, guaranteeing low latency!"; break; case FullscreenCapability.Incapable: - windowModeDropdown.WarningText = "Unable to enter exclusive fullscreen. You'll still experience some input latency."; + windowModeDropdown.WarningText = "Unable to run exclusive fullscreen. You'll still experience some input latency."; break; } } From 5f9a69e5c2f060cbcc2143f53cc7938953a94509 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 30 May 2022 17:19:03 +0900 Subject: [PATCH 267/395] Add localisation --- .../Localisation/LayoutSettingsStrings.cs | 29 +++++++++++++++++++ .../Sections/Graphics/LayoutSettings.cs | 6 ++-- 2 files changed, 32 insertions(+), 3 deletions(-) create mode 100644 osu.Game/Localisation/LayoutSettingsStrings.cs diff --git a/osu.Game/Localisation/LayoutSettingsStrings.cs b/osu.Game/Localisation/LayoutSettingsStrings.cs new file mode 100644 index 0000000000..5ac28f19b3 --- /dev/null +++ b/osu.Game/Localisation/LayoutSettingsStrings.cs @@ -0,0 +1,29 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Localisation; + +namespace osu.Game.Localisation +{ + public static class LayoutSettingsStrings + { + private const string prefix = @"osu.Game.Resources.Localisation.LayoutSettings"; + + /// + /// "Checking for fullscreen capabilities..." + /// + public static LocalisableString CheckingForFullscreenCapabilities => new TranslatableString(getKey(@"checking_for_fullscreen_capabilities"), @"Checking for fullscreen capabilities..."); + + /// + /// "osu! is running exclusive fullscreen, guaranteeing low latency!" + /// + public static LocalisableString OsuIsRunningExclusiveFullscreen => new TranslatableString(getKey(@"osu_is_running_exclusive_fullscreen"), @"osu! is running exclusive fullscreen, guaranteeing low latency!"); + + /// + /// "Unable to run exclusive fullscreen. You'll still experience some input latency." + /// + public static LocalisableString UnableToRunExclusiveFullscreen => new TranslatableString(getKey(@"unable_to_run_exclusive_fullscreen"), @"Unable to run exclusive fullscreen. You'll still experience some input latency."); + + private static string getKey(string key) => $@"{prefix}:{key}"; + } +} \ No newline at end of file diff --git a/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs b/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs index acbcdfcf76..69a50c19b4 100644 --- a/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs @@ -234,15 +234,15 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics { case FullscreenCapability.Unknown: if (host.Window is WindowsWindow) - windowModeDropdown.WarningText = "Checking for fullscreen capabilities..."; + windowModeDropdown.WarningText = LayoutSettingsStrings.CheckingForFullscreenCapabilities; break; case FullscreenCapability.Capable: - windowModeDropdown.WarningText = "osu! is running exclusive fullscreen, guaranteeing low latency!"; + windowModeDropdown.WarningText = LayoutSettingsStrings.OsuIsRunningExclusiveFullscreen; break; case FullscreenCapability.Incapable: - windowModeDropdown.WarningText = "Unable to run exclusive fullscreen. You'll still experience some input latency."; + windowModeDropdown.WarningText = LayoutSettingsStrings.UnableToRunExclusiveFullscreen; break; } } From f65d2db77f4374d57694f1e15583dfa95f0575a9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 30 May 2022 17:54:09 +0900 Subject: [PATCH 268/395] Remove "V2" suffix from `ChatOverlay` components --- osu.Game.Tests/Visual/Navigation/TestSceneOsuGame.cs | 2 +- .../Visual/Navigation/TestScenePerformFromScreen.cs | 4 ++-- ...{TestSceneChatOverlayV2.cs => TestSceneChatOverlay.cs} | 8 ++++---- osu.Game.Tests/Visual/Online/TestSceneMessageNotifier.cs | 2 +- osu.Game/Configuration/OsuConfigManager.cs | 2 +- osu.Game/Online/Chat/MessageNotifier.cs | 4 ++-- osu.Game/OsuGame.cs | 4 ++-- osu.Game/Overlays/{ChatOverlayV2.cs => ChatOverlay.cs} | 4 ++-- .../Profile/Header/Components/MessageUserButton.cs | 2 +- osu.Game/Overlays/Toolbar/ToolbarChatButton.cs | 2 +- 10 files changed, 17 insertions(+), 17 deletions(-) rename osu.Game.Tests/Visual/Online/{TestSceneChatOverlayV2.cs => TestSceneChatOverlay.cs} (99%) rename osu.Game/Overlays/{ChatOverlayV2.cs => ChatOverlay.cs} (98%) diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneOsuGame.cs b/osu.Game.Tests/Visual/Navigation/TestSceneOsuGame.cs index c8ea692bb2..e4871f611e 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSceneOsuGame.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSceneOsuGame.cs @@ -40,7 +40,7 @@ namespace osu.Game.Tests.Visual.Navigation typeof(DashboardOverlay), typeof(NewsOverlay), typeof(ChannelManager), - typeof(ChatOverlayV2), + typeof(ChatOverlay), typeof(SettingsOverlay), typeof(UserProfileOverlay), typeof(BeatmapSetOverlay), diff --git a/osu.Game.Tests/Visual/Navigation/TestScenePerformFromScreen.cs b/osu.Game.Tests/Visual/Navigation/TestScenePerformFromScreen.cs index 5d0116f80e..2ce914ba3d 100644 --- a/osu.Game.Tests/Visual/Navigation/TestScenePerformFromScreen.cs +++ b/osu.Game.Tests/Visual/Navigation/TestScenePerformFromScreen.cs @@ -86,9 +86,9 @@ namespace osu.Game.Tests.Visual.Navigation [Test] public void TestOverlaysAlwaysClosed() { - ChatOverlayV2 chat = null; + ChatOverlay chat = null; AddUntilStep("is at menu", () => Game.ScreenStack.CurrentScreen is MainMenu); - AddUntilStep("wait for chat load", () => (chat = Game.ChildrenOfType().SingleOrDefault()) != null); + AddUntilStep("wait for chat load", () => (chat = Game.ChildrenOfType().SingleOrDefault()) != null); AddStep("show chat", () => InputManager.Key(Key.F8)); diff --git a/osu.Game.Tests/Visual/Online/TestSceneChatOverlayV2.cs b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs similarity index 99% rename from osu.Game.Tests/Visual/Online/TestSceneChatOverlayV2.cs rename to osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs index 5f237b4b2d..4edbb9f215 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneChatOverlayV2.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs @@ -32,9 +32,9 @@ using osuTK.Input; namespace osu.Game.Tests.Visual.Online { [TestFixture] - public class TestSceneChatOverlayV2 : OsuManualInputManagerTestScene + public class TestSceneChatOverlay : OsuManualInputManagerTestScene { - private TestChatOverlayV2 chatOverlay; + private TestChatOverlay chatOverlay; private ChannelManager channelManager; private APIUser testUser; @@ -64,7 +64,7 @@ namespace osu.Game.Tests.Visual.Online Children = new Drawable[] { channelManager, - chatOverlay = new TestChatOverlayV2(), + chatOverlay = new TestChatOverlay(), }, }; }); @@ -564,7 +564,7 @@ namespace osu.Game.Tests.Visual.Online }; } - private class TestChatOverlayV2 : ChatOverlayV2 + private class TestChatOverlay : ChatOverlay { public bool SlowLoading { get; set; } diff --git a/osu.Game.Tests/Visual/Online/TestSceneMessageNotifier.cs b/osu.Game.Tests/Visual/Online/TestSceneMessageNotifier.cs index 46f426597a..79f62a16e3 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneMessageNotifier.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneMessageNotifier.cs @@ -208,7 +208,7 @@ namespace osu.Game.Tests.Visual.Online }; [Cached] - public ChatOverlayV2 ChatOverlay { get; } = new ChatOverlayV2(); + public ChatOverlay ChatOverlay { get; } = new ChatOverlay(); private readonly MessageNotifier messageNotifier = new MessageNotifier(); diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index 69e7dee1a5..20d555c16c 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -46,7 +46,7 @@ namespace osu.Game.Configuration SetDefault(OsuSetting.RandomSelectAlgorithm, RandomSelectAlgorithm.RandomPermutation); - SetDefault(OsuSetting.ChatDisplayHeight, ChatOverlayV2.DEFAULT_HEIGHT, 0.2f, 1f); + SetDefault(OsuSetting.ChatDisplayHeight, ChatOverlay.DEFAULT_HEIGHT, 0.2f, 1f); SetDefault(OsuSetting.BeatmapListingCardSize, BeatmapCardSize.Normal); diff --git a/osu.Game/Online/Chat/MessageNotifier.cs b/osu.Game/Online/Chat/MessageNotifier.cs index cea3e321fa..ca6082e19b 100644 --- a/osu.Game/Online/Chat/MessageNotifier.cs +++ b/osu.Game/Online/Chat/MessageNotifier.cs @@ -27,7 +27,7 @@ namespace osu.Game.Online.Chat private INotificationOverlay notifications { get; set; } [Resolved] - private ChatOverlayV2 chatOverlay { get; set; } + private ChatOverlay chatOverlay { get; set; } [Resolved] private ChannelManager channelManager { get; set; } @@ -170,7 +170,7 @@ namespace osu.Game.Online.Chat public override bool IsImportant => false; [BackgroundDependencyLoader] - private void load(OsuColour colours, ChatOverlayV2 chatOverlay, INotificationOverlay notificationOverlay) + private void load(OsuColour colours, ChatOverlay chatOverlay, INotificationOverlay notificationOverlay) { IconBackground.Colour = colours.PurpleDark; diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 402bd94f31..785881d97a 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -75,7 +75,7 @@ namespace osu.Game public Toolbar Toolbar; - private ChatOverlayV2 chatOverlay; + private ChatOverlay chatOverlay; private ChannelManager channelManager; @@ -848,7 +848,7 @@ namespace osu.Game loadComponentSingleFile(news = new NewsOverlay(), overlayContent.Add, true); var rankingsOverlay = loadComponentSingleFile(new RankingsOverlay(), overlayContent.Add, true); loadComponentSingleFile(channelManager = new ChannelManager(), AddInternal, true); - loadComponentSingleFile(chatOverlay = new ChatOverlayV2(), overlayContent.Add, true); + loadComponentSingleFile(chatOverlay = new ChatOverlay(), overlayContent.Add, true); loadComponentSingleFile(new MessageNotifier(), AddInternal, true); loadComponentSingleFile(Settings = new SettingsOverlay(), leftFloatingOverlayContent.Add, true); loadComponentSingleFile(changelogOverlay = new ChangelogOverlay(), overlayContent.Add, true); diff --git a/osu.Game/Overlays/ChatOverlayV2.cs b/osu.Game/Overlays/ChatOverlay.cs similarity index 98% rename from osu.Game/Overlays/ChatOverlayV2.cs rename to osu.Game/Overlays/ChatOverlay.cs index f2ec007b19..02769b5d68 100644 --- a/osu.Game/Overlays/ChatOverlayV2.cs +++ b/osu.Game/Overlays/ChatOverlay.cs @@ -27,7 +27,7 @@ using osu.Game.Overlays.Chat.Listing; namespace osu.Game.Overlays { - public class ChatOverlayV2 : OsuFocusedOverlayContainer, INamedOverlayComponent, IKeyBindingHandler + public class ChatOverlay : OsuFocusedOverlayContainer, INamedOverlayComponent, IKeyBindingHandler { public string IconTexture => "Icons/Hexacons/messaging"; public LocalisableString Title => ChatStrings.HeaderTitle; @@ -70,7 +70,7 @@ namespace osu.Game.Overlays private readonly IBindableList availableChannels = new BindableList(); private readonly IBindableList joinedChannels = new BindableList(); - public ChatOverlayV2() + public ChatOverlay() { Height = DEFAULT_HEIGHT; diff --git a/osu.Game/Overlays/Profile/Header/Components/MessageUserButton.cs b/osu.Game/Overlays/Profile/Header/Components/MessageUserButton.cs index eafb453f75..e3dc5f818a 100644 --- a/osu.Game/Overlays/Profile/Header/Components/MessageUserButton.cs +++ b/osu.Game/Overlays/Profile/Header/Components/MessageUserButton.cs @@ -27,7 +27,7 @@ namespace osu.Game.Overlays.Profile.Header.Components private UserProfileOverlay userOverlay { get; set; } [Resolved(CanBeNull = true)] - private ChatOverlayV2 chatOverlay { get; set; } + private ChatOverlay chatOverlay { get; set; } [Resolved] private IAPIProvider apiProvider { get; set; } diff --git a/osu.Game/Overlays/Toolbar/ToolbarChatButton.cs b/osu.Game/Overlays/Toolbar/ToolbarChatButton.cs index 20f405aae2..2d3b33e9bc 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarChatButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarChatButton.cs @@ -17,7 +17,7 @@ namespace osu.Game.Overlays.Toolbar } [BackgroundDependencyLoader(true)] - private void load(ChatOverlayV2 chat) + private void load(ChatOverlay chat) { StateContainer = chat; } From 0981d415a1fe5ed295b21049cc451f5817b36e68 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 30 May 2022 17:57:48 +0900 Subject: [PATCH 269/395] Select correct channel regardless of load order --- osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs index 4edbb9f215..2cf1114f30 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs @@ -469,6 +469,8 @@ namespace osu.Game.Tests.Visual.Online chatOverlay.Show(); }); + AddStep("Select channel 1", () => clickDrawable(getChannelListItem(testChannel1))); + waitForChannel1Visible(); AddStep("Press document next keys", () => InputManager.Keys(PlatformAction.DocumentNext)); waitForChannel2Visible(); From 17174a7b099b753a2d6c8e7c90754f2060c8f163 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 30 May 2022 19:29:13 +0900 Subject: [PATCH 270/395] Ensure some results have been loaded in playlist results screen tests Basically, the failing delayed test would fire two web requests during the proceedings. In unfortunate timing, the first would succeed and the test would think "everything is okay", but the actual request loading results has not yet run. This check ensures *something* is loaded, which seems to be enough to make things reliable. --- .../Visual/Playlists/TestScenePlaylistsResultsScreen.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs index f5fe00458a..c532e8bc05 100644 --- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs +++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs @@ -173,6 +173,8 @@ namespace osu.Game.Tests.Visual.Playlists { AddUntilStep("wait for scores loaded", () => requestComplete + // request handler may need to fire more than once to get scores. + && totalCount > 0 && resultsScreen.ScorePanelList.GetScorePanels().Count() == totalCount && resultsScreen.ScorePanelList.AllPanelsVisible); AddWaitStep("wait for display", 5); From 82a1ba1d46650bf11164e26537ddfa64ae995c84 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 30 May 2022 17:42:27 +0900 Subject: [PATCH 271/395] Use pooled memory for memory copies performed by `ZipArchiveReader` --- osu.Game/IO/Archives/ZipArchiveReader.cs | 56 +++++++++++++++++++++--- osu.Game/osu.Game.csproj | 1 + 2 files changed, 51 insertions(+), 6 deletions(-) diff --git a/osu.Game/IO/Archives/ZipArchiveReader.cs b/osu.Game/IO/Archives/ZipArchiveReader.cs index 80dfa104f3..ae2b85da51 100644 --- a/osu.Game/IO/Archives/ZipArchiveReader.cs +++ b/osu.Game/IO/Archives/ZipArchiveReader.cs @@ -1,11 +1,15 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Buffers; using System.Collections.Generic; using System.IO; using System.Linq; +using Microsoft.Toolkit.HighPerformance; +using osu.Framework.Extensions; using osu.Framework.IO.Stores; using SharpCompress.Archives.Zip; +using SixLabors.ImageSharp.Memory; namespace osu.Game.IO.Archives { @@ -27,15 +31,12 @@ namespace osu.Game.IO.Archives if (entry == null) throw new FileNotFoundException(); - // allow seeking - MemoryStream copy = new MemoryStream(); + var owner = MemoryAllocator.Default.Allocate((int)entry.Size); using (Stream s = entry.OpenEntryStream()) - s.CopyTo(copy); + s.ReadToFill(owner.Memory.Span); - copy.Position = 0; - - return copy; + return new MemoryOwnerMemoryStream(owner); } public override void Dispose() @@ -45,5 +46,48 @@ namespace osu.Game.IO.Archives } public override IEnumerable Filenames => archive.Entries.Select(e => e.Key).ExcludeSystemFileNames(); + + private class MemoryOwnerMemoryStream : Stream + { + private readonly IMemoryOwner owner; + private readonly Stream stream; + + public MemoryOwnerMemoryStream(IMemoryOwner owner) + { + this.owner = owner; + + stream = owner.Memory.AsStream(); + } + + protected override void Dispose(bool disposing) + { + owner?.Dispose(); + base.Dispose(disposing); + } + + public override void Flush() => stream.Flush(); + + public override int Read(byte[] buffer, int offset, int count) => stream.Read(buffer, offset, count); + + public override long Seek(long offset, SeekOrigin origin) => stream.Seek(offset, origin); + + public override void SetLength(long value) => stream.SetLength(value); + + public override void Write(byte[] buffer, int offset, int count) => stream.Write(buffer, offset, count); + + public override bool CanRead => stream.CanRead; + + public override bool CanSeek => stream.CanSeek; + + public override bool CanWrite => stream.CanWrite; + + public override long Length => stream.Length; + + public override long Position + { + get => stream.Position; + set => stream.Position = value; + } + } } } diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 79cfd7c917..6ce21ccad6 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -29,6 +29,7 @@ + all From d6b18d87b8f7c14bb078dca1863a4283ee16cb7f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 30 May 2022 19:58:47 +0900 Subject: [PATCH 272/395] Update framework --- osu.Android.props | 2 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index c28085557e..116c7dbfcd 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -52,7 +52,7 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 6ce21ccad6..eb47d0468f 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -36,7 +36,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/osu.iOS.props b/osu.iOS.props index b1ba64beba..ccecad6f82 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -61,7 +61,7 @@ - + @@ -84,7 +84,7 @@ - + From 477e520766ef2924e4bbc37ab5cb33cf085debfe Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 31 May 2022 12:24:44 +0900 Subject: [PATCH 273/395] Add comment regarding deadlock avoidance --- osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestScene.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestScene.cs b/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestScene.cs index 32e42a945d..df3974664e 100644 --- a/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestScene.cs +++ b/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestScene.cs @@ -83,6 +83,7 @@ namespace osu.Game.Tests.Visual.OnlinePlay #pragma warning disable RS0030 // We can't GetResultSafely() here (will fail with "Can't use GetResultSafely from inside an async operation."), but Wait is safe enough due to // the task being a TaskCompletionSource. + // Importantly, this doesn't deadlock because of the scheduler call above running inline where feasible (see the `false` argument). return tcs.Task.Result; #pragma warning restore RS0030 }; From 53844d3df1548a1db0c31ebc6cd82b9b529655af Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 31 May 2022 14:01:42 +0900 Subject: [PATCH 274/395] Refactor settings item "warning" text to allow non-warning state --- .../Visual/Settings/TestSceneSettingsItem.cs | 19 +++++---- .../Sections/Graphics/LayoutSettings.cs | 8 ++-- .../Sections/Graphics/RendererSettings.cs | 11 +++++- .../Settings/Sections/Input/MouseSettings.cs | 4 +- .../Settings/Sections/Input/TabletSettings.cs | 5 ++- .../UserInterface/MainMenuSettings.cs | 5 ++- osu.Game/Overlays/Settings/SettingsItem.cs | 39 +++++++++++-------- .../Overlays/Settings/SettingsNoticeText.cs | 19 --------- 8 files changed, 57 insertions(+), 53 deletions(-) delete mode 100644 osu.Game/Overlays/Settings/SettingsNoticeText.cs diff --git a/osu.Game.Tests/Visual/Settings/TestSceneSettingsItem.cs b/osu.Game.Tests/Visual/Settings/TestSceneSettingsItem.cs index 83265e13ad..f631e1603b 100644 --- a/osu.Game.Tests/Visual/Settings/TestSceneSettingsItem.cs +++ b/osu.Game.Tests/Visual/Settings/TestSceneSettingsItem.cs @@ -6,6 +6,7 @@ using NUnit.Framework; using osu.Framework.Bindables; using osu.Framework.Testing; using osu.Framework.Utils; +using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; using osu.Game.Overlays.Settings; @@ -83,7 +84,7 @@ namespace osu.Game.Tests.Visual.Settings AddStep("clear label", () => textBox.LabelText = default); AddAssert("default value button centre aligned to control size", () => Precision.AlmostEquals(restoreDefaultValueButton.Parent.DrawHeight, control.DrawHeight, 1)); - AddStep("set warning text", () => textBox.WarningText = "This is some very important warning text! Hopefully it doesn't break the alignment of the default value indicator..."); + AddStep("set warning text", () => textBox.SetWarningText("This is some very important warning text! Hopefully it doesn't break the alignment of the default value indicator...")); AddAssert("default value button centre aligned to control size", () => Precision.AlmostEquals(restoreDefaultValueButton.Parent.DrawHeight, control.DrawHeight, 1)); } @@ -129,16 +130,18 @@ namespace osu.Game.Tests.Visual.Settings SettingsNumberBox numberBox = null; AddStep("create settings item", () => Child = numberBox = new SettingsNumberBox()); - AddAssert("warning text not created", () => !numberBox.ChildrenOfType().Any()); + AddAssert("warning text not created", () => !numberBox.ChildrenOfType().Any()); - AddStep("set warning text", () => numberBox.WarningText = "this is a warning!"); - AddAssert("warning text created", () => numberBox.ChildrenOfType().Single().Alpha == 1); + AddStep("set warning text", () => numberBox.SetWarningText("this is a warning!")); + AddAssert("warning text created", () => numberBox.ChildrenOfType().Single().Alpha == 1); - AddStep("unset warning text", () => numberBox.WarningText = default); - AddAssert("warning text hidden", () => numberBox.ChildrenOfType().Single().Alpha == 0); + AddStep("unset warning text", () => numberBox.ClearWarningText()); + AddAssert("warning text hidden", () => !numberBox.ChildrenOfType().Any()); - AddStep("set warning text again", () => numberBox.WarningText = "another warning!"); - AddAssert("warning text shown again", () => numberBox.ChildrenOfType().Single().Alpha == 1); + AddStep("set warning text again", () => numberBox.SetWarningText("another warning!")); + AddAssert("warning text shown again", () => numberBox.ChildrenOfType().Single().Alpha == 1); + + AddStep("set non warning text", () => numberBox.SetWarningText("you did good!", false)); } } } diff --git a/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs b/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs index 69a50c19b4..1ebe5aa85e 100644 --- a/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs @@ -226,7 +226,7 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics { if (windowModeDropdown.Current.Value != WindowMode.Fullscreen) { - windowModeDropdown.WarningText = GraphicsSettingsStrings.NotFullscreenNote; + windowModeDropdown.SetWarningText(GraphicsSettingsStrings.NotFullscreenNote); return; } @@ -234,15 +234,15 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics { case FullscreenCapability.Unknown: if (host.Window is WindowsWindow) - windowModeDropdown.WarningText = LayoutSettingsStrings.CheckingForFullscreenCapabilities; + windowModeDropdown.SetWarningText(LayoutSettingsStrings.CheckingForFullscreenCapabilities); break; case FullscreenCapability.Capable: - windowModeDropdown.WarningText = LayoutSettingsStrings.OsuIsRunningExclusiveFullscreen; + windowModeDropdown.SetWarningText(LayoutSettingsStrings.OsuIsRunningExclusiveFullscreen, false); break; case FullscreenCapability.Incapable: - windowModeDropdown.WarningText = LayoutSettingsStrings.UnableToRunExclusiveFullscreen; + windowModeDropdown.SetWarningText(LayoutSettingsStrings.UnableToRunExclusiveFullscreen); break; } } diff --git a/osu.Game/Overlays/Settings/Sections/Graphics/RendererSettings.cs b/osu.Game/Overlays/Settings/Sections/Graphics/RendererSettings.cs index 653f30a018..6ec4decdda 100644 --- a/osu.Game/Overlays/Settings/Sections/Graphics/RendererSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Graphics/RendererSettings.cs @@ -48,7 +48,16 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics frameLimiterDropdown.Current.BindValueChanged(limit => { - frameLimiterDropdown.WarningText = limit.NewValue == FrameSync.Unlimited ? GraphicsSettingsStrings.UnlimitedFramesNote : default; + switch (limit.NewValue) + { + case FrameSync.Unlimited: + frameLimiterDropdown.SetWarningText(GraphicsSettingsStrings.UnlimitedFramesNote); + break; + + default: + frameLimiterDropdown.ClearWarningText(); + break; + } }, true); } } diff --git a/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs b/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs index 4235dc0a05..042cec106a 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs @@ -117,9 +117,9 @@ namespace osu.Game.Overlays.Settings.Sections.Input if (RuntimeInfo.OS != RuntimeInfo.Platform.Windows) { if (highPrecision.NewValue) - highPrecisionMouse.WarningText = MouseSettingsStrings.HighPrecisionPlatformWarning; + highPrecisionMouse.SetWarningText(MouseSettingsStrings.HighPrecisionPlatformWarning); else - highPrecisionMouse.WarningText = null; + highPrecisionMouse.ClearWarningText(); } }, true); } diff --git a/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs b/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs index 802d442ced..5d31c38ae7 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs @@ -11,6 +11,7 @@ using osu.Framework.Localisation; using osu.Framework.Platform; using osu.Framework.Threading; using osu.Game.Graphics; +using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osuTK; using osu.Game.Localisation; @@ -95,11 +96,13 @@ namespace osu.Game.Overlays.Settings.Sections.Input Origin = Anchor.TopCentre, Text = TabletSettingsStrings.NoTabletDetected, }, - new SettingsNoticeText(colours) + new LinkFlowContainer(cp => cp.Colour = colours.Yellow) { TextAnchor = Anchor.TopCentre, Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, }.With(t => { if (RuntimeInfo.OS == RuntimeInfo.Platform.Windows || RuntimeInfo.OS == RuntimeInfo.Platform.Linux) diff --git a/osu.Game/Overlays/Settings/Sections/UserInterface/MainMenuSettings.cs b/osu.Game/Overlays/Settings/Sections/UserInterface/MainMenuSettings.cs index 284e9cb2de..d3cc889abe 100644 --- a/osu.Game/Overlays/Settings/Sections/UserInterface/MainMenuSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/UserInterface/MainMenuSettings.cs @@ -61,7 +61,10 @@ namespace osu.Game.Overlays.Settings.Sections.UserInterface user.BindValueChanged(u => { - backgroundSourceDropdown.WarningText = u.NewValue?.IsSupporter != true ? UserInterfaceStrings.NotSupporterNote : default; + if (u.NewValue?.IsSupporter != true) + backgroundSourceDropdown.SetWarningText(UserInterfaceStrings.NotSupporterNote); + else + backgroundSourceDropdown.ClearWarningText(); }, true); } } diff --git a/osu.Game/Overlays/Settings/SettingsItem.cs b/osu.Game/Overlays/Settings/SettingsItem.cs index ee9daa1c0d..fa521c0126 100644 --- a/osu.Game/Overlays/Settings/SettingsItem.cs +++ b/osu.Game/Overlays/Settings/SettingsItem.cs @@ -70,27 +70,32 @@ namespace osu.Game.Overlays.Settings } /// - /// Text to be displayed at the bottom of this . + /// Clear any warning text. + /// + public void ClearWarningText() + { + warningText?.Expire(); + warningText = null; + } + + /// + /// Set the text to be displayed at the bottom of this . /// Generally used to recommend the user change their setting as the current one is considered sub-optimal. /// - public LocalisableString? WarningText + /// The text to display. + /// Whether the text is in a warning state. Will decide how this is visually represented. + public void SetWarningText(LocalisableString text, bool isWarning = true) { - set + ClearWarningText(); + + // construct lazily for cases where the label is not needed (may be provided by the Control). + FlowContent.Add(warningText = new LinkFlowContainer(cp => cp.Colour = isWarning ? colours.Yellow : colours.Green) { - bool hasValue = value != default; - - if (warningText == null) - { - if (!hasValue) - return; - - // construct lazily for cases where the label is not needed (may be provided by the Control). - FlowContent.Add(warningText = new SettingsNoticeText(colours) { Margin = new MarginPadding { Bottom = 5 } }); - } - - warningText.Alpha = hasValue ? 1 : 0; - warningText.Text = value ?? default; - } + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Margin = new MarginPadding { Bottom = 5 }, + Text = text, + }); } public virtual Bindable Current diff --git a/osu.Game/Overlays/Settings/SettingsNoticeText.cs b/osu.Game/Overlays/Settings/SettingsNoticeText.cs deleted file mode 100644 index 76ecf7edd4..0000000000 --- a/osu.Game/Overlays/Settings/SettingsNoticeText.cs +++ /dev/null @@ -1,19 +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.Graphics; -using osu.Game.Graphics; -using osu.Game.Graphics.Containers; - -namespace osu.Game.Overlays.Settings -{ - public class SettingsNoticeText : LinkFlowContainer - { - public SettingsNoticeText(OsuColour colours) - : base(s => s.Colour = colours.Yellow) - { - RelativeSizeAxes = Axes.X; - AutoSizeAxes = Axes.Y; - } - } -} From f7110116de361f40e6c8aeb76b761eb250714c9b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 31 May 2022 14:04:56 +0900 Subject: [PATCH 275/395] Only display exclusive fullscreen hinting on windows --- .../Sections/Graphics/LayoutSettings.cs | 27 ++++++++++++------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs b/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs index 1ebe5aa85e..53f84a9bb2 100644 --- a/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs @@ -230,20 +230,27 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics return; } - switch (fullscreenCapability.Value) + if (host.Window is WindowsWindow) { - case FullscreenCapability.Unknown: - if (host.Window is WindowsWindow) + switch (fullscreenCapability.Value) + { + case FullscreenCapability.Unknown: windowModeDropdown.SetWarningText(LayoutSettingsStrings.CheckingForFullscreenCapabilities); - break; + break; - case FullscreenCapability.Capable: - windowModeDropdown.SetWarningText(LayoutSettingsStrings.OsuIsRunningExclusiveFullscreen, false); - break; + case FullscreenCapability.Capable: + windowModeDropdown.SetWarningText(LayoutSettingsStrings.OsuIsRunningExclusiveFullscreen, false); + break; - case FullscreenCapability.Incapable: - windowModeDropdown.SetWarningText(LayoutSettingsStrings.UnableToRunExclusiveFullscreen); - break; + case FullscreenCapability.Incapable: + windowModeDropdown.SetWarningText(LayoutSettingsStrings.UnableToRunExclusiveFullscreen); + break; + } + } + else + { + // We can only detect exclusive fullscreen status on windows currently. + windowModeDropdown.ClearWarningText(); } } From 27efeb7d4e1ce134aeb923d8f2f456d6b5b70f4e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 31 May 2022 14:58:49 +0900 Subject: [PATCH 276/395] Fix `TimingSection` performing a beatmap save when switching bound timing points --- osu.Game/Screens/Edit/Timing/TimingSection.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Screens/Edit/Timing/TimingSection.cs b/osu.Game/Screens/Edit/Timing/TimingSection.cs index a5abd96d72..bebd4c1049 100644 --- a/osu.Game/Screens/Edit/Timing/TimingSection.cs +++ b/osu.Game/Screens/Edit/Timing/TimingSection.cs @@ -32,9 +32,11 @@ namespace osu.Game.Screens.Edit.Timing { if (point.NewValue != null) { + bpmTextEntry.Current.UnbindEvents(); bpmTextEntry.Bindable = point.NewValue.BeatLengthBindable; bpmTextEntry.Current.BindValueChanged(_ => ChangeHandler?.SaveState()); + timeSignature.Current.UnbindEvents(); timeSignature.Current = point.NewValue.TimeSignatureBindable; timeSignature.Current.BindValueChanged(_ => ChangeHandler?.SaveState()); } From cf5da44492a4d7da9c40d8e3f390e078e85cb94b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 31 May 2022 15:00:30 +0900 Subject: [PATCH 277/395] Add automatic control point tracking to the timing screen --- .../Visual/Editing/TestSceneTimingScreen.cs | 48 ++++++++++++++- osu.Game/Screens/Edit/Timing/TimingScreen.cs | 58 ++++++++++++++++++- 2 files changed, 104 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneTimingScreen.cs b/osu.Game.Tests/Visual/Editing/TestSceneTimingScreen.cs index 17b8189fc7..3a709cb66d 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneTimingScreen.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneTimingScreen.cs @@ -1,14 +1,18 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics.Containers; +using osu.Framework.Testing; using osu.Game.Overlays; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Osu; using osu.Game.Screens.Edit; using osu.Game.Screens.Edit.Timing; +using osu.Game.Screens.Edit.Timing.RowAttributes; +using osuTK.Input; namespace osu.Game.Tests.Visual.Editing { @@ -22,6 +26,8 @@ namespace osu.Game.Tests.Visual.Editing [Cached] private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Blue); + private TimingScreen timingScreen; + protected override bool ScrollUsingMouseWheel => false; public TestSceneTimingScreen() @@ -36,12 +42,52 @@ namespace osu.Game.Tests.Visual.Editing Beatmap.Value = CreateWorkingBeatmap(editorBeatmap.PlayableBeatmap); Beatmap.Disabled = true; - Child = new TimingScreen + Child = timingScreen = new TimingScreen { State = { Value = Visibility.Visible }, }; } + [SetUpSteps] + public void SetUpSteps() + { + AddStep("Stop clock", () => Clock.Stop()); + } + + [Test] + public void TestTrackingCurrentTimeWhileRunning() + { + AddStep("Select first effect point", () => + { + InputManager.MoveMouseTo(Child.ChildrenOfType().First()); + InputManager.Click(MouseButton.Left); + }); + + AddUntilStep("Selection changed", () => timingScreen.SelectedGroup.Value.Time == 54670); + AddUntilStep("Ensure seeked to correct time", () => Clock.CurrentTimeAccurate == 54670); + + AddStep("Seek to just before next point", () => Clock.Seek(69000)); + AddStep("Start clock", () => Clock.Start()); + + AddUntilStep("Selection changed", () => timingScreen.SelectedGroup.Value.Time == 69670); + } + + [Test] + public void TestTrackingCurrentTimeWhilePaused() + { + AddStep("Select first effect point", () => + { + InputManager.MoveMouseTo(Child.ChildrenOfType().First()); + InputManager.Click(MouseButton.Left); + }); + + AddUntilStep("Selection changed", () => timingScreen.SelectedGroup.Value.Time == 54670); + AddUntilStep("Ensure seeked to correct time", () => Clock.CurrentTimeAccurate == 54670); + + AddStep("Seek to later", () => Clock.Seek(80000)); + AddUntilStep("Selection changed", () => timingScreen.SelectedGroup.Value.Time == 69670); + } + protected override void Dispose(bool isDisposing) { Beatmap.Disabled = false; diff --git a/osu.Game/Screens/Edit/Timing/TimingScreen.cs b/osu.Game/Screens/Edit/Timing/TimingScreen.cs index f71a8d7d22..0cf4fca58a 100644 --- a/osu.Game/Screens/Edit/Timing/TimingScreen.cs +++ b/osu.Game/Screens/Edit/Timing/TimingScreen.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.Collections.Generic; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -19,7 +20,7 @@ namespace osu.Game.Screens.Edit.Timing public class TimingScreen : EditorScreenWithTimeline { [Cached] - private Bindable selectedGroup = new Bindable(); + public readonly Bindable SelectedGroup = new Bindable(); public TimingScreen() : base(EditorScreenMode.Timing) @@ -132,6 +133,61 @@ namespace osu.Game.Screens.Edit.Timing }, true); } + protected override void Update() + { + base.Update(); + + trackActivePoint(); + } + + /// + /// Given the user has selected a control point group, we want to track any group which is + /// active at the current point in time which matches the type the user has selected. + /// + /// So if the user is currently looking at a timing point and seeks into the future, a + /// future timing point would be automatically selected if it is now the new "current" point. + /// + private void trackActivePoint() + { + // For simplicity only match on the first type of the active control point. + var selectedPointType = selectedGroup.Value?.ControlPoints.FirstOrDefault()?.GetType(); + + if (selectedPointType != null) + { + // We don't have an efficient way of looking up groups currently, only individual point types. + // To improve the efficiency of this in the future, we should reconsider the overall structure of ControlPointInfo. + IEnumerable groups = Beatmap.ControlPointInfo.Groups; + + bool currentTimeBeforeSelectedGroup = clock.CurrentTimeAccurate < selectedGroup.Value.Time; + + // Decide whether we are searching backwards or forwards. + if (currentTimeBeforeSelectedGroup) + groups = groups.Reverse(); + + // Find the next group which has the same type as the selected one. + groups = groups.SkipWhile(g => g != selectedGroup.Value) + .Skip(1) + .Where(g => g.ControlPoints.Any(cp => cp.GetType() == selectedPointType)); + + ControlPointGroup newGroup = groups.FirstOrDefault(); + + if (newGroup != null) + { + if (currentTimeBeforeSelectedGroup) + { + // When seeking backwards, the first match from the LINQ query is always what we want. + selectedGroup.Value = newGroup; + } + else + { + // When seeking forwards, we also need to check that the next match is before the current time. + if (newGroup.Time <= clock.CurrentTimeAccurate) + selectedGroup.Value = newGroup; + } + } + } + } + private void delete() { if (selectedGroup.Value == null) From ebb83a5e497e69027aaaa9cef28704f7688e6d22 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 31 May 2022 15:10:55 +0900 Subject: [PATCH 278/395] Add TODO about scroll behaviour which is missing --- osu.Game/Screens/Edit/Timing/ControlPointTable.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Screens/Edit/Timing/ControlPointTable.cs b/osu.Game/Screens/Edit/Timing/ControlPointTable.cs index 0c12eff503..77d875b67f 100644 --- a/osu.Game/Screens/Edit/Timing/ControlPointTable.cs +++ b/osu.Game/Screens/Edit/Timing/ControlPointTable.cs @@ -61,6 +61,7 @@ namespace osu.Game.Screens.Edit.Timing selectedGroup.BindValueChanged(group => { + // TODO: This should scroll the selected row into view. foreach (var b in BackgroundFlow) b.Selected = b.Item == group.NewValue; }, true); } From 9746cbb1610383ccafc7957335a393b895c007f0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 31 May 2022 15:54:07 +0900 Subject: [PATCH 279/395] Ensure rows have loaded before attempting to click them --- osu.Game.Tests/Visual/Editing/TestSceneTimingScreen.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneTimingScreen.cs b/osu.Game.Tests/Visual/Editing/TestSceneTimingScreen.cs index 3a709cb66d..a358166477 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneTimingScreen.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneTimingScreen.cs @@ -52,6 +52,8 @@ namespace osu.Game.Tests.Visual.Editing public void SetUpSteps() { AddStep("Stop clock", () => Clock.Stop()); + + AddUntilStep("wait for rows to load", () => Child.ChildrenOfType().Any()); } [Test] From 132c94c1b57ce391c48a1446b29ab606ca61789f Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 31 May 2022 17:16:23 +0900 Subject: [PATCH 280/395] Remove Ruleset parameter from ResetFromReplayFrame() --- .../Gameplay/TestSceneScoreProcessor.cs | 6 +++--- .../Rulesets/Scoring/JudgementProcessor.cs | 3 +-- osu.Game/Rulesets/Scoring/ScoreProcessor.cs | 18 +++++++----------- osu.Game/Rulesets/UI/RulesetInputManager.cs | 6 +----- 4 files changed, 12 insertions(+), 21 deletions(-) diff --git a/osu.Game.Tests/Gameplay/TestSceneScoreProcessor.cs b/osu.Game.Tests/Gameplay/TestSceneScoreProcessor.cs index 9c307341bd..af4b002bc9 100644 --- a/osu.Game.Tests/Gameplay/TestSceneScoreProcessor.cs +++ b/osu.Game.Tests/Gameplay/TestSceneScoreProcessor.cs @@ -61,13 +61,13 @@ namespace osu.Game.Tests.Gameplay Assert.That(scoreProcessor.JudgedHits, Is.EqualTo(1)); // No header shouldn't cause any change - scoreProcessor.ResetFromReplayFrame(new OsuRuleset(), new OsuReplayFrame()); + scoreProcessor.ResetFromReplayFrame(new OsuReplayFrame()); Assert.That(scoreProcessor.TotalScore.Value, Is.EqualTo(1_000_000)); Assert.That(scoreProcessor.JudgedHits, Is.EqualTo(1)); // Reset with a miss instead. - scoreProcessor.ResetFromReplayFrame(new OsuRuleset(), new OsuReplayFrame + scoreProcessor.ResetFromReplayFrame(new OsuReplayFrame { Header = new FrameHeader(0, 0, 0, new Dictionary { { HitResult.Miss, 1 } }, DateTimeOffset.Now) }); @@ -76,7 +76,7 @@ namespace osu.Game.Tests.Gameplay Assert.That(scoreProcessor.JudgedHits, Is.EqualTo(1)); // Reset with no judged hit. - scoreProcessor.ResetFromReplayFrame(new OsuRuleset(), new OsuReplayFrame + scoreProcessor.ResetFromReplayFrame(new OsuReplayFrame { Header = new FrameHeader(0, 0, 0, new Dictionary(), DateTimeOffset.Now) }); diff --git a/osu.Game/Rulesets/Scoring/JudgementProcessor.cs b/osu.Game/Rulesets/Scoring/JudgementProcessor.cs index 94ddc32bb7..bfa67b8c45 100644 --- a/osu.Game/Rulesets/Scoring/JudgementProcessor.cs +++ b/osu.Game/Rulesets/Scoring/JudgementProcessor.cs @@ -117,9 +117,8 @@ namespace osu.Game.Rulesets.Scoring /// /// If the provided replay frame does not have any header information, this will be a noop. /// - /// The ruleset to be used for retrieving statistics. /// The replay frame to read header statistics from. - public virtual void ResetFromReplayFrame(Ruleset ruleset, ReplayFrame frame) + public virtual void ResetFromReplayFrame(ReplayFrame frame) { if (frame.Header == null) return; diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index 1dd1d1aeb6..1403a3f243 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -251,8 +251,7 @@ namespace osu.Game.Rulesets.Scoring if (!ruleset.RulesetInfo.Equals(scoreInfo.Ruleset)) throw new ArgumentException($"Unexpected score ruleset. Expected \"{ruleset.RulesetInfo.ShortName}\" but was \"{scoreInfo.Ruleset.ShortName}\"."); - extractFromStatistics(ruleset, - scoreInfo.Statistics, + extractFromStatistics(scoreInfo.Statistics, out double extractedBaseScore, out double extractedMaxBaseScore, out int extractedMaxCombo, @@ -281,8 +280,7 @@ namespace osu.Game.Rulesets.Scoring if (!beatmapApplied) throw new InvalidOperationException($"Cannot compute partial score without calling {nameof(ApplyBeatmap)}."); - extractFromStatistics(ruleset, - scoreInfo.Statistics, + extractFromStatistics(scoreInfo.Statistics, out double extractedBaseScore, out _, out _, @@ -318,9 +316,7 @@ namespace osu.Game.Rulesets.Scoring // Note: This cannot be applied universally to all legacy scores, as some rulesets (e.g. catch) group multiple judgements together. if (scoreInfo.IsLegacyScore && scoreInfo.Ruleset.OnlineID == 3) { - extractFromStatistics( - ruleset, - scoreInfo.Statistics, + extractFromStatistics(scoreInfo.Statistics, out double computedBaseScore, out double computedMaxBaseScore, out _, @@ -437,14 +433,14 @@ namespace osu.Game.Rulesets.Scoring score.TotalScore = (long)Math.Round(ComputeFinalScore(ScoringMode.Standardised, score)); } - public override void ResetFromReplayFrame(Ruleset ruleset, ReplayFrame frame) + public override void ResetFromReplayFrame(ReplayFrame frame) { - base.ResetFromReplayFrame(ruleset, frame); + base.ResetFromReplayFrame(frame); if (frame.Header == null) return; - extractFromStatistics(ruleset, frame.Header.Statistics, out baseScore, out rollingMaxBaseScore, out _, out _); + extractFromStatistics(frame.Header.Statistics, out baseScore, out rollingMaxBaseScore, out _, out _); HighestCombo.Value = frame.Header.MaxCombo; scoreResultCounts.Clear(); @@ -455,7 +451,7 @@ namespace osu.Game.Rulesets.Scoring OnResetFromReplayFrame?.Invoke(); } - private void extractFromStatistics(Ruleset ruleset, IReadOnlyDictionary statistics, out double baseScore, out double maxBaseScore, out int maxCombo, + private void extractFromStatistics(IReadOnlyDictionary statistics, out double baseScore, out double maxBaseScore, out int maxCombo, out int basicHitObjects) { baseScore = 0; diff --git a/osu.Game/Rulesets/UI/RulesetInputManager.cs b/osu.Game/Rulesets/UI/RulesetInputManager.cs index 7d1b23f48b..b5390eb6e2 100644 --- a/osu.Game/Rulesets/UI/RulesetInputManager.cs +++ b/osu.Game/Rulesets/UI/RulesetInputManager.cs @@ -27,8 +27,6 @@ namespace osu.Game.Rulesets.UI { public readonly KeyBindingContainer KeyBindingContainer; - private readonly Ruleset ruleset; - [Resolved(CanBeNull = true)] private ScoreProcessor scoreProcessor { get; set; } @@ -57,8 +55,6 @@ namespace osu.Game.Rulesets.UI protected RulesetInputManager(RulesetInfo ruleset, int variant, SimultaneousBindingMode unique) { - this.ruleset = ruleset.CreateInstance(); - InternalChild = KeyBindingContainer = CreateKeyBindingContainer(ruleset, variant, unique) .WithChild(content = new Container { RelativeSizeAxes = Axes.Both }); @@ -85,7 +81,7 @@ namespace osu.Game.Rulesets.UI break; case ReplayStatisticsFrameEvent statisticsStateChangeEvent: - scoreProcessor?.ResetFromReplayFrame(ruleset, statisticsStateChangeEvent.Frame); + scoreProcessor?.ResetFromReplayFrame(statisticsStateChangeEvent.Frame); break; default: From 655780fd986785d65db914275157825f0fad5046 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 31 May 2022 18:27:18 +0900 Subject: [PATCH 281/395] Fix regression in bpm textbox binding logic --- osu.Game/Screens/Edit/Timing/TimingSection.cs | 24 +++++++++++++++---- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/Edit/Timing/TimingSection.cs b/osu.Game/Screens/Edit/Timing/TimingSection.cs index bebd4c1049..1a97058d73 100644 --- a/osu.Game/Screens/Edit/Timing/TimingSection.cs +++ b/osu.Game/Screens/Edit/Timing/TimingSection.cs @@ -28,17 +28,31 @@ namespace osu.Game.Screens.Edit.Timing }); } + protected override void LoadComplete() + { + base.LoadComplete(); + + bpmTextEntry.Current.BindValueChanged(_ => saveChanges()); + timeSignature.Current.BindValueChanged(_ => saveChanges()); + + void saveChanges() + { + if (!isRebinding) ChangeHandler?.SaveState(); + } + } + + private bool isRebinding; + protected override void OnControlPointChanged(ValueChangedEvent point) { if (point.NewValue != null) { - bpmTextEntry.Current.UnbindEvents(); - bpmTextEntry.Bindable = point.NewValue.BeatLengthBindable; - bpmTextEntry.Current.BindValueChanged(_ => ChangeHandler?.SaveState()); + isRebinding = true; - timeSignature.Current.UnbindEvents(); + bpmTextEntry.Bindable = point.NewValue.BeatLengthBindable; timeSignature.Current = point.NewValue.TimeSignatureBindable; - timeSignature.Current.BindValueChanged(_ => ChangeHandler?.SaveState()); + + isRebinding = false; } } From c0804803fd530beddd1f07ced27720b02ad92273 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 31 May 2022 18:09:26 +0900 Subject: [PATCH 282/395] Add background to main waveform row --- .../Edit/Timing/WaveformComparisonDisplay.cs | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Timing/WaveformComparisonDisplay.cs b/osu.Game/Screens/Edit/Timing/WaveformComparisonDisplay.cs index c80d3c4261..d212738d8d 100644 --- a/osu.Game/Screens/Edit/Timing/WaveformComparisonDisplay.cs +++ b/osu.Game/Screens/Edit/Timing/WaveformComparisonDisplay.cs @@ -63,7 +63,7 @@ namespace osu.Game.Screens.Edit.Timing for (int i = 0; i < total_waveforms; i++) { - AddInternal(new WaveformRow + AddInternal(new WaveformRow(i == total_waveforms / 2) { RelativeSizeAxes = Axes.Both, RelativePositionAxes = Axes.Both, @@ -177,17 +177,29 @@ namespace osu.Game.Screens.Edit.Timing internal class WaveformRow : CompositeDrawable { + private readonly bool isMainRow; private OsuSpriteText beatIndexText = null!; private WaveformGraph waveformGraph = null!; [Resolved] private OverlayColourProvider colourProvider { get; set; } = null!; + public WaveformRow(bool isMainRow) + { + this.isMainRow = isMainRow; + } + [BackgroundDependencyLoader] private void load(IBindable beatmap) { InternalChildren = new Drawable[] { + new Box + { + Colour = colourProvider.Background3, + Alpha = isMainRow ? 1 : 0, + RelativeSizeAxes = Axes.Both, + }, waveformGraph = new WaveformGraph { RelativeSizeAxes = Axes.Both, From 6bc68ada4380a612c243e207c56d3691f60cce7a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 31 May 2022 16:15:53 +0900 Subject: [PATCH 283/395] Add ability to lock the `WaveformComparison` display to a current location --- .../Edit/Timing/WaveformComparisonDisplay.cs | 86 ++++++++++++++++++- 1 file changed, 85 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Timing/WaveformComparisonDisplay.cs b/osu.Game/Screens/Edit/Timing/WaveformComparisonDisplay.cs index c80d3c4261..e86e75ad0d 100644 --- a/osu.Game/Screens/Edit/Timing/WaveformComparisonDisplay.cs +++ b/osu.Game/Screens/Edit/Timing/WaveformComparisonDisplay.cs @@ -14,6 +14,7 @@ using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Events; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterfaceV2; using osu.Game.Overlays; @@ -26,6 +27,8 @@ namespace osu.Game.Screens.Edit.Timing { private const int total_waveforms = 8; + private const float corner_radius = LabelledDrawable.CORNER_RADIUS; + private readonly BindableNumber beatLength = new BindableDouble(); [Resolved] @@ -49,11 +52,18 @@ namespace osu.Game.Screens.Edit.Timing private readonly IBindableList controlPointGroups = new BindableList(); + private readonly BindableBool displayLocked = new BindableBool(); + + private LockedOverlay lockedOverlay = null!; + + [Resolved] + private OsuColour colours { get; set; } = null!; + public WaveformComparisonDisplay() { RelativeSizeAxes = Axes.Both; - CornerRadius = LabelledDrawable.CORNER_RADIUS; + CornerRadius = corner_radius; Masking = true; } @@ -81,12 +91,19 @@ namespace osu.Game.Screens.Edit.Timing Width = 3, }); + AddInternal(lockedOverlay = new LockedOverlay()); + selectedGroup.BindValueChanged(_ => updateTimingGroup(), true); controlPointGroups.BindTo(editorBeatmap.ControlPointInfo.Groups); controlPointGroups.BindCollectionChanged((_, __) => updateTimingGroup()); beatLength.BindValueChanged(_ => showFrom(lastDisplayedBeatIndex), true); + + displayLocked.BindValueChanged(locked => + { + lockedOverlay.FadeTo(locked.NewValue ? 1 : 0, 200, Easing.OutQuint); + }, true); } private void updateTimingGroup() @@ -130,6 +147,12 @@ namespace osu.Game.Screens.Edit.Timing return base.OnMouseMove(e); } + protected override bool OnClick(ClickEvent e) + { + displayLocked.Toggle(); + return true; + } + protected override void Update() { base.Update(); @@ -147,6 +170,9 @@ namespace osu.Game.Screens.Edit.Timing if (lastDisplayedBeatIndex == beatIndex) return; + if (displayLocked.Value) + return; + // Chosen as a pretty usable number across all BPMs. // Optimally we'd want this to scale with the BPM in question, but performing // scaling of the display is both expensive in resampling, and decreases usability @@ -175,6 +201,64 @@ namespace osu.Game.Screens.Edit.Timing lastDisplayedBeatIndex = beatIndex; } + internal class LockedOverlay : CompositeDrawable + { + private OsuSpriteText text = null!; + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + RelativeSizeAxes = Axes.Both; + Masking = true; + CornerRadius = corner_radius; + BorderColour = colours.Red; + BorderThickness = 3; + Alpha = 0; + + InternalChildren = new Drawable[] + { + new Box + { + AlwaysPresent = true, + RelativeSizeAxes = Axes.Both, + Alpha = 0, + }, + new Container + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + AutoSizeAxes = Axes.Both, + Children = new Drawable[] + { + new Box + { + Colour = colours.Red, + RelativeSizeAxes = Axes.Both, + }, + text = new OsuSpriteText + { + Colour = colours.GrayF, + Text = "Locked", + Margin = new MarginPadding(5), + Shadow = false, + Font = OsuFont.Default.With(size: 12, weight: FontWeight.SemiBold), + } + } + }, + }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + text + .FadeIn().Then().Delay(500) + .FadeOut().Then().Delay(500) + .Loop(); + } + } + internal class WaveformRow : CompositeDrawable { private OsuSpriteText beatIndexText = null!; From c8f21ee8b2a8765477c62ff732678684869e010d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 31 May 2022 16:32:02 +0900 Subject: [PATCH 284/395] Change `WaveformComparisonDisplay` to centre around a time offset rather than beat --- .../Edit/Timing/WaveformComparisonDisplay.cs | 48 +++++++++++-------- 1 file changed, 29 insertions(+), 19 deletions(-) diff --git a/osu.Game/Screens/Edit/Timing/WaveformComparisonDisplay.cs b/osu.Game/Screens/Edit/Timing/WaveformComparisonDisplay.cs index e86e75ad0d..46ecf3d9f2 100644 --- a/osu.Game/Screens/Edit/Timing/WaveformComparisonDisplay.cs +++ b/osu.Game/Screens/Edit/Timing/WaveformComparisonDisplay.cs @@ -45,7 +45,7 @@ namespace osu.Game.Screens.Edit.Timing private TimingControlPoint timingPoint = TimingControlPoint.DEFAULT; - private int lastDisplayedBeatIndex; + private double displayedTime; private double selectedGroupStartTime; private double selectedGroupEndTime; @@ -56,9 +56,6 @@ namespace osu.Game.Screens.Edit.Timing private LockedOverlay lockedOverlay = null!; - [Resolved] - private OsuColour colours { get; set; } = null!; - public WaveformComparisonDisplay() { RelativeSizeAxes = Axes.Both; @@ -98,7 +95,7 @@ namespace osu.Game.Screens.Edit.Timing controlPointGroups.BindTo(editorBeatmap.ControlPointInfo.Groups); controlPointGroups.BindCollectionChanged((_, __) => updateTimingGroup()); - beatLength.BindValueChanged(_ => showFrom(lastDisplayedBeatIndex), true); + beatLength.BindValueChanged(_ => regenerateDisplay(), true); displayLocked.BindValueChanged(locked => { @@ -139,10 +136,13 @@ namespace osu.Game.Screens.Edit.Timing protected override bool OnMouseMove(MouseMoveEvent e) { - float trackLength = (float)beatmap.Value.Track.Length; - int totalBeatsAvailable = (int)(trackLength / timingPoint.BeatLength); + if (!displayLocked.Value) + { + float trackLength = (float)beatmap.Value.Track.Length; + int totalBeatsAvailable = (int)(trackLength / timingPoint.BeatLength); - Scheduler.AddOnce(showFrom, (int)(e.MousePosition.X / DrawWidth * totalBeatsAvailable)); + Scheduler.AddOnce(showFromBeat, (int)(e.MousePosition.X / DrawWidth * totalBeatsAvailable)); + } return base.OnMouseMove(e); } @@ -157,21 +157,29 @@ namespace osu.Game.Screens.Edit.Timing { base.Update(); - if (!IsHovered) + if (!IsHovered && !displayLocked.Value) { int currentBeat = (int)Math.Floor((editorClock.CurrentTimeAccurate - selectedGroupStartTime) / timingPoint.BeatLength); - showFrom(currentBeat); + showFromBeat(currentBeat); } } - private void showFrom(int beatIndex) + private void showFromBeat(int beatIndex) => + showFromTime(selectedGroupStartTime + beatIndex * timingPoint.BeatLength); + + private void showFromTime(double time) { - if (lastDisplayedBeatIndex == beatIndex) + if (displayedTime == time) return; - if (displayLocked.Value) - return; + displayedTime = time; + regenerateDisplay(); + } + + private void regenerateDisplay() + { + double index = (displayedTime - selectedGroupStartTime) / timingPoint.BeatLength; // Chosen as a pretty usable number across all BPMs. // Optimally we'd want this to scale with the BPM in question, but performing @@ -182,23 +190,25 @@ namespace osu.Game.Screens.Edit.Timing float trackLength = (float)beatmap.Value.Track.Length; float scale = trackLength / visible_width; + const int start_offset = total_waveforms / 2; + // Start displaying from before the current beat - beatIndex -= total_waveforms / 2; + index -= start_offset; foreach (var row in InternalChildren.OfType()) { // offset to the required beat index. - double time = selectedGroupStartTime + beatIndex * timingPoint.BeatLength; + double time = selectedGroupStartTime + index * timingPoint.BeatLength; float offset = (float)(time - visible_width / 2) / trackLength * scale; row.Alpha = time < selectedGroupStartTime || time > selectedGroupEndTime ? 0.2f : 1; row.WaveformOffset = -offset; row.WaveformScale = new Vector2(scale, 1); - row.BeatIndex = beatIndex++; - } + row.BeatIndex = (int)Math.Floor(index); - lastDisplayedBeatIndex = beatIndex; + index++; + } } internal class LockedOverlay : CompositeDrawable From 51014b8748a77964f0ff9b2266119e4e697e724b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 31 May 2022 16:59:19 +0900 Subject: [PATCH 285/395] Ensure offset changes are correctly tracked by the display, even when locked --- .../Edit/Timing/WaveformComparisonDisplay.cs | 20 +++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/osu.Game/Screens/Edit/Timing/WaveformComparisonDisplay.cs b/osu.Game/Screens/Edit/Timing/WaveformComparisonDisplay.cs index 46ecf3d9f2..390965fbea 100644 --- a/osu.Game/Screens/Edit/Timing/WaveformComparisonDisplay.cs +++ b/osu.Game/Screens/Edit/Timing/WaveformComparisonDisplay.cs @@ -107,29 +107,37 @@ namespace osu.Game.Screens.Edit.Timing { beatLength.UnbindBindings(); - selectedGroupStartTime = 0; - selectedGroupEndTime = beatmap.Value.Track.Length; - var tcp = selectedGroup.Value?.ControlPoints.OfType().FirstOrDefault(); if (tcp == null) { timingPoint = new TimingControlPoint(); + // During movement of a control point's offset, this clause can be hit momentarily. + // We don't want to reset the `selectedGroupStartTime` here as we rely on having the + // last value to perform an offset traversal below. + selectedGroupEndTime = beatmap.Value.Track.Length; return; } timingPoint = tcp; beatLength.BindTo(timingPoint.BeatLengthBindable); - selectedGroupStartTime = selectedGroup.Value?.Time ?? 0; + double? newStartTime = selectedGroup.Value?.Time; + + if (newStartTime.HasValue && selectedGroupStartTime != newStartTime) + { + // The offset of the selected point may have changed. + // This handles the case the user has locked the view and expects the display to update with this change. + showFromTime(displayedTime + (newStartTime.Value - selectedGroupStartTime)); + } var nextGroup = editorBeatmap.ControlPointInfo.TimingPoints .SkipWhile(g => g != tcp) .Skip(1) .FirstOrDefault(); - if (nextGroup != null) - selectedGroupEndTime = nextGroup.Time; + selectedGroupStartTime = newStartTime ?? 0; + selectedGroupEndTime = nextGroup?.Time ?? beatmap.Value.Track.Length; } protected override bool OnHover(HoverEvent e) => true; From 94194a04f2931f5068b9ebb9e01de5f6dd13d8b0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 31 May 2022 18:05:38 +0900 Subject: [PATCH 286/395] Animate adjustments --- .../Edit/Timing/WaveformComparisonDisplay.cs | 24 ++++++++++++------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/osu.Game/Screens/Edit/Timing/WaveformComparisonDisplay.cs b/osu.Game/Screens/Edit/Timing/WaveformComparisonDisplay.cs index 390965fbea..172f0ac9b1 100644 --- a/osu.Game/Screens/Edit/Timing/WaveformComparisonDisplay.cs +++ b/osu.Game/Screens/Edit/Timing/WaveformComparisonDisplay.cs @@ -95,7 +95,7 @@ namespace osu.Game.Screens.Edit.Timing controlPointGroups.BindTo(editorBeatmap.ControlPointInfo.Groups); controlPointGroups.BindCollectionChanged((_, __) => updateTimingGroup()); - beatLength.BindValueChanged(_ => regenerateDisplay(), true); + beatLength.BindValueChanged(_ => regenerateDisplay(true), true); displayLocked.BindValueChanged(locked => { @@ -128,7 +128,7 @@ namespace osu.Game.Screens.Edit.Timing { // The offset of the selected point may have changed. // This handles the case the user has locked the view and expects the display to update with this change. - showFromTime(displayedTime + (newStartTime.Value - selectedGroupStartTime)); + showFromTime(displayedTime + (newStartTime.Value - selectedGroupStartTime), true); } var nextGroup = editorBeatmap.ControlPointInfo.TimingPoints @@ -174,18 +174,18 @@ namespace osu.Game.Screens.Edit.Timing } private void showFromBeat(int beatIndex) => - showFromTime(selectedGroupStartTime + beatIndex * timingPoint.BeatLength); + showFromTime(selectedGroupStartTime + beatIndex * timingPoint.BeatLength, false); - private void showFromTime(double time) + private void showFromTime(double time, bool animated) { if (displayedTime == time) return; displayedTime = time; - regenerateDisplay(); + regenerateDisplay(animated); } - private void regenerateDisplay() + private void regenerateDisplay(bool animated) { double index = (displayedTime - selectedGroupStartTime) / timingPoint.BeatLength; @@ -211,7 +211,7 @@ namespace osu.Game.Screens.Edit.Timing float offset = (float)(time - visible_width / 2) / trackLength * scale; row.Alpha = time < selectedGroupStartTime || time > selectedGroupEndTime ? 0.2f : 1; - row.WaveformOffset = -offset; + row.WaveformOffsetTo(-offset, animated); row.WaveformScale = new Vector2(scale, 1); row.BeatIndex = (int)Math.Floor(index); @@ -314,7 +314,15 @@ namespace osu.Game.Screens.Edit.Timing public int BeatIndex { set => beatIndexText.Text = value.ToString(); } public Vector2 WaveformScale { set => waveformGraph.Scale = value; } - public float WaveformOffset { set => waveformGraph.X = value; } + + public void WaveformOffsetTo(float value, bool animated) => + this.TransformTo(nameof(waveformOffset), value, animated ? 300 : 0, Easing.OutQuint); + + private float waveformOffset + { + get => waveformGraph.X; + set => waveformGraph.X = value; + } } } } From 475cc8174ff036f626f883ab6a75774c960a5e74 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 31 May 2022 18:17:08 +0900 Subject: [PATCH 287/395] Fix off-by-one display issue when adjusting offset --- .../Edit/Timing/WaveformComparisonDisplay.cs | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/osu.Game/Screens/Edit/Timing/WaveformComparisonDisplay.cs b/osu.Game/Screens/Edit/Timing/WaveformComparisonDisplay.cs index 172f0ac9b1..ce13a8a244 100644 --- a/osu.Game/Screens/Edit/Timing/WaveformComparisonDisplay.cs +++ b/osu.Game/Screens/Edit/Timing/WaveformComparisonDisplay.cs @@ -123,13 +123,7 @@ namespace osu.Game.Screens.Edit.Timing beatLength.BindTo(timingPoint.BeatLengthBindable); double? newStartTime = selectedGroup.Value?.Time; - - if (newStartTime.HasValue && selectedGroupStartTime != newStartTime) - { - // The offset of the selected point may have changed. - // This handles the case the user has locked the view and expects the display to update with this change. - showFromTime(displayedTime + (newStartTime.Value - selectedGroupStartTime), true); - } + double? offsetChange = newStartTime - selectedGroupStartTime; var nextGroup = editorBeatmap.ControlPointInfo.TimingPoints .SkipWhile(g => g != tcp) @@ -138,6 +132,13 @@ namespace osu.Game.Screens.Edit.Timing selectedGroupStartTime = newStartTime ?? 0; selectedGroupEndTime = nextGroup?.Time ?? beatmap.Value.Track.Length; + + if (newStartTime.HasValue && offsetChange.HasValue) + { + // The offset of the selected point may have changed. + // This handles the case the user has locked the view and expects the display to update with this change. + showFromTime(displayedTime + offsetChange.Value, true); + } } protected override bool OnHover(HoverEvent e) => true; From af0f934e1a1b47080aefb8f4353a411c3e20c732 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 31 May 2022 17:37:48 +0900 Subject: [PATCH 288/395] Move raw ScoreProcessor values into ScoringValues struct --- osu.Game/Rulesets/Scoring/ScoreProcessor.cs | 111 +++++++++++++------- osu.Game/Scoring/ScoringValues.cs | 38 +++++++ 2 files changed, 111 insertions(+), 38 deletions(-) create mode 100644 osu.Game/Scoring/ScoringValues.cs diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index 1403a3f243..493da763cb 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -88,17 +88,20 @@ namespace osu.Game.Rulesets.Scoring private readonly double accuracyPortion; private readonly double comboPortion; - private int maxAchievableCombo; + /// + /// Maximum achievable scoring values. + /// + private ScoringValues maximumScoringValues; /// - /// The maximum achievable base score. + /// Maximum achievable scoring values up to the current point in time. /// - private double maxBaseScore; + private ScoringValues rollingMaximumScoringValues; /// - /// The maximum number of basic (non-tick and non-bonus) hitobjects. + /// Scoring values for the current play. /// - private int maxBasicHitObjects; + private ScoringValues currentScoringValues; /// /// The maximum of a basic (non-tick and non-bonus) hitobject. @@ -106,9 +109,6 @@ namespace osu.Game.Rulesets.Scoring /// private HitResult? maxBasicResult; - private double rollingMaxBaseScore; - private double baseScore; - private int basicHitObjects; private bool beatmapApplied; private readonly Dictionary scoreResultCounts = new Dictionary(); @@ -164,23 +164,42 @@ namespace osu.Game.Rulesets.Scoring scoreResultCounts[result.Type] = scoreResultCounts.GetValueOrDefault(result.Type) + 1; if (!result.Type.IsScorable()) - return; + { + // The inverse of non-scorable (ignore) judgements may be bonus judgements. + if (result.Judgement.MaxResult.IsBonus()) + rollingMaximumScoringValues.BonusScore += result.Judgement.MaxNumericResult; + return; + } + + // Update rolling combo. if (result.Type.IncreasesCombo()) Combo.Value++; else if (result.Type.BreaksCombo()) Combo.Value = 0; - double scoreIncrease = result.Type.IsHit() ? result.Judgement.NumericResultFor(result) : 0; + // Update maximum combo. + currentScoringValues.MaxCombo = HighestCombo.Value; + rollingMaximumScoringValues.MaxCombo += result.Judgement.MaxResult.AffectsCombo() ? 1 : 0; - if (!result.Type.IsBonus()) + // Update base/bonus score. + if (result.Type.IsBonus()) { - baseScore += scoreIncrease; - rollingMaxBaseScore += result.Judgement.MaxNumericResult; + currentScoringValues.BonusScore += result.Type.IsHit() ? result.Judgement.NumericResultFor(result) : 0; + rollingMaximumScoringValues.BonusScore += result.Judgement.MaxNumericResult; + } + else + { + currentScoringValues.BaseScore += result.Type.IsHit() ? result.Judgement.NumericResultFor(result) : 0; + rollingMaximumScoringValues.BaseScore += result.Judgement.MaxNumericResult; } + // Update hitobject count. if (result.Type.IsBasic()) - basicHitObjects++; + { + currentScoringValues.HitObjects++; + rollingMaximumScoringValues.HitObjects++; + } hitEvents.Add(CreateHitEvent(result)); lastHitObject = result.HitObject; @@ -207,18 +226,36 @@ namespace osu.Game.Rulesets.Scoring scoreResultCounts[result.Type] = scoreResultCounts.GetValueOrDefault(result.Type) - 1; if (!result.Type.IsScorable()) - return; - - double scoreIncrease = result.Type.IsHit() ? result.Judgement.NumericResultFor(result) : 0; - - if (!result.Type.IsBonus()) { - baseScore -= scoreIncrease; - rollingMaxBaseScore -= result.Judgement.MaxNumericResult; + // The inverse of non-scorable (ignore) judgements may be bonus judgements. + if (result.Judgement.MaxResult.IsBonus()) + rollingMaximumScoringValues.BonusScore -= result.Judgement.MaxNumericResult; + + return; } + // Update maximum combo. + currentScoringValues.MaxCombo = HighestCombo.Value; + rollingMaximumScoringValues.MaxCombo -= result.Judgement.MaxResult.AffectsCombo() ? 1 : 0; + + // Update base/bonus score. + if (result.Type.IsBonus()) + { + currentScoringValues.BonusScore -= result.Type.IsHit() ? result.Judgement.NumericResultFor(result) : 0; + rollingMaximumScoringValues.BonusScore -= result.Judgement.MaxNumericResult; + } + else + { + currentScoringValues.BaseScore -= result.Type.IsHit() ? result.Judgement.NumericResultFor(result) : 0; + rollingMaximumScoringValues.BaseScore -= result.Judgement.MaxNumericResult; + } + + // Update hitobject count. if (result.Type.IsBasic()) - basicHitObjects--; + { + currentScoringValues.HitObjects--; + rollingMaximumScoringValues.HitObjects--; + } Debug.Assert(hitEvents.Count > 0); lastHitObject = hitEvents[^1].LastHitObject; @@ -229,12 +266,12 @@ namespace osu.Game.Rulesets.Scoring private void updateScore() { - double rollingAccuracyRatio = rollingMaxBaseScore > 0 ? baseScore / rollingMaxBaseScore : 1; - double accuracyRatio = maxBaseScore > 0 ? baseScore / maxBaseScore : 1; - double comboRatio = maxAchievableCombo > 0 ? (double)HighestCombo.Value / maxAchievableCombo : 1; + double rollingAccuracyRatio = rollingMaximumScoringValues.BaseScore > 0 ? currentScoringValues.BaseScore / rollingMaximumScoringValues.BaseScore : 1; + double accuracyRatio = maximumScoringValues.BaseScore > 0 ? currentScoringValues.BaseScore / maximumScoringValues.BaseScore : 1; + double comboRatio = maximumScoringValues.MaxCombo > 0 ? currentScoringValues.MaxCombo / maximumScoringValues.MaxCombo : 1; Accuracy.Value = rollingAccuracyRatio; - TotalScore.Value = ComputeScore(Mode.Value, accuracyRatio, comboRatio, getBonusScore(scoreResultCounts), maxBasicHitObjects); + TotalScore.Value = ComputeScore(Mode.Value, accuracyRatio, comboRatio, getBonusScore(scoreResultCounts), maximumScoringValues.HitObjects); } /// @@ -286,10 +323,10 @@ namespace osu.Game.Rulesets.Scoring out _, out _); - double accuracyRatio = maxBaseScore > 0 ? extractedBaseScore / maxBaseScore : 1; - double comboRatio = maxAchievableCombo > 0 ? (double)scoreInfo.MaxCombo / maxAchievableCombo : 1; + double accuracyRatio = maximumScoringValues.BaseScore > 0 ? extractedBaseScore / maximumScoringValues.BaseScore : 1; + double comboRatio = maximumScoringValues.MaxCombo > 0 ? (double)scoreInfo.MaxCombo / maximumScoringValues.MaxCombo : 1; - return ComputeScore(mode, accuracyRatio, comboRatio, getBonusScore(scoreInfo.Statistics), maxBasicHitObjects); + return ComputeScore(mode, accuracyRatio, comboRatio, getBonusScore(scoreInfo.Statistics), maximumScoringValues.HitObjects); } /// @@ -398,15 +435,10 @@ namespace osu.Game.Rulesets.Scoring lastHitObject = null; if (storeResults) - { - maxAchievableCombo = HighestCombo.Value; - maxBaseScore = baseScore; - maxBasicHitObjects = basicHitObjects; - } + maximumScoringValues = currentScoringValues; - baseScore = 0; - rollingMaxBaseScore = 0; - basicHitObjects = 0; + currentScoringValues = default; + rollingMaximumScoringValues = default; TotalScore.Value = 0; Accuracy.Value = 1; @@ -440,7 +472,10 @@ namespace osu.Game.Rulesets.Scoring if (frame.Header == null) return; - extractFromStatistics(frame.Header.Statistics, out baseScore, out rollingMaxBaseScore, out _, out _); + extractFromStatistics(frame.Header.Statistics, out double baseScore, out double rollingMaxBaseScore, out _, out _); + currentScoringValues.BaseScore = baseScore; + rollingMaximumScoringValues.BaseScore = rollingMaxBaseScore; + HighestCombo.Value = frame.Header.MaxCombo; scoreResultCounts.Clear(); diff --git a/osu.Game/Scoring/ScoringValues.cs b/osu.Game/Scoring/ScoringValues.cs new file mode 100644 index 0000000000..4b562c20e4 --- /dev/null +++ b/osu.Game/Scoring/ScoringValues.cs @@ -0,0 +1,38 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using MessagePack; +using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Scoring; + +namespace osu.Game.Scoring +{ + [MessagePackObject] + public struct ScoringValues + { + /// + /// The sum of all "basic" scoring values. See: and . + /// + [Key(0)] + public double BaseScore; + + /// + /// The sum of all "bonus" scoring values. See: and . + /// + [Key(1)] + public double BonusScore; + + /// + /// The highest achieved combo. + /// + [Key(2)] + public int MaxCombo; + + /// + /// The count of "basic" s. See: . + /// + [Key(3)] + public int HitObjects; + } +} From d0e3e50ca7c3ca2fa8b6a40ab74805b0e2904c58 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 31 May 2022 17:50:18 +0900 Subject: [PATCH 289/395] Extract score statistics directly into ScoringValues --- osu.Game/Rulesets/Scoring/ScoreProcessor.cs | 104 +++++++++----------- 1 file changed, 49 insertions(+), 55 deletions(-) diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index 493da763cb..c108380396 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -288,16 +288,12 @@ namespace osu.Game.Rulesets.Scoring if (!ruleset.RulesetInfo.Equals(scoreInfo.Ruleset)) throw new ArgumentException($"Unexpected score ruleset. Expected \"{ruleset.RulesetInfo.ShortName}\" but was \"{scoreInfo.Ruleset.ShortName}\"."); - extractFromStatistics(scoreInfo.Statistics, - out double extractedBaseScore, - out double extractedMaxBaseScore, - out int extractedMaxCombo, - out int extractedBasicHitObjects); + extractFromStatistics(scoreInfo.Statistics, out var current, out var max); - double accuracyRatio = extractedMaxBaseScore > 0 ? extractedBaseScore / extractedMaxBaseScore : 1; - double comboRatio = extractedMaxCombo > 0 ? (double)scoreInfo.MaxCombo / extractedMaxCombo : 1; + double accuracyRatio = max.BaseScore > 0 ? current.BaseScore / max.BaseScore : 1; + double comboRatio = max.MaxCombo > 0 ? (double)scoreInfo.MaxCombo / max.MaxCombo : 1; - return ComputeScore(mode, accuracyRatio, comboRatio, getBonusScore(scoreInfo.Statistics), extractedBasicHitObjects); + return ComputeScore(mode, accuracyRatio, comboRatio, getBonusScore(scoreInfo.Statistics), max.HitObjects); } /// @@ -317,13 +313,9 @@ namespace osu.Game.Rulesets.Scoring if (!beatmapApplied) throw new InvalidOperationException($"Cannot compute partial score without calling {nameof(ApplyBeatmap)}."); - extractFromStatistics(scoreInfo.Statistics, - out double extractedBaseScore, - out _, - out _, - out _); + extractFromStatistics(scoreInfo.Statistics, out var current, out _); - double accuracyRatio = maximumScoringValues.BaseScore > 0 ? extractedBaseScore / maximumScoringValues.BaseScore : 1; + double accuracyRatio = maximumScoringValues.BaseScore > 0 ? current.BaseScore / maximumScoringValues.BaseScore : 1; double comboRatio = maximumScoringValues.MaxCombo > 0 ? (double)scoreInfo.MaxCombo / maximumScoringValues.MaxCombo : 1; return ComputeScore(mode, accuracyRatio, comboRatio, getBonusScore(scoreInfo.Statistics), maximumScoringValues.HitObjects); @@ -353,14 +345,9 @@ namespace osu.Game.Rulesets.Scoring // Note: This cannot be applied universally to all legacy scores, as some rulesets (e.g. catch) group multiple judgements together. if (scoreInfo.IsLegacyScore && scoreInfo.Ruleset.OnlineID == 3) { - extractFromStatistics(scoreInfo.Statistics, - out double computedBaseScore, - out double computedMaxBaseScore, - out _, - out _); - - if (computedMaxBaseScore > 0) - accuracyRatio = computedBaseScore / computedMaxBaseScore; + extractFromStatistics(scoreInfo.Statistics, out var current, out var maximum); + if (maximum.BaseScore > 0) + accuracyRatio = current.BaseScore / current.MaxCombo; } int computedBasicHitObjects = scoreInfo.Statistics.Where(kvp => kvp.Key.IsBasic()).Select(kvp => kvp.Value).Sum(); @@ -472,10 +459,7 @@ namespace osu.Game.Rulesets.Scoring if (frame.Header == null) return; - extractFromStatistics(frame.Header.Statistics, out double baseScore, out double rollingMaxBaseScore, out _, out _); - currentScoringValues.BaseScore = baseScore; - rollingMaximumScoringValues.BaseScore = rollingMaxBaseScore; - + extractFromStatistics(frame.Header.Statistics, out currentScoringValues, out rollingMaximumScoringValues); HighestCombo.Value = frame.Header.MaxCombo; scoreResultCounts.Clear(); @@ -486,49 +470,59 @@ namespace osu.Game.Rulesets.Scoring OnResetFromReplayFrame?.Invoke(); } - private void extractFromStatistics(IReadOnlyDictionary statistics, out double baseScore, out double maxBaseScore, out int maxCombo, - out int basicHitObjects) + private void extractFromStatistics(IReadOnlyDictionary statistics, out ScoringValues current, out ScoringValues maximum) { - baseScore = 0; - maxBaseScore = 0; - maxCombo = 0; - basicHitObjects = 0; + current = default; + maximum = default; foreach ((HitResult result, int count) in statistics) { - // Bonus scores are counted separately directly from the statistics dictionary later on. - if (!result.IsScorable() || result.IsBonus()) + if (!result.IsScorable()) continue; - // The maximum result of this judgement if it wasn't a miss. - // E.g. For a GOOD judgement, the max result is either GREAT/PERFECT depending on which one the ruleset uses (osu!: GREAT, osu!mania: PERFECT). - HitResult maxResult; - - switch (result) + if (result.IsBonus()) { - case HitResult.LargeTickHit: - case HitResult.LargeTickMiss: - maxResult = HitResult.LargeTickHit; - break; + current.BonusScore += count * Judgement.ToNumericResult(result); + maximum.BonusScore += count * Judgement.ToNumericResult(result); + } + else + { + // The maximum result of this judgement if it wasn't a miss. + // E.g. For a GOOD judgement, the max result is either GREAT/PERFECT depending on which one the ruleset uses (osu!: GREAT, osu!mania: PERFECT). + HitResult maxResult; - case HitResult.SmallTickHit: - case HitResult.SmallTickMiss: - maxResult = HitResult.SmallTickHit; - break; + switch (result) + { + case HitResult.LargeTickHit: + case HitResult.LargeTickMiss: + maxResult = HitResult.LargeTickHit; + break; - default: - maxResult = maxBasicResult ??= ruleset.GetHitResults().OrderByDescending(kvp => Judgement.ToNumericResult(kvp.result)).First().result; - break; + case HitResult.SmallTickHit: + case HitResult.SmallTickMiss: + maxResult = HitResult.SmallTickHit; + break; + + default: + maxResult = maxBasicResult ??= ruleset.GetHitResults().OrderByDescending(kvp => Judgement.ToNumericResult(kvp.result)).First().result; + break; + } + + current.BaseScore += count * Judgement.ToNumericResult(result); + maximum.BaseScore += count * Judgement.ToNumericResult(maxResult); } - baseScore += count * Judgement.ToNumericResult(result); - maxBaseScore += count * Judgement.ToNumericResult(maxResult); - if (result.AffectsCombo()) - maxCombo += count; + { + current.MaxCombo += count; + maximum.MaxCombo += count; + } if (result.IsBasic()) - basicHitObjects += count; + { + current.HitObjects += count; + maximum.HitObjects += count; + } } } From a809a19eeca00c59d67b7b3e638526137ea95e0d Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 31 May 2022 17:51:46 +0900 Subject: [PATCH 290/395] Remove getBonusScore() --- osu.Game/Rulesets/Scoring/ScoreProcessor.cs | 29 ++++++--------------- 1 file changed, 8 insertions(+), 21 deletions(-) diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index c108380396..372fcbb061 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -271,7 +271,7 @@ namespace osu.Game.Rulesets.Scoring double comboRatio = maximumScoringValues.MaxCombo > 0 ? currentScoringValues.MaxCombo / maximumScoringValues.MaxCombo : 1; Accuracy.Value = rollingAccuracyRatio; - TotalScore.Value = ComputeScore(Mode.Value, accuracyRatio, comboRatio, getBonusScore(scoreResultCounts), maximumScoringValues.HitObjects); + TotalScore.Value = ComputeScore(Mode.Value, accuracyRatio, comboRatio, currentScoringValues.BonusScore, maximumScoringValues.HitObjects); } /// @@ -293,7 +293,7 @@ namespace osu.Game.Rulesets.Scoring double accuracyRatio = max.BaseScore > 0 ? current.BaseScore / max.BaseScore : 1; double comboRatio = max.MaxCombo > 0 ? (double)scoreInfo.MaxCombo / max.MaxCombo : 1; - return ComputeScore(mode, accuracyRatio, comboRatio, getBonusScore(scoreInfo.Statistics), max.HitObjects); + return ComputeScore(mode, accuracyRatio, comboRatio, current.BonusScore, max.HitObjects); } /// @@ -318,7 +318,7 @@ namespace osu.Game.Rulesets.Scoring double accuracyRatio = maximumScoringValues.BaseScore > 0 ? current.BaseScore / maximumScoringValues.BaseScore : 1; double comboRatio = maximumScoringValues.MaxCombo > 0 ? (double)scoreInfo.MaxCombo / maximumScoringValues.MaxCombo : 1; - return ComputeScore(mode, accuracyRatio, comboRatio, getBonusScore(scoreInfo.Statistics), maximumScoringValues.HitObjects); + return ComputeScore(mode, accuracyRatio, comboRatio, current.BonusScore, maximumScoringValues.HitObjects); } /// @@ -340,19 +340,15 @@ namespace osu.Game.Rulesets.Scoring double accuracyRatio = scoreInfo.Accuracy; double comboRatio = maxAchievableCombo > 0 ? (double)scoreInfo.MaxCombo / maxAchievableCombo : 1; + extractFromStatistics(scoreInfo.Statistics, out var current, out var maximum); + // For legacy osu!mania scores, a full-GREAT score has 100% accuracy. If combined with a full-combo, the score becomes indistinguishable from a full-PERFECT score. // To get around this, the accuracy ratio is always recalculated based on the hit statistics rather than trusting the score. // Note: This cannot be applied universally to all legacy scores, as some rulesets (e.g. catch) group multiple judgements together. - if (scoreInfo.IsLegacyScore && scoreInfo.Ruleset.OnlineID == 3) - { - extractFromStatistics(scoreInfo.Statistics, out var current, out var maximum); - if (maximum.BaseScore > 0) - accuracyRatio = current.BaseScore / current.MaxCombo; - } + if (scoreInfo.IsLegacyScore && scoreInfo.Ruleset.OnlineID == 3 && maximum.BaseScore > 0) + accuracyRatio = current.BaseScore / current.MaxCombo; - int computedBasicHitObjects = scoreInfo.Statistics.Where(kvp => kvp.Key.IsBasic()).Select(kvp => kvp.Value).Sum(); - - return ComputeScore(mode, accuracyRatio, comboRatio, getBonusScore(scoreInfo.Statistics), computedBasicHitObjects); + return ComputeScore(mode, accuracyRatio, comboRatio, current.BonusScore, maximum.HitObjects); } /// @@ -382,15 +378,6 @@ namespace osu.Game.Rulesets.Scoring } } - /// - /// Calculates the total bonus score from score statistics. - /// - /// The score statistics. - /// The total bonus score. - private double getBonusScore(IReadOnlyDictionary statistics) - => statistics.GetValueOrDefault(HitResult.SmallBonus) * Judgement.SMALL_BONUS_SCORE - + statistics.GetValueOrDefault(HitResult.LargeBonus) * Judgement.LARGE_BONUS_SCORE; - private ScoreRank rankFrom(double acc) { if (acc == 1) From 2289812801ffc632dd318e626688b68b18613428 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 31 May 2022 17:57:12 +0900 Subject: [PATCH 291/395] Add method to compute score from ScoringValues --- osu.Game/Rulesets/Scoring/ScoreProcessor.cs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index 372fcbb061..27c2712641 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -351,6 +351,20 @@ namespace osu.Game.Rulesets.Scoring return ComputeScore(mode, accuracyRatio, comboRatio, current.BonusScore, maximum.HitObjects); } + /// + /// Computes the total score from scoring values. + /// + /// The to represent the score as. + /// The current scoring values. + /// The maximum scoring values. + /// The total score computed from the given scoring values. + public double ComputeScore(ScoringMode mode, ScoringValues current, ScoringValues maximum) + { + double accuracyRatio = maximum.BaseScore > 0 ? current.BaseScore / maximum.BaseScore : 1; + double comboRatio = maximum.MaxCombo > 0 ? current.MaxCombo / maximum.MaxCombo : 1; + return ComputeScore(mode, accuracyRatio, comboRatio, current.BonusScore, maximum.HitObjects); + } + /// /// Computes the total score from individual scoring components. /// From 6ccdb618531dd10a906bcc60649a1545a0cacb16 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 31 May 2022 18:01:02 +0900 Subject: [PATCH 292/395] Use new ComputeScore() overload in more cases --- osu.Game/Rulesets/Scoring/ScoreProcessor.cs | 22 +++++++-------------- 1 file changed, 7 insertions(+), 15 deletions(-) diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index 27c2712641..b286839fa7 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -266,12 +266,8 @@ namespace osu.Game.Rulesets.Scoring private void updateScore() { - double rollingAccuracyRatio = rollingMaximumScoringValues.BaseScore > 0 ? currentScoringValues.BaseScore / rollingMaximumScoringValues.BaseScore : 1; - double accuracyRatio = maximumScoringValues.BaseScore > 0 ? currentScoringValues.BaseScore / maximumScoringValues.BaseScore : 1; - double comboRatio = maximumScoringValues.MaxCombo > 0 ? currentScoringValues.MaxCombo / maximumScoringValues.MaxCombo : 1; - - Accuracy.Value = rollingAccuracyRatio; - TotalScore.Value = ComputeScore(Mode.Value, accuracyRatio, comboRatio, currentScoringValues.BonusScore, maximumScoringValues.HitObjects); + Accuracy.Value = rollingMaximumScoringValues.BaseScore > 0 ? currentScoringValues.BaseScore / rollingMaximumScoringValues.BaseScore : 1; + TotalScore.Value = ComputeScore(Mode.Value, currentScoringValues, maximumScoringValues); } /// @@ -288,12 +284,10 @@ namespace osu.Game.Rulesets.Scoring if (!ruleset.RulesetInfo.Equals(scoreInfo.Ruleset)) throw new ArgumentException($"Unexpected score ruleset. Expected \"{ruleset.RulesetInfo.ShortName}\" but was \"{scoreInfo.Ruleset.ShortName}\"."); - extractFromStatistics(scoreInfo.Statistics, out var current, out var max); + extractFromStatistics(scoreInfo.Statistics, out var current, out var maximum); + current.MaxCombo = scoreInfo.MaxCombo; - double accuracyRatio = max.BaseScore > 0 ? current.BaseScore / max.BaseScore : 1; - double comboRatio = max.MaxCombo > 0 ? (double)scoreInfo.MaxCombo / max.MaxCombo : 1; - - return ComputeScore(mode, accuracyRatio, comboRatio, current.BonusScore, max.HitObjects); + return ComputeScore(mode, current, maximum); } /// @@ -314,11 +308,9 @@ namespace osu.Game.Rulesets.Scoring throw new InvalidOperationException($"Cannot compute partial score without calling {nameof(ApplyBeatmap)}."); extractFromStatistics(scoreInfo.Statistics, out var current, out _); + current.MaxCombo = scoreInfo.MaxCombo; - double accuracyRatio = maximumScoringValues.BaseScore > 0 ? current.BaseScore / maximumScoringValues.BaseScore : 1; - double comboRatio = maximumScoringValues.MaxCombo > 0 ? (double)scoreInfo.MaxCombo / maximumScoringValues.MaxCombo : 1; - - return ComputeScore(mode, accuracyRatio, comboRatio, current.BonusScore, maximumScoringValues.HitObjects); + return ComputeScore(mode, current, maximumScoringValues); } /// From 20988be6bbad41cc663a8a216096b4a9c6bc5799 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 31 May 2022 18:01:11 +0900 Subject: [PATCH 293/395] Fix incorrect value --- osu.Game/Rulesets/Scoring/ScoreProcessor.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index b286839fa7..18b978b413 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -338,7 +338,7 @@ namespace osu.Game.Rulesets.Scoring // To get around this, the accuracy ratio is always recalculated based on the hit statistics rather than trusting the score. // Note: This cannot be applied universally to all legacy scores, as some rulesets (e.g. catch) group multiple judgements together. if (scoreInfo.IsLegacyScore && scoreInfo.Ruleset.OnlineID == 3 && maximum.BaseScore > 0) - accuracyRatio = current.BaseScore / current.MaxCombo; + accuracyRatio = current.BaseScore / maximum.BaseScore; return ComputeScore(mode, accuracyRatio, comboRatio, current.BonusScore, maximum.HitObjects); } From 44ca350822afee38e7f2c8f464a8b2d608871de2 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 31 May 2022 18:17:25 +0900 Subject: [PATCH 294/395] Reset minimal scoring values from frames --- osu.Game/Rulesets/Scoring/ScoreProcessor.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index 18b978b413..6351d15649 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -452,7 +452,12 @@ namespace osu.Game.Rulesets.Scoring if (frame.Header == null) return; - extractFromStatistics(frame.Header.Statistics, out currentScoringValues, out rollingMaximumScoringValues); + extractFromStatistics(frame.Header.Statistics, out var current, out var maximum); + currentScoringValues.BaseScore = current.BaseScore; + currentScoringValues.MaxCombo = frame.Header.MaxCombo; + rollingMaximumScoringValues.BaseScore = maximum.BaseScore; + rollingMaximumScoringValues.MaxCombo = maximum.MaxCombo; + HighestCombo.Value = frame.Header.MaxCombo; scoreResultCounts.Clear(); From d6d56ee22dfed6ab20f195efb7d53d48a4b9de35 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 31 May 2022 18:17:40 +0900 Subject: [PATCH 295/395] Fix unintentional truncation --- osu.Game/Rulesets/Scoring/ScoreProcessor.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index 6351d15649..afeef9341a 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -353,7 +353,7 @@ namespace osu.Game.Rulesets.Scoring public double ComputeScore(ScoringMode mode, ScoringValues current, ScoringValues maximum) { double accuracyRatio = maximum.BaseScore > 0 ? current.BaseScore / maximum.BaseScore : 1; - double comboRatio = maximum.MaxCombo > 0 ? current.MaxCombo / maximum.MaxCombo : 1; + double comboRatio = maximum.MaxCombo > 0 ? (double)current.MaxCombo / maximum.MaxCombo : 1; return ComputeScore(mode, accuracyRatio, comboRatio, current.BonusScore, maximum.HitObjects); } From 28d8799e119318b860c1d551ba79b66645a8682c Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 31 May 2022 18:39:29 +0900 Subject: [PATCH 296/395] Add overloads to + document + expose ExtractScoringValues --- osu.Game/Rulesets/Scoring/ScoreProcessor.cs | 89 ++++++++++++++++++--- 1 file changed, 77 insertions(+), 12 deletions(-) diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index afeef9341a..96cf4bcc24 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -11,6 +11,7 @@ using osu.Framework.Bindables; using osu.Framework.Utils; using osu.Game.Beatmaps; using osu.Game.Extensions; +using osu.Game.Online.Spectator; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects; @@ -89,13 +90,16 @@ namespace osu.Game.Rulesets.Scoring private readonly double comboPortion; /// - /// Maximum achievable scoring values. + /// Scoring values for a perfect play. /// private ScoringValues maximumScoringValues; /// /// Maximum achievable scoring values up to the current point in time. /// + /// + /// This is only used to determine the accuracy with respect to the current point in time for an ongoing play session. + /// private ScoringValues rollingMaximumScoringValues; /// @@ -284,7 +288,7 @@ namespace osu.Game.Rulesets.Scoring if (!ruleset.RulesetInfo.Equals(scoreInfo.Ruleset)) throw new ArgumentException($"Unexpected score ruleset. Expected \"{ruleset.RulesetInfo.ShortName}\" but was \"{scoreInfo.Ruleset.ShortName}\"."); - extractFromStatistics(scoreInfo.Statistics, out var current, out var maximum); + extractScoringValues(scoreInfo.Statistics, out var current, out var maximum); current.MaxCombo = scoreInfo.MaxCombo; return ComputeScore(mode, current, maximum); @@ -307,7 +311,7 @@ namespace osu.Game.Rulesets.Scoring if (!beatmapApplied) throw new InvalidOperationException($"Cannot compute partial score without calling {nameof(ApplyBeatmap)}."); - extractFromStatistics(scoreInfo.Statistics, out var current, out _); + extractScoringValues(scoreInfo.Statistics, out var current, out _); current.MaxCombo = scoreInfo.MaxCombo; return ComputeScore(mode, current, maximumScoringValues); @@ -332,7 +336,7 @@ namespace osu.Game.Rulesets.Scoring double accuracyRatio = scoreInfo.Accuracy; double comboRatio = maxAchievableCombo > 0 ? (double)scoreInfo.MaxCombo / maxAchievableCombo : 1; - extractFromStatistics(scoreInfo.Statistics, out var current, out var maximum); + extractScoringValues(scoreInfo.Statistics, out var current, out var maximum); // For legacy osu!mania scores, a full-GREAT score has 100% accuracy. If combined with a full-combo, the score becomes indistinguishable from a full-PERFECT score. // To get around this, the accuracy ratio is always recalculated based on the hit statistics rather than trusting the score. @@ -452,7 +456,7 @@ namespace osu.Game.Rulesets.Scoring if (frame.Header == null) return; - extractFromStatistics(frame.Header.Statistics, out var current, out var maximum); + extractScoringValues(frame.Header.Statistics, out var current, out var maximum); currentScoringValues.BaseScore = current.BaseScore; currentScoringValues.MaxCombo = frame.Header.MaxCombo; rollingMaximumScoringValues.BaseScore = maximum.BaseScore; @@ -468,7 +472,72 @@ namespace osu.Game.Rulesets.Scoring OnResetFromReplayFrame?.Invoke(); } - private void extractFromStatistics(IReadOnlyDictionary statistics, out ScoringValues current, out ScoringValues maximum) + #region ScoringValue extraction + + /// + /// Applies a best-effort extraction of hit statistics into . + /// + /// + /// This method is useful in a variety of situations, with a few drawbacks that need to be considered: + /// + /// The maximum will always be 0. + /// The current and maximum will always be the same value. + /// + /// Consumers are expected to more accurately fill in the above values through external means. + /// + /// Ensure to fill in the maximum for use in + /// . + /// + /// + /// The score to extract scoring values from. + /// The "current" scoring values, representing the hit statistics as they appear. + /// The "maximum" scoring values, representing the hit statistics as if the maximum hit result was attained each time. + public void ExtractScoringValues(ScoreInfo scoreInfo, out ScoringValues current, out ScoringValues maximum) + { + extractScoringValues(scoreInfo.Statistics, out current, out maximum); + current.MaxCombo = scoreInfo.MaxCombo; + } + + /// + /// Applies a best-effort extraction of hit statistics into . + /// + /// + /// This method is useful in a variety of situations, with a few drawbacks that need to be considered: + /// + /// The maximum will always be 0. + /// The current and maximum will always be the same value. + /// + /// Consumers are expected to more accurately fill in the above values through external means. + /// + /// Ensure to fill in the maximum for use in + /// . + /// + /// + /// The replay frame header to extract scoring values from. + /// The "current" scoring values, representing the hit statistics as they appear. + /// The "maximum" scoring values, representing the hit statistics as if the maximum hit result was attained each time. + public void ExtractScoringValues(FrameHeader header, out ScoringValues current, out ScoringValues maximum) + { + extractScoringValues(header.Statistics, out current, out maximum); + current.MaxCombo = header.MaxCombo; + } + + /// + /// Applies a best-effort extraction of hit statistics into . + /// + /// + /// This method is useful in a variety of situations, with a few drawbacks that need to be considered: + /// + /// The current will always be 0. + /// The maximum will always be 0. + /// The current and maximum will always be the same value. + /// + /// Consumers are expected to more accurately fill in the above values (especially the current ) via external means (e.g. ). + /// + /// The hit statistics to extract scoring values from. + /// The "current" scoring values, representing the hit statistics as they appear. + /// The "maximum" scoring values, representing the hit statistics as if the maximum hit result was attained each time. + private void extractScoringValues(IReadOnlyDictionary statistics, out ScoringValues current, out ScoringValues maximum) { current = default; maximum = default; @@ -479,10 +548,7 @@ namespace osu.Game.Rulesets.Scoring continue; if (result.IsBonus()) - { current.BonusScore += count * Judgement.ToNumericResult(result); - maximum.BonusScore += count * Judgement.ToNumericResult(result); - } else { // The maximum result of this judgement if it wasn't a miss. @@ -511,10 +577,7 @@ namespace osu.Game.Rulesets.Scoring } if (result.AffectsCombo()) - { - current.MaxCombo += count; maximum.MaxCombo += count; - } if (result.IsBasic()) { @@ -524,6 +587,8 @@ namespace osu.Game.Rulesets.Scoring } } + #endregion + protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); From 93240073a645642f540fa9aab46e2fa7ce52490a Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 31 May 2022 19:34:51 +0900 Subject: [PATCH 297/395] Rename field + rewrite xmldoc a bit --- osu.Game/Rulesets/Scoring/ScoreProcessor.cs | 32 ++++++++++----------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index 96cf4bcc24..4463941ec2 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -95,12 +95,12 @@ namespace osu.Game.Rulesets.Scoring private ScoringValues maximumScoringValues; /// - /// Maximum achievable scoring values up to the current point in time. + /// Scoring values for the current play assuming all perfect hits. /// /// /// This is only used to determine the accuracy with respect to the current point in time for an ongoing play session. /// - private ScoringValues rollingMaximumScoringValues; + private ScoringValues currentMaximumScoringValues; /// /// Scoring values for the current play. @@ -171,7 +171,7 @@ namespace osu.Game.Rulesets.Scoring { // The inverse of non-scorable (ignore) judgements may be bonus judgements. if (result.Judgement.MaxResult.IsBonus()) - rollingMaximumScoringValues.BonusScore += result.Judgement.MaxNumericResult; + currentMaximumScoringValues.BonusScore += result.Judgement.MaxNumericResult; return; } @@ -184,25 +184,25 @@ namespace osu.Game.Rulesets.Scoring // Update maximum combo. currentScoringValues.MaxCombo = HighestCombo.Value; - rollingMaximumScoringValues.MaxCombo += result.Judgement.MaxResult.AffectsCombo() ? 1 : 0; + currentMaximumScoringValues.MaxCombo += result.Judgement.MaxResult.AffectsCombo() ? 1 : 0; // Update base/bonus score. if (result.Type.IsBonus()) { currentScoringValues.BonusScore += result.Type.IsHit() ? result.Judgement.NumericResultFor(result) : 0; - rollingMaximumScoringValues.BonusScore += result.Judgement.MaxNumericResult; + currentMaximumScoringValues.BonusScore += result.Judgement.MaxNumericResult; } else { currentScoringValues.BaseScore += result.Type.IsHit() ? result.Judgement.NumericResultFor(result) : 0; - rollingMaximumScoringValues.BaseScore += result.Judgement.MaxNumericResult; + currentMaximumScoringValues.BaseScore += result.Judgement.MaxNumericResult; } // Update hitobject count. if (result.Type.IsBasic()) { currentScoringValues.HitObjects++; - rollingMaximumScoringValues.HitObjects++; + currentMaximumScoringValues.HitObjects++; } hitEvents.Add(CreateHitEvent(result)); @@ -233,32 +233,32 @@ namespace osu.Game.Rulesets.Scoring { // The inverse of non-scorable (ignore) judgements may be bonus judgements. if (result.Judgement.MaxResult.IsBonus()) - rollingMaximumScoringValues.BonusScore -= result.Judgement.MaxNumericResult; + currentMaximumScoringValues.BonusScore -= result.Judgement.MaxNumericResult; return; } // Update maximum combo. currentScoringValues.MaxCombo = HighestCombo.Value; - rollingMaximumScoringValues.MaxCombo -= result.Judgement.MaxResult.AffectsCombo() ? 1 : 0; + currentMaximumScoringValues.MaxCombo -= result.Judgement.MaxResult.AffectsCombo() ? 1 : 0; // Update base/bonus score. if (result.Type.IsBonus()) { currentScoringValues.BonusScore -= result.Type.IsHit() ? result.Judgement.NumericResultFor(result) : 0; - rollingMaximumScoringValues.BonusScore -= result.Judgement.MaxNumericResult; + currentMaximumScoringValues.BonusScore -= result.Judgement.MaxNumericResult; } else { currentScoringValues.BaseScore -= result.Type.IsHit() ? result.Judgement.NumericResultFor(result) : 0; - rollingMaximumScoringValues.BaseScore -= result.Judgement.MaxNumericResult; + currentMaximumScoringValues.BaseScore -= result.Judgement.MaxNumericResult; } // Update hitobject count. if (result.Type.IsBasic()) { currentScoringValues.HitObjects--; - rollingMaximumScoringValues.HitObjects--; + currentMaximumScoringValues.HitObjects--; } Debug.Assert(hitEvents.Count > 0); @@ -270,7 +270,7 @@ namespace osu.Game.Rulesets.Scoring private void updateScore() { - Accuracy.Value = rollingMaximumScoringValues.BaseScore > 0 ? currentScoringValues.BaseScore / rollingMaximumScoringValues.BaseScore : 1; + Accuracy.Value = currentMaximumScoringValues.BaseScore > 0 ? currentScoringValues.BaseScore / currentMaximumScoringValues.BaseScore : 1; TotalScore.Value = ComputeScore(Mode.Value, currentScoringValues, maximumScoringValues); } @@ -422,7 +422,7 @@ namespace osu.Game.Rulesets.Scoring maximumScoringValues = currentScoringValues; currentScoringValues = default; - rollingMaximumScoringValues = default; + currentMaximumScoringValues = default; TotalScore.Value = 0; Accuracy.Value = 1; @@ -459,8 +459,8 @@ namespace osu.Game.Rulesets.Scoring extractScoringValues(frame.Header.Statistics, out var current, out var maximum); currentScoringValues.BaseScore = current.BaseScore; currentScoringValues.MaxCombo = frame.Header.MaxCombo; - rollingMaximumScoringValues.BaseScore = maximum.BaseScore; - rollingMaximumScoringValues.MaxCombo = maximum.MaxCombo; + currentMaximumScoringValues.BaseScore = maximum.BaseScore; + currentMaximumScoringValues.MaxCombo = maximum.MaxCombo; HighestCombo.Value = frame.Header.MaxCombo; From 1e0ee1b2147ff532e7f3cea26e0c2708c4077fdc Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 31 May 2022 19:49:37 +0900 Subject: [PATCH 298/395] Expose MaximumScoringValues for user consumption --- osu.Game/Rulesets/Scoring/ScoreProcessor.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index 4463941ec2..8e69ffa0a0 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -92,7 +92,7 @@ namespace osu.Game.Rulesets.Scoring /// /// Scoring values for a perfect play. /// - private ScoringValues maximumScoringValues; + public ScoringValues MaximumScoringValues { get; private set; } /// /// Scoring values for the current play assuming all perfect hits. @@ -271,7 +271,7 @@ namespace osu.Game.Rulesets.Scoring private void updateScore() { Accuracy.Value = currentMaximumScoringValues.BaseScore > 0 ? currentScoringValues.BaseScore / currentMaximumScoringValues.BaseScore : 1; - TotalScore.Value = ComputeScore(Mode.Value, currentScoringValues, maximumScoringValues); + TotalScore.Value = ComputeScore(Mode.Value, currentScoringValues, MaximumScoringValues); } /// @@ -314,7 +314,7 @@ namespace osu.Game.Rulesets.Scoring extractScoringValues(scoreInfo.Statistics, out var current, out _); current.MaxCombo = scoreInfo.MaxCombo; - return ComputeScore(mode, current, maximumScoringValues); + return ComputeScore(mode, current, MaximumScoringValues); } /// @@ -419,7 +419,7 @@ namespace osu.Game.Rulesets.Scoring lastHitObject = null; if (storeResults) - maximumScoringValues = currentScoringValues; + MaximumScoringValues = currentScoringValues; currentScoringValues = default; currentMaximumScoringValues = default; From a940676fc2ae5d24b34a8c500a8d6bc9fc4a57a2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 31 May 2022 21:10:02 +0900 Subject: [PATCH 299/395] Add adjustment buttons --- .../Editing/TestSceneTapTimingControl.cs | 6 + osu.Game/Screens/Edit/Timing/AdjustButton.cs | 235 ++++++++++++++++++ .../Screens/Edit/Timing/TapTimingControl.cs | 65 ++++- 3 files changed, 305 insertions(+), 1 deletion(-) create mode 100644 osu.Game/Screens/Edit/Timing/AdjustButton.cs diff --git a/osu.Game.Tests/Visual/Editing/TestSceneTapTimingControl.cs b/osu.Game.Tests/Visual/Editing/TestSceneTapTimingControl.cs index 46b45979ea..8dd368f2a9 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneTapTimingControl.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneTapTimingControl.cs @@ -77,6 +77,12 @@ namespace osu.Game.Tests.Visual.Editing timingInfo.Text = $"offset: {selectedGroup.Value.Time:N2} bpm: {selectedGroup.Value.ControlPoints.OfType().First().BPM:N2}"; } + [Test] + public void TestNoop() + { + AddStep("do nothing", () => { }); + } + [Test] public void TestTapThenReset() { diff --git a/osu.Game/Screens/Edit/Timing/AdjustButton.cs b/osu.Game/Screens/Edit/Timing/AdjustButton.cs new file mode 100644 index 0000000000..b0a0517e94 --- /dev/null +++ b/osu.Game/Screens/Edit/Timing/AdjustButton.cs @@ -0,0 +1,235 @@ +// 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.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Input.Events; +using osu.Framework.Localisation; +using osu.Framework.Threading; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Overlays; + +namespace osu.Game.Screens.Edit.Timing +{ + public class AdjustButton : CompositeDrawable + { + public Action Action; + + private readonly double adjustAmount; + private ScheduledDelegate adjustDelegate; + + private const int adjust_levels = 4; + + private const double initial_delay = 300; + + private const double minimum_delay = 80; + + public Container Content { get; set; } + + private double adjustDelay = initial_delay; + + private readonly Box background; + + private readonly OsuSpriteText text; + + public LocalisableString Text + { + get => text?.Text ?? default; + set + { + if (text != null) + text.Text = value; + } + } + + [Resolved] + private OverlayColourProvider colourProvider { get; set; } + + public AdjustButton(double adjustAmount) + { + this.adjustAmount = adjustAmount; + + CornerRadius = 5; + Masking = true; + + AddInternal(Content = new Container + { + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + background = new Box + { + RelativeSizeAxes = Axes.Both, + Depth = float.MaxValue + }, + text = new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Font = OsuFont.Default.With(weight: FontWeight.SemiBold), + Padding = new MarginPadding(5), + Depth = float.MinValue + } + } + }); + } + + [BackgroundDependencyLoader] + private void load() + { + background.Colour = colourProvider.Background3; + + for (int i = 1; i <= adjust_levels; i++) + { + Content.Add(new IncrementBox(i, adjustAmount)); + Content.Add(new IncrementBox(-i, adjustAmount)); + } + } + + protected override bool OnMouseDown(MouseDownEvent e) + { + beginRepeat(); + return true; + } + + protected override void OnMouseUp(MouseUpEvent e) + { + adjustDelegate?.Cancel(); + base.OnMouseUp(e); + } + + private void beginRepeat() + { + adjustDelegate?.Cancel(); + + adjustDelay = initial_delay; + adjustNext(); + + void adjustNext() + { + var hoveredBox = Content.OfType().FirstOrDefault(d => d.IsHovered); + + if (hoveredBox != null) + { + Action(adjustAmount * hoveredBox.Multiplier); + + adjustDelay = Math.Max(minimum_delay, adjustDelay * 0.9f); + + hoveredBox.Flash(); + } + else + { + adjustDelay = initial_delay; + } + + adjustDelegate = Scheduler.AddDelayed(adjustNext, adjustDelay); + } + } + + private class IncrementBox : CompositeDrawable + { + public readonly float Multiplier; + + private readonly Box box; + private readonly OsuSpriteText text; + + public IncrementBox(int index, double amount) + { + Multiplier = Math.Sign(index) * convertMultiplier(index); + + float ratio = (float)index / adjust_levels; + + RelativeSizeAxes = Axes.Both; + + Width = 0.5f * Math.Abs(ratio); + + Anchor direction = index < 0 ? Anchor.x2 : Anchor.x0; + + Origin |= direction; + + Depth = Math.Abs(index); + + Anchor = Anchor.TopCentre; + + InternalChildren = new Drawable[] + { + box = new Box + { + RelativeSizeAxes = Axes.Both, + Blending = BlendingParameters.Additive + }, + text = new OsuSpriteText + { + Anchor = direction, + Origin = direction, + Font = OsuFont.Default.With(size: 10, weight: FontWeight.Bold), + Text = $"{(index > 0 ? "+" : "-")}{Math.Abs(Multiplier * amount)}", + Padding = new MarginPadding(5), + Alpha = 0, + } + }; + } + + [Resolved] + private OverlayColourProvider colourProvider { get; set; } + + protected override void LoadComplete() + { + base.LoadComplete(); + + box.Colour = colourProvider.Background1; + box.Alpha = 0.1f; + } + + private float convertMultiplier(int m) + { + switch (Math.Abs(m)) + { + default: return 1; + + case 2: return 2; + + case 3: return 5; + + case 4: return 10; + } + } + + protected override bool OnHover(HoverEvent e) + { + box.Colour = colourProvider.Colour0; + + box.FadeTo(0.2f, 100, Easing.OutQuint); + text.FadeIn(100, Easing.OutQuint); + return true; + } + + protected override void OnHoverLost(HoverLostEvent e) + { + box.Colour = colourProvider.Background1; + + box.FadeTo(0.1f, 500, Easing.OutQuint); + text.FadeOut(100, Easing.OutQuint); + base.OnHoverLost(e); + } + + public void Flash() + { + box + .FadeTo(0.4f, 20, Easing.OutQuint) + .Then() + .FadeTo(0.2f, 400, Easing.OutQuint); + + text + .MoveToY(-5, 20, Easing.OutQuint) + .Then() + .MoveToY(0, 400, Easing.OutQuint); + } + } + } +} diff --git a/osu.Game/Screens/Edit/Timing/TapTimingControl.cs b/osu.Game/Screens/Edit/Timing/TapTimingControl.cs index d0ab4d1f98..f9f5a20405 100644 --- a/osu.Game/Screens/Edit/Timing/TapTimingControl.cs +++ b/osu.Game/Screens/Edit/Timing/TapTimingControl.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.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -18,6 +19,9 @@ namespace osu.Game.Screens.Edit.Timing [Resolved] private EditorClock editorClock { get; set; } + [Resolved] + private EditorBeatmap beatmap { get; set; } + [Resolved] private Bindable selectedGroup { get; set; } @@ -45,6 +49,7 @@ namespace osu.Game.Screens.Edit.Timing { new Dimension(GridSizeMode.Absolute, 200), new Dimension(GridSizeMode.Absolute, 60), + new Dimension(GridSizeMode.Absolute, 60), }, Content = new[] { @@ -77,7 +82,36 @@ namespace osu.Game.Screens.Edit.Timing }, } } - } + }, + }, + new Drawable[] + { + new Container + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding(10), + Children = new Drawable[] + { + new AdjustButton(1) + { + Text = "Offset", + RelativeSizeAxes = Axes.X, + Width = 0.48f, + Height = 50, + Action = adjustOffset, + }, + new AdjustButton(0.1) + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + Text = "BPM", + RelativeSizeAxes = Axes.X, + Width = 0.48f, + Height = 50, + Action = adjustBpm, + } + } + }, }, new Drawable[] { @@ -113,6 +147,35 @@ namespace osu.Game.Screens.Edit.Timing }; } + private void adjustOffset(double adjust) + { + // VERY TEMPORARY + var currentGroupItems = selectedGroup.Value.ControlPoints.ToArray(); + + beatmap.ControlPointInfo.RemoveGroup(selectedGroup.Value); + + double newOffset = selectedGroup.Value.Time + adjust; + + foreach (var cp in currentGroupItems) + beatmap.ControlPointInfo.Add(newOffset, cp); + + // the control point might not necessarily exist yet, if currentGroupItems was empty. + selectedGroup.Value = beatmap.ControlPointInfo.GroupAt(newOffset, true); + + if (!editorClock.IsRunning) + editorClock.Seek(newOffset); + } + + private void adjustBpm(double adjust) + { + var timing = selectedGroup.Value.ControlPoints.OfType().FirstOrDefault(); + + if (timing == null) + return; + + timing.BeatLength = 60000 / (timing.BPM + adjust); + } + private void tap() { editorClock.Seek(selectedGroup.Value.Time); From f99bcb23a0d296ef7cfd462276d86e730ed13cd8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 31 May 2022 21:49:32 +0900 Subject: [PATCH 300/395] Automatically make first control point added to beatmap have timing data --- osu.Game/Screens/Edit/Timing/TimingScreen.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Timing/TimingScreen.cs b/osu.Game/Screens/Edit/Timing/TimingScreen.cs index f71a8d7d22..c3b804c2d6 100644 --- a/osu.Game/Screens/Edit/Timing/TimingScreen.cs +++ b/osu.Game/Screens/Edit/Timing/TimingScreen.cs @@ -144,7 +144,14 @@ namespace osu.Game.Screens.Edit.Timing private void addNew() { - selectedGroup.Value = Beatmap.ControlPointInfo.GroupAt(clock.CurrentTime, true); + bool isFirstControlPoint = !Beatmap.ControlPointInfo.TimingPoints.Any(); + + var group = Beatmap.ControlPointInfo.GroupAt(clock.CurrentTime, true); + + if (isFirstControlPoint) + group.Add(new TimingControlPoint()); + + selectedGroup.Value = group; } } } From 8240b645b4c93c2d83d2deaa31c49274e8920463 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 31 May 2022 21:49:47 +0900 Subject: [PATCH 301/395] Copy attribute types from currently selected control point to new placements --- osu.Game/Screens/Edit/Timing/TimingScreen.cs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/osu.Game/Screens/Edit/Timing/TimingScreen.cs b/osu.Game/Screens/Edit/Timing/TimingScreen.cs index c3b804c2d6..5ee6106c46 100644 --- a/osu.Game/Screens/Edit/Timing/TimingScreen.cs +++ b/osu.Game/Screens/Edit/Timing/TimingScreen.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; @@ -150,6 +151,23 @@ namespace osu.Game.Screens.Edit.Timing if (isFirstControlPoint) group.Add(new TimingControlPoint()); + else + { + // Try and create matching types from the currently selected control point. + var selected = selectedGroup.Value; + + if (selected != null) + { + foreach (var controlPoint in selected.ControlPoints) + { + if (Activator.CreateInstance(controlPoint.GetType()) is ControlPoint copy) + { + copy.CopyFrom(controlPoint); + group.Add(copy); + } + } + } + } selectedGroup.Value = group; } From bc22079fdcf4c06d89c38298ba83a526abc6f7b7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 31 May 2022 22:02:30 +0900 Subject: [PATCH 302/395] Fix row selected colour flicker when changing offset rapidly --- osu.Game/Screens/Edit/EditorTable.cs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/osu.Game/Screens/Edit/EditorTable.cs b/osu.Game/Screens/Edit/EditorTable.cs index a67a060134..26819dcfe7 100644 --- a/osu.Game/Screens/Edit/EditorTable.cs +++ b/osu.Game/Screens/Edit/EditorTable.cs @@ -99,6 +99,15 @@ namespace osu.Game.Screens.Edit colourSelected = colours.Colour3; } + protected override void LoadComplete() + { + base.LoadComplete(); + + // Reduce flicker of rows when offset is being changed rapidly. + // Probably need to reconsider this. + FinishTransforms(true); + } + private bool selected; public bool Selected From 50c3dfca318ee462abffa7435ebce3326ed68972 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 1 Jun 2022 09:34:46 +0900 Subject: [PATCH 303/395] Apply code reviews --- osu.Game/Rulesets/Scoring/ScoreProcessor.cs | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index 8e69ffa0a0..5712d9c94a 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -6,6 +6,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using System.Diagnostics.Contracts; using System.Linq; using osu.Framework.Bindables; using osu.Framework.Utils; @@ -283,6 +284,7 @@ namespace osu.Game.Rulesets.Scoring /// The to represent the score as. /// The to compute the total score of. /// The total score in the given . + [Pure] public double ComputeFinalScore(ScoringMode mode, ScoreInfo scoreInfo) { if (!ruleset.RulesetInfo.Equals(scoreInfo.Ruleset)) @@ -303,6 +305,7 @@ namespace osu.Game.Rulesets.Scoring /// The to represent the score as. /// The to compute the total score of. /// The total score in the given . + [Pure] public double ComputePartialScore(ScoringMode mode, ScoreInfo scoreInfo) { if (!ruleset.RulesetInfo.Equals(scoreInfo.Ruleset)) @@ -328,6 +331,7 @@ namespace osu.Game.Rulesets.Scoring /// The to compute the total score of. /// The maximum achievable combo for the provided beatmap. /// The total score in the given . + [Pure] public double ComputeFinalLegacyScore(ScoringMode mode, ScoreInfo scoreInfo, int maxAchievableCombo) { if (!ruleset.RulesetInfo.Equals(scoreInfo.Ruleset)) @@ -354,6 +358,7 @@ namespace osu.Game.Rulesets.Scoring /// The current scoring values. /// The maximum scoring values. /// The total score computed from the given scoring values. + [Pure] public double ComputeScore(ScoringMode mode, ScoringValues current, ScoringValues maximum) { double accuracyRatio = maximum.BaseScore > 0 ? current.BaseScore / maximum.BaseScore : 1; @@ -370,6 +375,7 @@ namespace osu.Game.Rulesets.Scoring /// The total bonus score. /// The total number of basic (non-tick and non-bonus) hitobjects in the beatmap. /// The total score computed from the given scoring component ratios. + [Pure] public double ComputeScore(ScoringMode mode, double accuracyRatio, double comboRatio, double bonusScore, int totalBasicHitObjects) { switch (mode) @@ -492,7 +498,8 @@ namespace osu.Game.Rulesets.Scoring /// The score to extract scoring values from. /// The "current" scoring values, representing the hit statistics as they appear. /// The "maximum" scoring values, representing the hit statistics as if the maximum hit result was attained each time. - public void ExtractScoringValues(ScoreInfo scoreInfo, out ScoringValues current, out ScoringValues maximum) + [Pure] + internal void ExtractScoringValues(ScoreInfo scoreInfo, out ScoringValues current, out ScoringValues maximum) { extractScoringValues(scoreInfo.Statistics, out current, out maximum); current.MaxCombo = scoreInfo.MaxCombo; @@ -516,7 +523,8 @@ namespace osu.Game.Rulesets.Scoring /// The replay frame header to extract scoring values from. /// The "current" scoring values, representing the hit statistics as they appear. /// The "maximum" scoring values, representing the hit statistics as if the maximum hit result was attained each time. - public void ExtractScoringValues(FrameHeader header, out ScoringValues current, out ScoringValues maximum) + [Pure] + internal void ExtractScoringValues(FrameHeader header, out ScoringValues current, out ScoringValues maximum) { extractScoringValues(header.Statistics, out current, out maximum); current.MaxCombo = header.MaxCombo; @@ -537,6 +545,7 @@ namespace osu.Game.Rulesets.Scoring /// The hit statistics to extract scoring values from. /// The "current" scoring values, representing the hit statistics as they appear. /// The "maximum" scoring values, representing the hit statistics as if the maximum hit result was attained each time. + [Pure] private void extractScoringValues(IReadOnlyDictionary statistics, out ScoringValues current, out ScoringValues maximum) { current = default; From 425390e13bcb3aca9deb7329819883db4f2831e4 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 1 Jun 2022 09:51:06 +0900 Subject: [PATCH 304/395] Refactor to reduce nested conditions --- osu.Game/Rulesets/Scoring/ScoreProcessor.cs | 93 +++++++++------------ 1 file changed, 38 insertions(+), 55 deletions(-) diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index 5712d9c94a..26a32ab1a0 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -168,43 +168,20 @@ namespace osu.Game.Rulesets.Scoring scoreResultCounts[result.Type] = scoreResultCounts.GetValueOrDefault(result.Type) + 1; + // Always update the maximum scoring values. + applyResult(result.Judgement.MaxResult, ref currentMaximumScoringValues); + currentMaximumScoringValues.MaxCombo += result.Judgement.MaxResult.IncreasesCombo() ? 1 : 0; + if (!result.Type.IsScorable()) - { - // The inverse of non-scorable (ignore) judgements may be bonus judgements. - if (result.Judgement.MaxResult.IsBonus()) - currentMaximumScoringValues.BonusScore += result.Judgement.MaxNumericResult; - return; - } - // Update rolling combo. if (result.Type.IncreasesCombo()) Combo.Value++; else if (result.Type.BreaksCombo()) Combo.Value = 0; - // Update maximum combo. + applyResult(result.Type, ref currentScoringValues); currentScoringValues.MaxCombo = HighestCombo.Value; - currentMaximumScoringValues.MaxCombo += result.Judgement.MaxResult.AffectsCombo() ? 1 : 0; - - // Update base/bonus score. - if (result.Type.IsBonus()) - { - currentScoringValues.BonusScore += result.Type.IsHit() ? result.Judgement.NumericResultFor(result) : 0; - currentMaximumScoringValues.BonusScore += result.Judgement.MaxNumericResult; - } - else - { - currentScoringValues.BaseScore += result.Type.IsHit() ? result.Judgement.NumericResultFor(result) : 0; - currentMaximumScoringValues.BaseScore += result.Judgement.MaxNumericResult; - } - - // Update hitobject count. - if (result.Type.IsBasic()) - { - currentScoringValues.HitObjects++; - currentMaximumScoringValues.HitObjects++; - } hitEvents.Add(CreateHitEvent(result)); lastHitObject = result.HitObject; @@ -212,6 +189,20 @@ namespace osu.Game.Rulesets.Scoring updateScore(); } + private static void applyResult(HitResult result, ref ScoringValues scoringValues) + { + if (!result.IsScorable()) + return; + + if (result.IsBonus()) + scoringValues.BonusScore += result.IsHit() ? Judgement.ToNumericResult(result) : 0; + else + scoringValues.BaseScore += result.IsHit() ? Judgement.ToNumericResult(result) : 0; + + if (result.IsBasic()) + scoringValues.HitObjects++; + } + /// /// Creates the that describes a . /// @@ -230,37 +221,15 @@ namespace osu.Game.Rulesets.Scoring scoreResultCounts[result.Type] = scoreResultCounts.GetValueOrDefault(result.Type) - 1; + // Always update the maximum scoring values. + revertResult(result.Judgement.MaxResult, ref currentMaximumScoringValues); + currentMaximumScoringValues.MaxCombo -= result.Judgement.MaxResult.IncreasesCombo() ? 1 : 0; + if (!result.Type.IsScorable()) - { - // The inverse of non-scorable (ignore) judgements may be bonus judgements. - if (result.Judgement.MaxResult.IsBonus()) - currentMaximumScoringValues.BonusScore -= result.Judgement.MaxNumericResult; - return; - } - // Update maximum combo. + revertResult(result.Type, ref currentScoringValues); currentScoringValues.MaxCombo = HighestCombo.Value; - currentMaximumScoringValues.MaxCombo -= result.Judgement.MaxResult.AffectsCombo() ? 1 : 0; - - // Update base/bonus score. - if (result.Type.IsBonus()) - { - currentScoringValues.BonusScore -= result.Type.IsHit() ? result.Judgement.NumericResultFor(result) : 0; - currentMaximumScoringValues.BonusScore -= result.Judgement.MaxNumericResult; - } - else - { - currentScoringValues.BaseScore -= result.Type.IsHit() ? result.Judgement.NumericResultFor(result) : 0; - currentMaximumScoringValues.BaseScore -= result.Judgement.MaxNumericResult; - } - - // Update hitobject count. - if (result.Type.IsBasic()) - { - currentScoringValues.HitObjects--; - currentMaximumScoringValues.HitObjects--; - } Debug.Assert(hitEvents.Count > 0); lastHitObject = hitEvents[^1].LastHitObject; @@ -269,6 +238,20 @@ namespace osu.Game.Rulesets.Scoring updateScore(); } + private static void revertResult(HitResult result, ref ScoringValues scoringValues) + { + if (!result.IsScorable()) + return; + + if (result.IsBonus()) + scoringValues.BonusScore -= result.IsHit() ? Judgement.ToNumericResult(result) : 0; + else + scoringValues.BaseScore -= result.IsHit() ? Judgement.ToNumericResult(result) : 0; + + if (result.IsBasic()) + scoringValues.HitObjects--; + } + private void updateScore() { Accuracy.Value = currentMaximumScoringValues.BaseScore > 0 ? currentScoringValues.BaseScore / currentMaximumScoringValues.BaseScore : 1; From 25941f618786ffd007f2be0e7be303d424593b2b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 1 Jun 2022 10:47:45 +0900 Subject: [PATCH 305/395] Use `DeepClone` instead of reflection call MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bartłomiej Dach --- osu.Game/Screens/Edit/Timing/TimingScreen.cs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/osu.Game/Screens/Edit/Timing/TimingScreen.cs b/osu.Game/Screens/Edit/Timing/TimingScreen.cs index 5ee6106c46..b94f3e3ef4 100644 --- a/osu.Game/Screens/Edit/Timing/TimingScreen.cs +++ b/osu.Game/Screens/Edit/Timing/TimingScreen.cs @@ -160,11 +160,7 @@ namespace osu.Game.Screens.Edit.Timing { foreach (var controlPoint in selected.ControlPoints) { - if (Activator.CreateInstance(controlPoint.GetType()) is ControlPoint copy) - { - copy.CopyFrom(controlPoint); - group.Add(copy); - } + group.Add(controlPoint.DeepClone()); } } } From 6042cf1a3b9fe2255346853ce791f037823d7ef6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 1 Jun 2022 14:36:25 +0900 Subject: [PATCH 306/395] Add metronome sound Placeholder pending follow-up from @nekodex (which as discussed should probably have a second sound for the metronome locking into "stopped" position). --- .../Screens/Edit/Timing/MetronomeDisplay.cs | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Edit/Timing/MetronomeDisplay.cs b/osu.Game/Screens/Edit/Timing/MetronomeDisplay.cs index 57fcff6a4c..1f065d1551 100644 --- a/osu.Game/Screens/Edit/Timing/MetronomeDisplay.cs +++ b/osu.Game/Screens/Edit/Timing/MetronomeDisplay.cs @@ -3,6 +3,8 @@ using System; using osu.Framework.Allocation; +using osu.Framework.Audio; +using osu.Framework.Audio.Sample; using osu.Framework.Audio.Track; using osu.Framework.Bindables; using osu.Framework.Extensions.LocalisationExtensions; @@ -31,12 +33,16 @@ namespace osu.Game.Screens.Edit.Timing private IAdjustableClock metronomeClock; + private Sample clunk; + [Resolved] private OverlayColourProvider overlayColourProvider { get; set; } [BackgroundDependencyLoader] - private void load() + private void load(AudioManager audio) { + clunk = audio.Samples.Get(@"Multiplayer/countdown-tick"); + const float taper = 25; const float swing_vertical_offset = -23; const float lower_cover_height = 32; @@ -269,8 +275,15 @@ namespace osu.Game.Screens.Edit.Timing if (currentAngle != 0 && Math.Abs(currentAngle - targetAngle) > angle * 1.8f && isSwinging) { - using (stick.BeginDelayedSequence(beatLength / 2)) + using (BeginDelayedSequence(beatLength / 2)) + { stick.FlashColour(overlayColourProvider.Content1, beatLength, Easing.OutQuint); + Schedule(() => + { + clunk.Frequency.Value = RNG.NextDouble(0.98f, 1.02f); + clunk?.Play(); + }); + } } } } From 58ba92772cd8acde594fb9dbc1ad6ba0271ecdf3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 1 Jun 2022 16:01:34 +0900 Subject: [PATCH 307/395] Reword comment to read better MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bartłomiej Dach --- osu.Game/Screens/Edit/Timing/WaveformComparisonDisplay.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Edit/Timing/WaveformComparisonDisplay.cs b/osu.Game/Screens/Edit/Timing/WaveformComparisonDisplay.cs index ce13a8a244..1f12e2e5c9 100644 --- a/osu.Game/Screens/Edit/Timing/WaveformComparisonDisplay.cs +++ b/osu.Game/Screens/Edit/Timing/WaveformComparisonDisplay.cs @@ -112,9 +112,10 @@ namespace osu.Game.Screens.Edit.Timing if (tcp == null) { timingPoint = new TimingControlPoint(); - // During movement of a control point's offset, this clause can be hit momentarily. + // During movement of a control point's offset, this clause can be hit momentarily, + // as moving a control point is implemented by removing it and inserting it at the new time. // We don't want to reset the `selectedGroupStartTime` here as we rely on having the - // last value to perform an offset traversal below. + // last value to update the waveform display below. selectedGroupEndTime = beatmap.Value.Track.Length; return; } From f3fd5bbfc13b027cbae8d657e2d5b8d32ddf9f40 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 1 Jun 2022 16:05:29 +0900 Subject: [PATCH 308/395] Increase flash delay and ensure text is always shown immediately on lock --- .../Edit/Timing/WaveformComparisonDisplay.cs | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/Edit/Timing/WaveformComparisonDisplay.cs b/osu.Game/Screens/Edit/Timing/WaveformComparisonDisplay.cs index 1f12e2e5c9..4275eaf7d3 100644 --- a/osu.Game/Screens/Edit/Timing/WaveformComparisonDisplay.cs +++ b/osu.Game/Screens/Edit/Timing/WaveformComparisonDisplay.cs @@ -99,7 +99,10 @@ namespace osu.Game.Screens.Edit.Timing displayLocked.BindValueChanged(locked => { - lockedOverlay.FadeTo(locked.NewValue ? 1 : 0, 200, Easing.OutQuint); + if (locked.NewValue) + lockedOverlay.Show(); + else + lockedOverlay.Hide(); }, true); } @@ -268,15 +271,20 @@ namespace osu.Game.Screens.Edit.Timing }; } - protected override void LoadComplete() + public override void Show() { - base.LoadComplete(); + this.FadeIn(100, Easing.OutQuint); text - .FadeIn().Then().Delay(500) - .FadeOut().Then().Delay(500) + .FadeIn().Then().Delay(600) + .FadeOut().Then().Delay(600) .Loop(); } + + public override void Hide() + { + this.FadeOut(100, Easing.OutQuint); + } } internal class WaveformRow : CompositeDrawable From 42598ce22aa7852054ce74771b685db186d434aa Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 1 Jun 2022 16:51:34 +0900 Subject: [PATCH 309/395] Refactor `warning` to `notice` in method names and usages --- .../Visual/Settings/TestSceneSettingsItem.cs | 10 +++++----- .../Settings/Sections/Graphics/LayoutSettings.cs | 10 +++++----- .../Sections/Graphics/RendererSettings.cs | 4 ++-- .../Settings/Sections/Input/MouseSettings.cs | 4 ++-- .../Sections/UserInterface/MainMenuSettings.cs | 4 ++-- osu.Game/Overlays/Settings/SettingsItem.cs | 16 ++++++++-------- 6 files changed, 24 insertions(+), 24 deletions(-) diff --git a/osu.Game.Tests/Visual/Settings/TestSceneSettingsItem.cs b/osu.Game.Tests/Visual/Settings/TestSceneSettingsItem.cs index f631e1603b..3e679a7905 100644 --- a/osu.Game.Tests/Visual/Settings/TestSceneSettingsItem.cs +++ b/osu.Game.Tests/Visual/Settings/TestSceneSettingsItem.cs @@ -84,7 +84,7 @@ namespace osu.Game.Tests.Visual.Settings AddStep("clear label", () => textBox.LabelText = default); AddAssert("default value button centre aligned to control size", () => Precision.AlmostEquals(restoreDefaultValueButton.Parent.DrawHeight, control.DrawHeight, 1)); - AddStep("set warning text", () => textBox.SetWarningText("This is some very important warning text! Hopefully it doesn't break the alignment of the default value indicator...")); + AddStep("set warning text", () => textBox.SetNoticeText("This is some very important warning text! Hopefully it doesn't break the alignment of the default value indicator...", true)); AddAssert("default value button centre aligned to control size", () => Precision.AlmostEquals(restoreDefaultValueButton.Parent.DrawHeight, control.DrawHeight, 1)); } @@ -132,16 +132,16 @@ namespace osu.Game.Tests.Visual.Settings AddStep("create settings item", () => Child = numberBox = new SettingsNumberBox()); AddAssert("warning text not created", () => !numberBox.ChildrenOfType().Any()); - AddStep("set warning text", () => numberBox.SetWarningText("this is a warning!")); + AddStep("set warning text", () => numberBox.SetNoticeText("this is a warning!", true)); AddAssert("warning text created", () => numberBox.ChildrenOfType().Single().Alpha == 1); - AddStep("unset warning text", () => numberBox.ClearWarningText()); + AddStep("unset warning text", () => numberBox.ClearNoticeText()); AddAssert("warning text hidden", () => !numberBox.ChildrenOfType().Any()); - AddStep("set warning text again", () => numberBox.SetWarningText("another warning!")); + AddStep("set warning text again", () => numberBox.SetNoticeText("another warning!", true)); AddAssert("warning text shown again", () => numberBox.ChildrenOfType().Single().Alpha == 1); - AddStep("set non warning text", () => numberBox.SetWarningText("you did good!", false)); + AddStep("set non warning text", () => numberBox.SetNoticeText("you did good!")); } } } diff --git a/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs b/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs index 53f84a9bb2..d79ba593f7 100644 --- a/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs @@ -226,7 +226,7 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics { if (windowModeDropdown.Current.Value != WindowMode.Fullscreen) { - windowModeDropdown.SetWarningText(GraphicsSettingsStrings.NotFullscreenNote); + windowModeDropdown.SetNoticeText(GraphicsSettingsStrings.NotFullscreenNote, true); return; } @@ -235,22 +235,22 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics switch (fullscreenCapability.Value) { case FullscreenCapability.Unknown: - windowModeDropdown.SetWarningText(LayoutSettingsStrings.CheckingForFullscreenCapabilities); + windowModeDropdown.SetNoticeText(LayoutSettingsStrings.CheckingForFullscreenCapabilities, true); break; case FullscreenCapability.Capable: - windowModeDropdown.SetWarningText(LayoutSettingsStrings.OsuIsRunningExclusiveFullscreen, false); + windowModeDropdown.SetNoticeText(LayoutSettingsStrings.OsuIsRunningExclusiveFullscreen); break; case FullscreenCapability.Incapable: - windowModeDropdown.SetWarningText(LayoutSettingsStrings.UnableToRunExclusiveFullscreen); + windowModeDropdown.SetNoticeText(LayoutSettingsStrings.UnableToRunExclusiveFullscreen, true); break; } } else { // We can only detect exclusive fullscreen status on windows currently. - windowModeDropdown.ClearWarningText(); + windowModeDropdown.ClearNoticeText(); } } diff --git a/osu.Game/Overlays/Settings/Sections/Graphics/RendererSettings.cs b/osu.Game/Overlays/Settings/Sections/Graphics/RendererSettings.cs index 6ec4decdda..8c3e45cd62 100644 --- a/osu.Game/Overlays/Settings/Sections/Graphics/RendererSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Graphics/RendererSettings.cs @@ -51,11 +51,11 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics switch (limit.NewValue) { case FrameSync.Unlimited: - frameLimiterDropdown.SetWarningText(GraphicsSettingsStrings.UnlimitedFramesNote); + frameLimiterDropdown.SetNoticeText(GraphicsSettingsStrings.UnlimitedFramesNote, true); break; default: - frameLimiterDropdown.ClearWarningText(); + frameLimiterDropdown.ClearNoticeText(); break; } }, true); diff --git a/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs b/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs index 042cec106a..1511d53b6b 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs @@ -117,9 +117,9 @@ namespace osu.Game.Overlays.Settings.Sections.Input if (RuntimeInfo.OS != RuntimeInfo.Platform.Windows) { if (highPrecision.NewValue) - highPrecisionMouse.SetWarningText(MouseSettingsStrings.HighPrecisionPlatformWarning); + highPrecisionMouse.SetNoticeText(MouseSettingsStrings.HighPrecisionPlatformWarning, true); else - highPrecisionMouse.ClearWarningText(); + highPrecisionMouse.ClearNoticeText(); } }, true); } diff --git a/osu.Game/Overlays/Settings/Sections/UserInterface/MainMenuSettings.cs b/osu.Game/Overlays/Settings/Sections/UserInterface/MainMenuSettings.cs index d3cc889abe..fceffa09c5 100644 --- a/osu.Game/Overlays/Settings/Sections/UserInterface/MainMenuSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/UserInterface/MainMenuSettings.cs @@ -62,9 +62,9 @@ namespace osu.Game.Overlays.Settings.Sections.UserInterface user.BindValueChanged(u => { if (u.NewValue?.IsSupporter != true) - backgroundSourceDropdown.SetWarningText(UserInterfaceStrings.NotSupporterNote); + backgroundSourceDropdown.SetNoticeText(UserInterfaceStrings.NotSupporterNote, true); else - backgroundSourceDropdown.ClearWarningText(); + backgroundSourceDropdown.ClearNoticeText(); }, true); } } diff --git a/osu.Game/Overlays/Settings/SettingsItem.cs b/osu.Game/Overlays/Settings/SettingsItem.cs index fa521c0126..ea076b77ac 100644 --- a/osu.Game/Overlays/Settings/SettingsItem.cs +++ b/osu.Game/Overlays/Settings/SettingsItem.cs @@ -41,7 +41,7 @@ namespace osu.Game.Overlays.Settings private SpriteText labelText; - private OsuTextFlowContainer warningText; + private OsuTextFlowContainer noticeText; public bool ShowsDefaultIndicator = true; private readonly Container defaultValueIndicatorContainer; @@ -72,24 +72,24 @@ namespace osu.Game.Overlays.Settings /// /// Clear any warning text. /// - public void ClearWarningText() + public void ClearNoticeText() { - warningText?.Expire(); - warningText = null; + noticeText?.Expire(); + noticeText = null; } /// /// Set the text to be displayed at the bottom of this . - /// Generally used to recommend the user change their setting as the current one is considered sub-optimal. + /// Generally used to provide feedback to a user about a sub-optimal setting. /// /// The text to display. /// Whether the text is in a warning state. Will decide how this is visually represented. - public void SetWarningText(LocalisableString text, bool isWarning = true) + public void SetNoticeText(LocalisableString text, bool isWarning = false) { - ClearWarningText(); + ClearNoticeText(); // construct lazily for cases where the label is not needed (may be provided by the Control). - FlowContent.Add(warningText = new LinkFlowContainer(cp => cp.Colour = isWarning ? colours.Yellow : colours.Green) + FlowContent.Add(noticeText = new LinkFlowContainer(cp => cp.Colour = isWarning ? colours.Yellow : colours.Green) { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, From c55c3325a4c7df43dbd8df314e29c9dc7e21354c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 1 Jun 2022 17:43:40 +0900 Subject: [PATCH 310/395] Remove unused using statements --- osu.Game/Screens/Edit/Timing/TimingScreen.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Timing/TimingScreen.cs b/osu.Game/Screens/Edit/Timing/TimingScreen.cs index b94f3e3ef4..3c59cfff64 100644 --- a/osu.Game/Screens/Edit/Timing/TimingScreen.cs +++ b/osu.Game/Screens/Edit/Timing/TimingScreen.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; From 8c54bd46bb2209e8f09965b2a2e5e06d82311e2a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 1 Jun 2022 17:46:05 +0900 Subject: [PATCH 311/395] Rename button to more appropriate name --- osu.Game/Screens/Edit/Timing/TapTimingControl.cs | 4 ++-- .../Edit/Timing/{AdjustButton.cs => TimingAdjustButton.cs} | 7 +++++-- 2 files changed, 7 insertions(+), 4 deletions(-) rename osu.Game/Screens/Edit/Timing/{AdjustButton.cs => TimingAdjustButton.cs} (96%) diff --git a/osu.Game/Screens/Edit/Timing/TapTimingControl.cs b/osu.Game/Screens/Edit/Timing/TapTimingControl.cs index f9f5a20405..990f8d2ce0 100644 --- a/osu.Game/Screens/Edit/Timing/TapTimingControl.cs +++ b/osu.Game/Screens/Edit/Timing/TapTimingControl.cs @@ -92,7 +92,7 @@ namespace osu.Game.Screens.Edit.Timing Padding = new MarginPadding(10), Children = new Drawable[] { - new AdjustButton(1) + new TimingAdjustButton(1) { Text = "Offset", RelativeSizeAxes = Axes.X, @@ -100,7 +100,7 @@ namespace osu.Game.Screens.Edit.Timing Height = 50, Action = adjustOffset, }, - new AdjustButton(0.1) + new TimingAdjustButton(0.1) { Anchor = Anchor.TopRight, Origin = Anchor.TopRight, diff --git a/osu.Game/Screens/Edit/Timing/AdjustButton.cs b/osu.Game/Screens/Edit/Timing/TimingAdjustButton.cs similarity index 96% rename from osu.Game/Screens/Edit/Timing/AdjustButton.cs rename to osu.Game/Screens/Edit/Timing/TimingAdjustButton.cs index b0a0517e94..335f593772 100644 --- a/osu.Game/Screens/Edit/Timing/AdjustButton.cs +++ b/osu.Game/Screens/Edit/Timing/TimingAdjustButton.cs @@ -16,7 +16,10 @@ using osu.Game.Overlays; namespace osu.Game.Screens.Edit.Timing { - public class AdjustButton : CompositeDrawable + /// + /// A button with variable constant output based on hold position and length. + /// + public class TimingAdjustButton : CompositeDrawable { public Action Action; @@ -50,7 +53,7 @@ namespace osu.Game.Screens.Edit.Timing [Resolved] private OverlayColourProvider colourProvider { get; set; } - public AdjustButton(double adjustAmount) + public TimingAdjustButton(double adjustAmount) { this.adjustAmount = adjustAmount; From 1293bbdbd931a7f17f5c5249e69593d0f16430c8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 1 Jun 2022 17:46:33 +0900 Subject: [PATCH 312/395] Remove unnecessary null checks on `Text` property --- osu.Game/Screens/Edit/Timing/TimingAdjustButton.cs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/osu.Game/Screens/Edit/Timing/TimingAdjustButton.cs b/osu.Game/Screens/Edit/Timing/TimingAdjustButton.cs index 335f593772..46bb8356f7 100644 --- a/osu.Game/Screens/Edit/Timing/TimingAdjustButton.cs +++ b/osu.Game/Screens/Edit/Timing/TimingAdjustButton.cs @@ -42,12 +42,8 @@ namespace osu.Game.Screens.Edit.Timing public LocalisableString Text { - get => text?.Text ?? default; - set - { - if (text != null) - text.Text = value; - } + get => text.Text; + set => text.Text = value; } [Resolved] From 187acb0718bda64c693f19ea9da3bb041d556161 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 1 Jun 2022 17:57:53 +0900 Subject: [PATCH 313/395] Simplify linq tracking logic --- osu.Game/Screens/Edit/Timing/TimingScreen.cs | 30 ++------------------ 1 file changed, 3 insertions(+), 27 deletions(-) diff --git a/osu.Game/Screens/Edit/Timing/TimingScreen.cs b/osu.Game/Screens/Edit/Timing/TimingScreen.cs index 0cf4fca58a..ebb958f884 100644 --- a/osu.Game/Screens/Edit/Timing/TimingScreen.cs +++ b/osu.Game/Screens/Edit/Timing/TimingScreen.cs @@ -156,35 +156,11 @@ namespace osu.Game.Screens.Edit.Timing { // We don't have an efficient way of looking up groups currently, only individual point types. // To improve the efficiency of this in the future, we should reconsider the overall structure of ControlPointInfo. - IEnumerable groups = Beatmap.ControlPointInfo.Groups; - - bool currentTimeBeforeSelectedGroup = clock.CurrentTimeAccurate < selectedGroup.Value.Time; - - // Decide whether we are searching backwards or forwards. - if (currentTimeBeforeSelectedGroup) - groups = groups.Reverse(); // Find the next group which has the same type as the selected one. - groups = groups.SkipWhile(g => g != selectedGroup.Value) - .Skip(1) - .Where(g => g.ControlPoints.Any(cp => cp.GetType() == selectedPointType)); - - ControlPointGroup newGroup = groups.FirstOrDefault(); - - if (newGroup != null) - { - if (currentTimeBeforeSelectedGroup) - { - // When seeking backwards, the first match from the LINQ query is always what we want. - selectedGroup.Value = newGroup; - } - else - { - // When seeking forwards, we also need to check that the next match is before the current time. - if (newGroup.Time <= clock.CurrentTimeAccurate) - selectedGroup.Value = newGroup; - } - } + selectedGroup.Value = Beatmap.ControlPointInfo.Groups + .Where(g => g.ControlPoints.Any(cp => cp.GetType() == selectedPointType)) + .LastOrDefault(g => g.Time <= clock.CurrentTimeAccurate); } } From 0472881078490377697b66a7b4201dc341d87d84 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 1 Jun 2022 18:03:03 +0900 Subject: [PATCH 314/395] Fix null check only covering one of two calls --- osu.Game/Screens/Edit/Timing/MetronomeDisplay.cs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Edit/Timing/MetronomeDisplay.cs b/osu.Game/Screens/Edit/Timing/MetronomeDisplay.cs index 1f065d1551..3a067a281e 100644 --- a/osu.Game/Screens/Edit/Timing/MetronomeDisplay.cs +++ b/osu.Game/Screens/Edit/Timing/MetronomeDisplay.cs @@ -278,11 +278,15 @@ namespace osu.Game.Screens.Edit.Timing using (BeginDelayedSequence(beatLength / 2)) { stick.FlashColour(overlayColourProvider.Content1, beatLength, Easing.OutQuint); - Schedule(() => + + if (clunk != null) { - clunk.Frequency.Value = RNG.NextDouble(0.98f, 1.02f); - clunk?.Play(); - }); + Schedule(() => + { + clunk.Frequency.Value = RNG.NextDouble(0.98f, 1.02f); + clunk.Play(); + }); + } } } } From 93b8c90ecca5cae08e7fce1b331f3c0f0bfe5739 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 1 Jun 2022 18:04:13 +0900 Subject: [PATCH 315/395] Apply frequency adjust to channel rather than sample for safety --- osu.Game/Screens/Edit/Timing/MetronomeDisplay.cs | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/osu.Game/Screens/Edit/Timing/MetronomeDisplay.cs b/osu.Game/Screens/Edit/Timing/MetronomeDisplay.cs index 3a067a281e..4143c5ea55 100644 --- a/osu.Game/Screens/Edit/Timing/MetronomeDisplay.cs +++ b/osu.Game/Screens/Edit/Timing/MetronomeDisplay.cs @@ -279,14 +279,16 @@ namespace osu.Game.Screens.Edit.Timing { stick.FlashColour(overlayColourProvider.Content1, beatLength, Easing.OutQuint); - if (clunk != null) + Schedule(() => { - Schedule(() => + var channel = clunk?.GetChannel(); + + if (channel != null) { - clunk.Frequency.Value = RNG.NextDouble(0.98f, 1.02f); - clunk.Play(); - }); - } + channel.Frequency.Value = RNG.NextDouble(0.98f, 1.02f); + channel.Play(); + } + }); } } } From a4ec32b499dabcc1280033b1fe37688fbec1f5c0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 1 Jun 2022 18:02:17 +0900 Subject: [PATCH 316/395] Add button sound effect --- .../Screens/Edit/Timing/TimingAdjustButton.cs | 24 +++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Edit/Timing/TimingAdjustButton.cs b/osu.Game/Screens/Edit/Timing/TimingAdjustButton.cs index 46bb8356f7..9fc7e56a3d 100644 --- a/osu.Game/Screens/Edit/Timing/TimingAdjustButton.cs +++ b/osu.Game/Screens/Edit/Timing/TimingAdjustButton.cs @@ -4,6 +4,8 @@ using System; using System.Linq; using osu.Framework.Allocation; +using osu.Framework.Audio; +using osu.Framework.Audio.Sample; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; @@ -26,6 +28,8 @@ namespace osu.Game.Screens.Edit.Timing private readonly double adjustAmount; private ScheduledDelegate adjustDelegate; + private const int max_multiplier = 10; + private const int adjust_levels = 4; private const double initial_delay = 300; @@ -40,6 +44,8 @@ namespace osu.Game.Screens.Edit.Timing private readonly OsuSpriteText text; + private Sample sample; + public LocalisableString Text { get => text.Text; @@ -79,8 +85,10 @@ namespace osu.Game.Screens.Edit.Timing } [BackgroundDependencyLoader] - private void load() + private void load(AudioManager audio) { + sample = audio.Samples.Get(@"UI/notch-tick"); + background.Colour = colourProvider.Background3; for (int i = 1; i <= adjust_levels; i++) @@ -120,6 +128,17 @@ namespace osu.Game.Screens.Edit.Timing adjustDelay = Math.Max(minimum_delay, adjustDelay * 0.9f); hoveredBox.Flash(); + + var channel = sample?.GetChannel(); + + if (channel != null) + { + double repeatModifier = 0.05f * (Math.Abs(adjustDelay - initial_delay) / minimum_delay); + double multiplierModifier = (hoveredBox.Multiplier / max_multiplier) * 0.2f; + + channel.Frequency.Value = 1 + multiplierModifier + repeatModifier; + channel.Play(); + } } else { @@ -195,7 +214,8 @@ namespace osu.Game.Screens.Edit.Timing case 3: return 5; - case 4: return 10; + case 4: + return max_multiplier; } } From 534e8f8fac69c2df110922f17b1b6f79ecfde98f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 1 Jun 2022 20:20:16 +0900 Subject: [PATCH 317/395] Only switch to found group if non-null --- osu.Game/Screens/Edit/Timing/TimingScreen.cs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Edit/Timing/TimingScreen.cs b/osu.Game/Screens/Edit/Timing/TimingScreen.cs index ebb958f884..d7ca306037 100644 --- a/osu.Game/Screens/Edit/Timing/TimingScreen.cs +++ b/osu.Game/Screens/Edit/Timing/TimingScreen.cs @@ -158,9 +158,12 @@ namespace osu.Game.Screens.Edit.Timing // To improve the efficiency of this in the future, we should reconsider the overall structure of ControlPointInfo. // Find the next group which has the same type as the selected one. - selectedGroup.Value = Beatmap.ControlPointInfo.Groups - .Where(g => g.ControlPoints.Any(cp => cp.GetType() == selectedPointType)) - .LastOrDefault(g => g.Time <= clock.CurrentTimeAccurate); + var found = Beatmap.ControlPointInfo.Groups + .Where(g => g.ControlPoints.Any(cp => cp.GetType() == selectedPointType)) + .LastOrDefault(g => g.Time <= clock.CurrentTimeAccurate); + + if (found != null) + selectedGroup.Value = found; } } From cb37dd74c19f9bb21c754128f8850123f1a83359 Mon Sep 17 00:00:00 2001 From: "Hugo \"ThePooN\" Denizart" Date: Wed, 1 Jun 2022 17:14:07 +0200 Subject: [PATCH 318/395] =?UTF-8?q?=F0=9F=94=A7=20Add=20prefix=20to=20Sent?= =?UTF-8?q?ry=20release=20name?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/sentry-release.yml | 2 +- osu.Game/Utils/SentryLogger.cs | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/.github/workflows/sentry-release.yml b/.github/workflows/sentry-release.yml index 8ca9f38234..794943c4b1 100644 --- a/.github/workflows/sentry-release.yml +++ b/.github/workflows/sentry-release.yml @@ -23,4 +23,4 @@ jobs: SENTRY_URL: https://sentry.ppy.sh/ with: environment: production - version: ${{ github.ref }} + version: osu/${{ github.ref_name }} diff --git a/osu.Game/Utils/SentryLogger.cs b/osu.Game/Utils/SentryLogger.cs index 137bf7e0aa..a4f8ea0ef6 100644 --- a/osu.Game/Utils/SentryLogger.cs +++ b/osu.Game/Utils/SentryLogger.cs @@ -48,9 +48,8 @@ namespace osu.Game.Utils options.AutoSessionTracking = true; options.IsEnvironmentUser = false; - // The reported release needs to match release tags on github in order for sentry - // to automatically associate and track against releases. - options.Release = game.Version.Replace($@"-{OsuGameBase.BUILD_SUFFIX}", string.Empty); + // The reported release needs to match version as reported to Sentry in .github/workflows/sentry-release.yml + options.Release = $"osu/{game.Version.Replace($@"-{OsuGameBase.BUILD_SUFFIX}", string.Empty)}"; }); Logger.NewEntry += processLogEntry; From 384cdcbc40e0a193a7e8d9102e44b93579eef869 Mon Sep 17 00:00:00 2001 From: "Hugo \"ThePooN\" Denizart" Date: Wed, 1 Jun 2022 18:12:29 +0200 Subject: [PATCH 319/395] =?UTF-8?q?=F0=9F=9A=91=20Fix=20use=20of=20illegal?= =?UTF-8?q?=20character=20in=20Sentry's=20release=20name=20scheme?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/sentry-release.yml | 2 +- osu.Game/Utils/SentryLogger.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/sentry-release.yml b/.github/workflows/sentry-release.yml index 794943c4b1..442b97c473 100644 --- a/.github/workflows/sentry-release.yml +++ b/.github/workflows/sentry-release.yml @@ -23,4 +23,4 @@ jobs: SENTRY_URL: https://sentry.ppy.sh/ with: environment: production - version: osu/${{ github.ref_name }} + version: osu@${{ github.ref_name }} diff --git a/osu.Game/Utils/SentryLogger.cs b/osu.Game/Utils/SentryLogger.cs index a4f8ea0ef6..ecc7fc4774 100644 --- a/osu.Game/Utils/SentryLogger.cs +++ b/osu.Game/Utils/SentryLogger.cs @@ -49,7 +49,7 @@ namespace osu.Game.Utils options.AutoSessionTracking = true; options.IsEnvironmentUser = false; // The reported release needs to match version as reported to Sentry in .github/workflows/sentry-release.yml - options.Release = $"osu/{game.Version.Replace($@"-{OsuGameBase.BUILD_SUFFIX}", string.Empty)}"; + options.Release = $"osu@{game.Version.Replace($@"-{OsuGameBase.BUILD_SUFFIX}", string.Empty)}"; }); Logger.NewEntry += processLogEntry; From 6b709de2c26d259676e158e35a228e82eab13071 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 1 Jun 2022 20:18:02 +0200 Subject: [PATCH 320/395] Remove unused using directive --- osu.Game/Screens/Edit/Timing/TimingScreen.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Timing/TimingScreen.cs b/osu.Game/Screens/Edit/Timing/TimingScreen.cs index d7ca306037..d104cc4b5c 100644 --- a/osu.Game/Screens/Edit/Timing/TimingScreen.cs +++ b/osu.Game/Screens/Edit/Timing/TimingScreen.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.Collections.Generic; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; From 801e11d8418b49018d9174629d413e14bd7b36ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 1 Jun 2022 20:25:56 +0200 Subject: [PATCH 321/395] Apply performance regression fix to effect section too --- osu.Game/Screens/Edit/Timing/EffectSection.cs | 25 +++++++++++++++---- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/Edit/Timing/EffectSection.cs b/osu.Game/Screens/Edit/Timing/EffectSection.cs index c8944d0357..c9f73411f1 100644 --- a/osu.Game/Screens/Edit/Timing/EffectSection.cs +++ b/osu.Game/Screens/Edit/Timing/EffectSection.cs @@ -31,18 +31,33 @@ namespace osu.Game.Screens.Edit.Timing }); } + protected override void LoadComplete() + { + base.LoadComplete(); + + kiai.Current.BindValueChanged(_ => saveChanges()); + omitBarLine.Current.BindValueChanged(_ => saveChanges()); + scrollSpeedSlider.Current.BindValueChanged(_ => saveChanges()); + + void saveChanges() + { + if (!isRebinding) ChangeHandler?.SaveState(); + } + } + + private bool isRebinding; + protected override void OnControlPointChanged(ValueChangedEvent point) { if (point.NewValue != null) { + isRebinding = true; + kiai.Current = point.NewValue.KiaiModeBindable; - kiai.Current.BindValueChanged(_ => ChangeHandler?.SaveState()); - omitBarLine.Current = point.NewValue.OmitFirstBarLineBindable; - omitBarLine.Current.BindValueChanged(_ => ChangeHandler?.SaveState()); - scrollSpeedSlider.Current = point.NewValue.ScrollSpeedBindable; - scrollSpeedSlider.Current.BindValueChanged(_ => ChangeHandler?.SaveState()); + + isRebinding = false; } } From e551e486977bef815098a9047a4b89529a7267df Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 2 Jun 2022 11:01:46 +0900 Subject: [PATCH 322/395] Use ScoreInfo overload in more places --- osu.Game/Rulesets/Scoring/ScoreProcessor.cs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index 26a32ab1a0..853f43ddb6 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -273,8 +273,7 @@ namespace osu.Game.Rulesets.Scoring if (!ruleset.RulesetInfo.Equals(scoreInfo.Ruleset)) throw new ArgumentException($"Unexpected score ruleset. Expected \"{ruleset.RulesetInfo.ShortName}\" but was \"{scoreInfo.Ruleset.ShortName}\"."); - extractScoringValues(scoreInfo.Statistics, out var current, out var maximum); - current.MaxCombo = scoreInfo.MaxCombo; + ExtractScoringValues(scoreInfo, out var current, out var maximum); return ComputeScore(mode, current, maximum); } @@ -297,8 +296,7 @@ namespace osu.Game.Rulesets.Scoring if (!beatmapApplied) throw new InvalidOperationException($"Cannot compute partial score without calling {nameof(ApplyBeatmap)}."); - extractScoringValues(scoreInfo.Statistics, out var current, out _); - current.MaxCombo = scoreInfo.MaxCombo; + ExtractScoringValues(scoreInfo, out var current, out _); return ComputeScore(mode, current, MaximumScoringValues); } @@ -323,7 +321,7 @@ namespace osu.Game.Rulesets.Scoring double accuracyRatio = scoreInfo.Accuracy; double comboRatio = maxAchievableCombo > 0 ? (double)scoreInfo.MaxCombo / maxAchievableCombo : 1; - extractScoringValues(scoreInfo.Statistics, out var current, out var maximum); + ExtractScoringValues(scoreInfo, out var current, out var maximum); // For legacy osu!mania scores, a full-GREAT score has 100% accuracy. If combined with a full-combo, the score becomes indistinguishable from a full-PERFECT score. // To get around this, the accuracy ratio is always recalculated based on the hit statistics rather than trusting the score. From d12f6ea2213da50789f48c9cf3f0e65a55e1ae93 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 1 Jun 2022 18:53:35 +0900 Subject: [PATCH 323/395] Add basics of tap button --- .../Visual/Editing/TestSceneTapButton.cs | 48 +++++++ osu.Game/Screens/Edit/Timing/TapButton.cs | 122 ++++++++++++++++++ .../Screens/Edit/Timing/TapTimingControl.cs | 9 ++ 3 files changed, 179 insertions(+) create mode 100644 osu.Game.Tests/Visual/Editing/TestSceneTapButton.cs create mode 100644 osu.Game/Screens/Edit/Timing/TapButton.cs diff --git a/osu.Game.Tests/Visual/Editing/TestSceneTapButton.cs b/osu.Game.Tests/Visual/Editing/TestSceneTapButton.cs new file mode 100644 index 0000000000..d8141619ab --- /dev/null +++ b/osu.Game.Tests/Visual/Editing/TestSceneTapButton.cs @@ -0,0 +1,48 @@ +// 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.Overlays; +using osu.Game.Screens.Edit.Timing; +using osuTK; +using osuTK.Input; + +namespace osu.Game.Tests.Visual.Editing +{ + public class TestSceneTapButton : OsuManualInputManagerTestScene + { + private TapButton tapButton; + + [Cached] + private readonly OverlayColourProvider overlayColour = new OverlayColourProvider(OverlayColourScheme.Aquamarine); + + [Test] + public void TestBasic() + { + AddStep("create button", () => + { + Child = tapButton = new TapButton + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Scale = new Vector2(4), + }; + }); + + bool pressed = false; + + AddRepeatStep("Press button", () => + { + InputManager.MoveMouseTo(tapButton); + if (!pressed) + InputManager.PressButton(MouseButton.Left); + else + InputManager.ReleaseButton(MouseButton.Left); + + pressed = !pressed; + }, 100); + } + } +} diff --git a/osu.Game/Screens/Edit/Timing/TapButton.cs b/osu.Game/Screens/Edit/Timing/TapButton.cs new file mode 100644 index 0000000000..59ec966fe0 --- /dev/null +++ b/osu.Game/Screens/Edit/Timing/TapButton.cs @@ -0,0 +1,122 @@ +// 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.Shapes; +using osu.Framework.Input.Events; +using osu.Game.Overlays; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Screens.Edit.Timing +{ + internal class TapButton : CircularContainer + { + public const float SIZE = 100; + + [Resolved] + private OverlayColourProvider colourProvider { get; set; } + + private Circle hoverLayer; + private CircularContainer innerCircle; + private Circle outerCircle; + + private Container scaleContainer; + + [BackgroundDependencyLoader] + private void load() + { + Size = new Vector2(SIZE); + + const float ring_width = 20; + const float light_padding = 3; + + InternalChild = scaleContainer = new Container + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + outerCircle = new Circle + { + RelativeSizeAxes = Axes.Both, + Colour = colourProvider.Background3 + }, + new CircularContainer + { + RelativeSizeAxes = Axes.Both, + Name = "outer masking", + Masking = true, + BorderThickness = light_padding, + BorderColour = colourProvider.Background3, + Children = new Drawable[] + { + new Box + { + Colour = Color4.Black, + RelativeSizeAxes = Axes.Both, + Alpha = 0, + AlwaysPresent = true, + }, + } + }, + innerCircle = new CircularContainer + { + Size = new Vector2(SIZE - ring_width + light_padding), + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Masking = true, + BorderThickness = light_padding, + BorderColour = colourProvider.Background3, + Children = new Drawable[] + { + new Box + { + Colour = colourProvider.Background2, + RelativeSizeAxes = Axes.Both, + AlwaysPresent = true, + }, + } + }, + hoverLayer = new Circle + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.White.Opacity(0.01f), + Blending = BlendingParameters.Additive, + Alpha = 0, + }, + } + }; + } + + protected override bool OnHover(HoverEvent e) + { + hoverLayer.FadeIn(500, Easing.OutQuint); + return base.OnHover(e); + } + + protected override void OnHoverLost(HoverLostEvent e) + { + hoverLayer.FadeOut(500, Easing.OutQuint); + base.OnHoverLost(e); + } + + protected override bool OnMouseDown(MouseDownEvent e) + { + scaleContainer.ScaleTo(0.98f, 200, Easing.OutQuint); + innerCircle.ScaleTo(0.95f, 200, Easing.OutQuint); + return base.OnMouseDown(e); + } + + protected override void OnMouseUp(MouseUpEvent e) + { + scaleContainer.ScaleTo(1, 1200, Easing.OutQuint); + innerCircle.ScaleTo(1, 1200, Easing.OutQuint); + base.OnMouseUp(e); + } + } +} diff --git a/osu.Game/Screens/Edit/Timing/TapTimingControl.cs b/osu.Game/Screens/Edit/Timing/TapTimingControl.cs index 990f8d2ce0..20d6c656ff 100644 --- a/osu.Game/Screens/Edit/Timing/TapTimingControl.cs +++ b/osu.Game/Screens/Edit/Timing/TapTimingControl.cs @@ -50,6 +50,7 @@ namespace osu.Game.Screens.Edit.Timing new Dimension(GridSizeMode.Absolute, 200), new Dimension(GridSizeMode.Absolute, 60), new Dimension(GridSizeMode.Absolute, 60), + new Dimension(GridSizeMode.Absolute, 120), }, Content = new[] { @@ -141,6 +142,14 @@ namespace osu.Game.Screens.Edit.Timing } } }, + }, + new Drawable[] + { + new TapButton + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + } } } }, From c3ba7b2c3beef436f9a03b4210ff3181e9d79a4d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 1 Jun 2022 19:45:11 +0900 Subject: [PATCH 324/395] Add lights --- osu.Game/Screens/Edit/Timing/TapButton.cs | 150 +++++++++++++++++++--- 1 file changed, 132 insertions(+), 18 deletions(-) diff --git a/osu.Game/Screens/Edit/Timing/TapButton.cs b/osu.Game/Screens/Edit/Timing/TapButton.cs index 59ec966fe0..3eb8e23bad 100644 --- a/osu.Game/Screens/Edit/Timing/TapButton.cs +++ b/osu.Game/Screens/Edit/Timing/TapButton.cs @@ -6,7 +6,10 @@ using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.UserInterface; using osu.Framework.Input.Events; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; using osu.Game.Overlays; using osuTK; using osuTK.Graphics; @@ -21,17 +24,23 @@ namespace osu.Game.Screens.Edit.Timing private OverlayColourProvider colourProvider { get; set; } private Circle hoverLayer; + private CircularContainer innerCircle; - private Circle outerCircle; + private Box innerCircleHighlight; + + private int currentLight; private Container scaleContainer; + private Container lights; + + private const int light_count = 6; [BackgroundDependencyLoader] private void load() { Size = new Vector2(SIZE); - const float ring_width = 20; + const float ring_width = 18; const float light_padding = 3; InternalChild = scaleContainer = new Container @@ -41,11 +50,15 @@ namespace osu.Game.Screens.Edit.Timing RelativeSizeAxes = Axes.Both, Children = new Drawable[] { - outerCircle = new Circle + new Circle { RelativeSizeAxes = Axes.Both, Colour = colourProvider.Background3 }, + lights = new Container + { + RelativeSizeAxes = Axes.Both, + }, new CircularContainer { RelativeSizeAxes = Axes.Both, @@ -64,35 +77,65 @@ namespace osu.Game.Screens.Edit.Timing }, } }, + new Circle + { + Size = new Vector2(SIZE - ring_width * 2 + light_padding * 2), + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Colour = colourProvider.Background3, + }, innerCircle = new CircularContainer { - Size = new Vector2(SIZE - ring_width + light_padding), + Size = new Vector2(SIZE - ring_width * 2), Anchor = Anchor.Centre, Origin = Anchor.Centre, Masking = true, - BorderThickness = light_padding, - BorderColour = colourProvider.Background3, Children = new Drawable[] { new Box { Colour = colourProvider.Background2, RelativeSizeAxes = Axes.Both, - AlwaysPresent = true, + }, + innerCircleHighlight = new Box + { + Colour = colourProvider.Colour3, + Blending = BlendingParameters.Additive, + RelativeSizeAxes = Axes.Both, + Alpha = 0, + }, + new OsuSpriteText + { + Font = OsuFont.Torus.With(size: 20), + Colour = colourProvider.Background1, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Text = "Tap!" + }, + hoverLayer = new Circle + { + RelativeSizeAxes = Axes.Both, + Colour = colourProvider.Background1.Opacity(0.3f), + Blending = BlendingParameters.Additive, + Alpha = 0, }, } }, - hoverLayer = new Circle - { - RelativeSizeAxes = Axes.Both, - Colour = Color4.White.Opacity(0.01f), - Blending = BlendingParameters.Additive, - Alpha = 0, - }, } }; + + for (int i = 0; i < light_count; i++) + { + lights.Add(new Light + { + Rotation = i * (360f / light_count) + }); + } } + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => + hoverLayer.ReceivePositionalInputAt(screenSpacePos); + protected override bool OnHover(HoverEvent e) { hoverLayer.FadeIn(500, Easing.OutQuint); @@ -107,16 +150,87 @@ namespace osu.Game.Screens.Edit.Timing protected override bool OnMouseDown(MouseDownEvent e) { - scaleContainer.ScaleTo(0.98f, 200, Easing.OutQuint); - innerCircle.ScaleTo(0.95f, 200, Easing.OutQuint); + const double in_duration = 100; + + scaleContainer.ScaleTo(0.99f, in_duration, Easing.OutQuint); + innerCircle.ScaleTo(0.96f, in_duration, Easing.OutQuint); + + innerCircleHighlight + .FadeIn(50, Easing.OutQuint) + .FlashColour(Color4.White, 1000, Easing.OutQuint); + + lights[currentLight % light_count].Hide(); + lights[(currentLight + light_count / 2) % light_count].Hide(); + + currentLight++; + + lights[currentLight % light_count].Show(); + lights[(currentLight + light_count / 2) % light_count].Show(); + return base.OnMouseDown(e); } protected override void OnMouseUp(MouseUpEvent e) { - scaleContainer.ScaleTo(1, 1200, Easing.OutQuint); - innerCircle.ScaleTo(1, 1200, Easing.OutQuint); + const double out_duration = 800; + + scaleContainer.ScaleTo(1, out_duration, Easing.OutQuint); + innerCircle.ScaleTo(1, out_duration, Easing.OutQuint); + innerCircleHighlight.FadeOut(out_duration, Easing.OutQuint); base.OnMouseUp(e); } + + private class Light : CompositeDrawable + { + private CircularProgress fill; + + [Resolved] + private OverlayColourProvider colourProvider { get; set; } + + [BackgroundDependencyLoader] + private void load() + { + RelativeSizeAxes = Axes.Both; + Anchor = Anchor.Centre; + Origin = Anchor.Centre; + + InternalChildren = new Drawable[] + { + new CircularProgress + { + RelativeSizeAxes = Axes.Both, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(0.99f), + Current = { Value = 1f / light_count - 0.01f }, + Colour = colourProvider.Background2, + }, + fill = new CircularProgress + { + RelativeSizeAxes = Axes.Both, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Alpha = 0, + Size = new Vector2(0.99f), + Current = { Value = 1f / light_count - 0.01f }, + Colour = colourProvider.Colour1, + Blending = BlendingParameters.Additive + }, + }; + } + + public override void Show() + { + fill + .FadeIn(50, Easing.OutQuint) + .FlashColour(Color4.White, 1000, Easing.OutQuint); + } + + public override void Hide() + { + fill + .FadeOut(300, Easing.OutQuint); + } + } } } From 3c7a04256fda8041f4e8e5b51a4a2ba8050cb345 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 1 Jun 2022 20:04:44 +0900 Subject: [PATCH 325/395] Add glow --- osu.Game/Screens/Edit/Timing/TapButton.cs | 62 +++++++++++++++++------ 1 file changed, 47 insertions(+), 15 deletions(-) diff --git a/osu.Game/Screens/Edit/Timing/TapButton.cs b/osu.Game/Screens/Edit/Timing/TapButton.cs index 3eb8e23bad..f7a5a0ca7b 100644 --- a/osu.Game/Screens/Edit/Timing/TapButton.cs +++ b/osu.Game/Screens/Edit/Timing/TapButton.cs @@ -5,6 +5,7 @@ using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.UserInterface; using osu.Framework.Input.Events; @@ -32,6 +33,8 @@ namespace osu.Game.Screens.Edit.Timing private Container scaleContainer; private Container lights; + private Container lightsGlow; + private OsuSpriteText text; private const int light_count = 6; @@ -79,11 +82,16 @@ namespace osu.Game.Screens.Edit.Timing }, new Circle { + Name = "inner masking", Size = new Vector2(SIZE - ring_width * 2 + light_padding * 2), Anchor = Anchor.Centre, Origin = Anchor.Centre, Colour = colourProvider.Background3, }, + lightsGlow = new Container + { + RelativeSizeAxes = Axes.Both, + }, innerCircle = new CircularContainer { Size = new Vector2(SIZE - ring_width * 2), @@ -104,7 +112,7 @@ namespace osu.Game.Screens.Edit.Timing RelativeSizeAxes = Axes.Both, Alpha = 0, }, - new OsuSpriteText + text = new OsuSpriteText { Font = OsuFont.Torus.With(size: 20), Colour = colourProvider.Background1, @@ -126,10 +134,13 @@ namespace osu.Game.Screens.Edit.Timing for (int i = 0; i < light_count; i++) { - lights.Add(new Light + var light = new Light { Rotation = i * (360f / light_count) - }); + }; + + lights.Add(light); + lightsGlow.Add(light.Glow.CreateProxy()); } } @@ -152,6 +163,8 @@ namespace osu.Game.Screens.Edit.Timing { const double in_duration = 100; + text.FadeColour(colourProvider.Background4, in_duration, Easing.OutQuint); + scaleContainer.ScaleTo(0.99f, in_duration, Easing.OutQuint); innerCircle.ScaleTo(0.96f, in_duration, Easing.OutQuint); @@ -174,6 +187,8 @@ namespace osu.Game.Screens.Edit.Timing { const double out_duration = 800; + text.FadeColour(colourProvider.Background1, out_duration, Easing.OutQuint); + scaleContainer.ScaleTo(1, out_duration, Easing.OutQuint); innerCircle.ScaleTo(1, out_duration, Easing.OutQuint); innerCircleHighlight.FadeOut(out_duration, Easing.OutQuint); @@ -182,7 +197,9 @@ namespace osu.Game.Screens.Edit.Timing private class Light : CompositeDrawable { - private CircularProgress fill; + public Drawable Glow { get; private set; } + + private Container fillContent; [Resolved] private OverlayColourProvider colourProvider { get; set; } @@ -194,41 +211,56 @@ namespace osu.Game.Screens.Edit.Timing Anchor = Anchor.Centre; Origin = Anchor.Centre; + Size = new Vector2(0.98f); // Avoid bleed into masking edge. + InternalChildren = new Drawable[] { new CircularProgress { RelativeSizeAxes = Axes.Both, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Size = new Vector2(0.99f), Current = { Value = 1f / light_count - 0.01f }, Colour = colourProvider.Background2, }, - fill = new CircularProgress + fillContent = new Container { RelativeSizeAxes = Axes.Both, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, Alpha = 0, - Size = new Vector2(0.99f), - Current = { Value = 1f / light_count - 0.01f }, Colour = colourProvider.Colour1, - Blending = BlendingParameters.Additive + Children = new[] + { + new CircularProgress + { + RelativeSizeAxes = Axes.Both, + Current = { Value = 1f / light_count - 0.01f }, + Blending = BlendingParameters.Additive + }, + Glow = new CircularProgress + { + RelativeSizeAxes = Axes.Both, + Current = { Value = 1f / light_count - 0.01f }, + Blending = BlendingParameters.Additive + }.WithEffect(new GlowEffect + { + Colour = colourProvider.Colour1.Opacity(0.4f), + BlurSigma = new Vector2(9f), + Strength = 10, + PadExtent = true + }), + } }, }; } public override void Show() { - fill + fillContent .FadeIn(50, Easing.OutQuint) .FlashColour(Color4.White, 1000, Easing.OutQuint); } public override void Hide() { - fill + fillContent .FadeOut(300, Easing.OutQuint); } } From a2d177d7d258cba760086a59a413584367ff7efd Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 1 Jun 2022 20:37:02 +0900 Subject: [PATCH 326/395] Add BPM display and tracking --- osu.Game/Screens/Edit/Timing/TapButton.cs | 109 +++++++++++++++++++--- 1 file changed, 98 insertions(+), 11 deletions(-) diff --git a/osu.Game/Screens/Edit/Timing/TapButton.cs b/osu.Game/Screens/Edit/Timing/TapButton.cs index f7a5a0ca7b..9c6dde4ffe 100644 --- a/osu.Game/Screens/Edit/Timing/TapButton.cs +++ b/osu.Game/Screens/Edit/Timing/TapButton.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 System.Collections.Generic; +using System.Linq; using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; +using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.UserInterface; using osu.Framework.Input.Events; +using osu.Framework.Threading; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Overlays; @@ -34,7 +39,12 @@ namespace osu.Game.Screens.Edit.Timing private Container scaleContainer; private Container lights; private Container lightsGlow; - private OsuSpriteText text; + private OsuSpriteText bpmText; + private Container textContainer; + + private bool grabbedMouseDown; + + private ScheduledDelegate resetDelegate; private const int light_count = 6; @@ -65,7 +75,7 @@ namespace osu.Game.Screens.Edit.Timing new CircularContainer { RelativeSizeAxes = Axes.Both, - Name = "outer masking", + Name = @"outer masking", Masking = true, BorderThickness = light_padding, BorderColour = colourProvider.Background3, @@ -82,7 +92,7 @@ namespace osu.Game.Screens.Edit.Timing }, new Circle { - Name = "inner masking", + Name = @"inner masking", Size = new Vector2(SIZE - ring_width * 2 + light_padding * 2), Anchor = Anchor.Centre, Origin = Anchor.Centre, @@ -112,13 +122,27 @@ namespace osu.Game.Screens.Edit.Timing RelativeSizeAxes = Axes.Both, Alpha = 0, }, - text = new OsuSpriteText + textContainer = new Container { - Font = OsuFont.Torus.With(size: 20), + RelativeSizeAxes = Axes.Both, Colour = colourProvider.Background1, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Text = "Tap!" + Children = new Drawable[] + { + new OsuSpriteText + { + Font = OsuFont.Torus.With(size: 20), + Anchor = Anchor.Centre, + Origin = Anchor.BottomCentre, + Y = 3, + Text = "Tap" + }, + bpmText = new OsuSpriteText + { + Font = OsuFont.Torus.With(size: 14), + Anchor = Anchor.Centre, + Origin = Anchor.TopCentre, + }, + } }, hoverLayer = new Circle { @@ -142,20 +166,38 @@ namespace osu.Game.Screens.Edit.Timing lights.Add(light); lightsGlow.Add(light.Glow.CreateProxy()); } + + reset(); } public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => hoverLayer.ReceivePositionalInputAt(screenSpacePos); + private ColourInfo textColour + { + get + { + if (grabbedMouseDown) + return colourProvider.Background4; + + if (IsHovered) + return colourProvider.Content2; + + return colourProvider.Background1; + } + } + protected override bool OnHover(HoverEvent e) { hoverLayer.FadeIn(500, Easing.OutQuint); + textContainer.FadeColour(textColour, 500, Easing.OutQuint); return base.OnHover(e); } protected override void OnHoverLost(HoverLostEvent e) { hoverLayer.FadeOut(500, Easing.OutQuint); + textContainer.FadeColour(textColour, 500, Easing.OutQuint); base.OnHoverLost(e); } @@ -163,7 +205,14 @@ namespace osu.Game.Screens.Edit.Timing { const double in_duration = 100; - text.FadeColour(colourProvider.Background4, in_duration, Easing.OutQuint); + grabbedMouseDown = true; + + handleTap(); + + resetDelegate?.Cancel(); + resetDelegate = Scheduler.AddDelayed(reset, 1000); + + textContainer.FadeColour(textColour, in_duration, Easing.OutQuint); scaleContainer.ScaleTo(0.99f, in_duration, Easing.OutQuint); innerCircle.ScaleTo(0.96f, in_duration, Easing.OutQuint); @@ -180,14 +229,16 @@ namespace osu.Game.Screens.Edit.Timing lights[currentLight % light_count].Show(); lights[(currentLight + light_count / 2) % light_count].Show(); - return base.OnMouseDown(e); + return true; } protected override void OnMouseUp(MouseUpEvent e) { const double out_duration = 800; - text.FadeColour(colourProvider.Background1, out_duration, Easing.OutQuint); + grabbedMouseDown = false; + + textContainer.FadeColour(textColour, out_duration, Easing.OutQuint); scaleContainer.ScaleTo(1, out_duration, Easing.OutQuint); innerCircle.ScaleTo(1, out_duration, Easing.OutQuint); @@ -195,6 +246,42 @@ namespace osu.Game.Screens.Edit.Timing base.OnMouseUp(e); } + private readonly List tapTimings = new List(); + + private void reset() + { + bpmText.FadeOut(500, Easing.OutQuint); + + using (BeginDelayedSequence(tapTimings.Count > 0 ? 500 : 0)) + { + Schedule(() => bpmText.Text = "the beat!"); + bpmText.FadeIn(800, Easing.OutQuint); + } + + foreach (var light in lights) + light.Hide(); + + tapTimings.Clear(); + } + + private void handleTap() + { + tapTimings.Add(Clock.CurrentTime); + + if (tapTimings.Count > 8) + tapTimings.RemoveAt(0); + + if (tapTimings.Count < 5) + { + bpmText.Text = new string('.', tapTimings.Count); + return; + } + + double bpm = Math.Round(60000 / ((tapTimings.Last() - tapTimings.First()) / (tapTimings.Count - 1))); + + bpmText.Text = $"{bpm} BPM"; + } + private class Light : CompositeDrawable { public Drawable Glow { get; private set; } From d47a3bb8e4948aefff1d8d07e965eac729455cf6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 1 Jun 2022 20:44:05 +0900 Subject: [PATCH 327/395] Use NRT and transfer BPM --- osu.Game/Screens/Edit/Timing/TapButton.cs | 41 ++++++++++++++++------- 1 file changed, 28 insertions(+), 13 deletions(-) diff --git a/osu.Game/Screens/Edit/Timing/TapButton.cs b/osu.Game/Screens/Edit/Timing/TapButton.cs index 9c6dde4ffe..d85edb6cce 100644 --- a/osu.Game/Screens/Edit/Timing/TapButton.cs +++ b/osu.Game/Screens/Edit/Timing/TapButton.cs @@ -1,10 +1,13 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +#nullable enable + using System; using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; @@ -14,6 +17,7 @@ using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.UserInterface; using osu.Framework.Input.Events; using osu.Framework.Threading; +using osu.Game.Beatmaps.ControlPoints; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Overlays; @@ -27,24 +31,27 @@ namespace osu.Game.Screens.Edit.Timing public const float SIZE = 100; [Resolved] - private OverlayColourProvider colourProvider { get; set; } + private OverlayColourProvider colourProvider { get; set; } = null!; - private Circle hoverLayer; + [Resolved(canBeNull: true)] + private Bindable? selectedGroup { get; set; } - private CircularContainer innerCircle; - private Box innerCircleHighlight; + private Circle hoverLayer = null!; + + private CircularContainer innerCircle = null!; + private Box innerCircleHighlight = null!; private int currentLight; - private Container scaleContainer; - private Container lights; - private Container lightsGlow; - private OsuSpriteText bpmText; - private Container textContainer; + private Container scaleContainer = null!; + private Container lights = null!; + private Container lightsGlow = null!; + private OsuSpriteText bpmText = null!; + private Container textContainer = null!; private bool grabbedMouseDown; - private ScheduledDelegate resetDelegate; + private ScheduledDelegate? resetDelegate; private const int light_count = 6; @@ -280,16 +287,24 @@ namespace osu.Game.Screens.Edit.Timing double bpm = Math.Round(60000 / ((tapTimings.Last() - tapTimings.First()) / (tapTimings.Count - 1))); bpmText.Text = $"{bpm} BPM"; + + var timingPoint = selectedGroup?.Value.ControlPoints.OfType().FirstOrDefault(); + + if (timingPoint != null) + { + // Intentionally use the rounded BPM here. + timingPoint.BeatLength = 60000 / bpm; + } } private class Light : CompositeDrawable { - public Drawable Glow { get; private set; } + public Drawable Glow { get; private set; } = null!; - private Container fillContent; + private Container fillContent = null!; [Resolved] - private OverlayColourProvider colourProvider { get; set; } + private OverlayColourProvider colourProvider { get; set; } = null!; [BackgroundDependencyLoader] private void load() From 96ccd29bdc9d2372abdca8fe1a8ba284e87ceb3c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 1 Jun 2022 20:48:06 +0900 Subject: [PATCH 328/395] Don't play metronome click when tapping for timing --- osu.Game/Screens/Edit/Timing/MetronomeDisplay.cs | 5 +++++ osu.Game/Screens/Edit/Timing/TapButton.cs | 4 ++++ osu.Game/Screens/Edit/Timing/TapTimingControl.cs | 12 +++++++++++- 3 files changed, 20 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Timing/MetronomeDisplay.cs b/osu.Game/Screens/Edit/Timing/MetronomeDisplay.cs index 4143c5ea55..2ecd66a05f 100644 --- a/osu.Game/Screens/Edit/Timing/MetronomeDisplay.cs +++ b/osu.Game/Screens/Edit/Timing/MetronomeDisplay.cs @@ -38,6 +38,8 @@ namespace osu.Game.Screens.Edit.Timing [Resolved] private OverlayColourProvider overlayColourProvider { get; set; } + public bool EnableClicking { get; set; } = true; + [BackgroundDependencyLoader] private void load(AudioManager audio) { @@ -281,6 +283,9 @@ namespace osu.Game.Screens.Edit.Timing Schedule(() => { + if (!EnableClicking) + return; + var channel = clunk?.GetChannel(); if (channel != null) diff --git a/osu.Game/Screens/Edit/Timing/TapButton.cs b/osu.Game/Screens/Edit/Timing/TapButton.cs index d85edb6cce..fb5580c6ac 100644 --- a/osu.Game/Screens/Edit/Timing/TapButton.cs +++ b/osu.Game/Screens/Edit/Timing/TapButton.cs @@ -30,6 +30,8 @@ namespace osu.Game.Screens.Edit.Timing { public const float SIZE = 100; + public readonly BindableBool IsHandlingTapping = new BindableBool(); + [Resolved] private OverlayColourProvider colourProvider { get; set; } = null!; @@ -213,6 +215,7 @@ namespace osu.Game.Screens.Edit.Timing const double in_duration = 100; grabbedMouseDown = true; + IsHandlingTapping.Value = true; handleTap(); @@ -269,6 +272,7 @@ namespace osu.Game.Screens.Edit.Timing light.Hide(); tapTimings.Clear(); + IsHandlingTapping.Value = false; } private void handleTap() diff --git a/osu.Game/Screens/Edit/Timing/TapTimingControl.cs b/osu.Game/Screens/Edit/Timing/TapTimingControl.cs index 20d6c656ff..16e9f703c8 100644 --- a/osu.Game/Screens/Edit/Timing/TapTimingControl.cs +++ b/osu.Game/Screens/Edit/Timing/TapTimingControl.cs @@ -25,6 +25,10 @@ namespace osu.Game.Screens.Edit.Timing [Resolved] private Bindable selectedGroup { get; set; } + private readonly BindableBool isHandlingTapping = new BindableBool(); + + private MetronomeDisplay metronome; + [BackgroundDependencyLoader] private void load(OverlayColourProvider colourProvider, OsuColour colours) { @@ -73,7 +77,7 @@ namespace osu.Game.Screens.Edit.Timing { new Drawable[] { - new MetronomeDisplay + metronome = new MetronomeDisplay { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, @@ -149,11 +153,17 @@ namespace osu.Game.Screens.Edit.Timing { Anchor = Anchor.Centre, Origin = Anchor.Centre, + IsHandlingTapping = { BindTarget = isHandlingTapping } } } } }, }; + + isHandlingTapping.BindValueChanged(handling => + { + metronome.EnableClicking = !handling.NewValue; + }, true); } private void adjustOffset(double adjust) From b88bce9b8baf47dc54b27e041d9a51c41e672dfc Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 1 Jun 2022 20:57:16 +0900 Subject: [PATCH 329/395] Restart track playback when tapping to time --- osu.Game/Screens/Edit/Timing/TapTimingControl.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/osu.Game/Screens/Edit/Timing/TapTimingControl.cs b/osu.Game/Screens/Edit/Timing/TapTimingControl.cs index 16e9f703c8..6640eeaa38 100644 --- a/osu.Game/Screens/Edit/Timing/TapTimingControl.cs +++ b/osu.Game/Screens/Edit/Timing/TapTimingControl.cs @@ -163,6 +163,12 @@ namespace osu.Game.Screens.Edit.Timing isHandlingTapping.BindValueChanged(handling => { metronome.EnableClicking = !handling.NewValue; + + if (handling.NewValue) + { + editorClock.Seek(selectedGroup.Value.Time); + editorClock.Start(); + } }, true); } From f3f7e28353d340b916a2d4ba7735e21de3c51c16 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 1 Jun 2022 20:57:24 +0900 Subject: [PATCH 330/395] Ignore initial taps as they are generally inaccurate --- osu.Game/Screens/Edit/Timing/TapButton.cs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Edit/Timing/TapButton.cs b/osu.Game/Screens/Edit/Timing/TapButton.cs index fb5580c6ac..65f31f4396 100644 --- a/osu.Game/Screens/Edit/Timing/TapButton.cs +++ b/osu.Game/Screens/Edit/Timing/TapButton.cs @@ -275,20 +275,23 @@ namespace osu.Game.Screens.Edit.Timing IsHandlingTapping.Value = false; } + private const int initial_taps_to_ignore = 4; + private const int max_taps_to_consider = 8; + private void handleTap() { tapTimings.Add(Clock.CurrentTime); - if (tapTimings.Count > 8) + if (tapTimings.Count > initial_taps_to_ignore + max_taps_to_consider) tapTimings.RemoveAt(0); - if (tapTimings.Count < 5) + if (tapTimings.Count < initial_taps_to_ignore * 2) { bpmText.Text = new string('.', tapTimings.Count); return; } - double bpm = Math.Round(60000 / ((tapTimings.Last() - tapTimings.First()) / (tapTimings.Count - 1))); + double bpm = Math.Round(60000 / ((tapTimings.Last() - tapTimings.Skip(initial_taps_to_ignore).First()) / (tapTimings.Count - initial_taps_to_ignore - 1))); bpmText.Text = $"{bpm} BPM"; From d4e88441eceabfd2fce25cfb268836e896c5473d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 1 Jun 2022 22:00:34 +0900 Subject: [PATCH 331/395] Adjust metrics to make timing section fit better in editor --- osu.Game/Screens/Edit/Timing/GroupSection.cs | 2 +- osu.Game/Screens/Edit/Timing/Section.cs | 2 +- osu.Game/Screens/Edit/Timing/TimingAdjustButton.cs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Edit/Timing/GroupSection.cs b/osu.Game/Screens/Edit/Timing/GroupSection.cs index bb2dd35a9c..f613488aae 100644 --- a/osu.Game/Screens/Edit/Timing/GroupSection.cs +++ b/osu.Game/Screens/Edit/Timing/GroupSection.cs @@ -37,7 +37,7 @@ namespace osu.Game.Screens.Edit.Timing RelativeSizeAxes = Axes.X; AutoSizeAxes = Axes.Y; - Padding = new MarginPadding(10); + Padding = new MarginPadding(10) { Bottom = 0 }; InternalChildren = new Drawable[] { diff --git a/osu.Game/Screens/Edit/Timing/Section.cs b/osu.Game/Screens/Edit/Timing/Section.cs index 139abfb187..17147c21f4 100644 --- a/osu.Game/Screens/Edit/Timing/Section.cs +++ b/osu.Game/Screens/Edit/Timing/Section.cs @@ -77,7 +77,7 @@ namespace osu.Game.Screens.Edit.Timing { Flow = new FillFlowContainer { - Padding = new MarginPadding(20), + Padding = new MarginPadding(10) { Top = 0 }, Spacing = new Vector2(20), RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, diff --git a/osu.Game/Screens/Edit/Timing/TimingAdjustButton.cs b/osu.Game/Screens/Edit/Timing/TimingAdjustButton.cs index 9fc7e56a3d..eecd85fbca 100644 --- a/osu.Game/Screens/Edit/Timing/TimingAdjustButton.cs +++ b/osu.Game/Screens/Edit/Timing/TimingAdjustButton.cs @@ -187,7 +187,7 @@ namespace osu.Game.Screens.Edit.Timing Origin = direction, Font = OsuFont.Default.With(size: 10, weight: FontWeight.Bold), Text = $"{(index > 0 ? "+" : "-")}{Math.Abs(Multiplier * amount)}", - Padding = new MarginPadding(5), + Padding = new MarginPadding(2), Alpha = 0, } }; From 781a1527b10e8c5d846b335e2961d1749aa2c995 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 1 Jun 2022 22:00:54 +0900 Subject: [PATCH 332/395] Adjust button metrics and move surrounding buttons to be more integrated --- osu.Game/Screens/Edit/Timing/TapButton.cs | 27 +-- .../Screens/Edit/Timing/TapTimingControl.cs | 159 +++++++++++++----- 2 files changed, 130 insertions(+), 56 deletions(-) diff --git a/osu.Game/Screens/Edit/Timing/TapButton.cs b/osu.Game/Screens/Edit/Timing/TapButton.cs index 65f31f4396..d18f65c2eb 100644 --- a/osu.Game/Screens/Edit/Timing/TapButton.cs +++ b/osu.Game/Screens/Edit/Timing/TapButton.cs @@ -28,7 +28,7 @@ namespace osu.Game.Screens.Edit.Timing { internal class TapButton : CircularContainer { - public const float SIZE = 100; + public const float SIZE = 140; public readonly BindableBool IsHandlingTapping = new BindableBool(); @@ -62,7 +62,7 @@ namespace osu.Game.Screens.Edit.Timing { Size = new Vector2(SIZE); - const float ring_width = 18; + const float ring_width = 22; const float light_padding = 3; InternalChild = scaleContainer = new Container @@ -75,7 +75,7 @@ namespace osu.Game.Screens.Edit.Timing new Circle { RelativeSizeAxes = Axes.Both, - Colour = colourProvider.Background3 + Colour = colourProvider.Background4 }, lights = new Container { @@ -87,7 +87,7 @@ namespace osu.Game.Screens.Edit.Timing Name = @"outer masking", Masking = true, BorderThickness = light_padding, - BorderColour = colourProvider.Background3, + BorderColour = colourProvider.Background4, Children = new Drawable[] { new Box @@ -105,7 +105,7 @@ namespace osu.Game.Screens.Edit.Timing Size = new Vector2(SIZE - ring_width * 2 + light_padding * 2), Anchor = Anchor.Centre, Origin = Anchor.Centre, - Colour = colourProvider.Background3, + Colour = colourProvider.Background4, }, lightsGlow = new Container { @@ -139,17 +139,18 @@ namespace osu.Game.Screens.Edit.Timing { new OsuSpriteText { - Font = OsuFont.Torus.With(size: 20), + Font = OsuFont.Torus.With(size: 34, weight: FontWeight.SemiBold), Anchor = Anchor.Centre, Origin = Anchor.BottomCentre, - Y = 3, - Text = "Tap" + Y = 5, + Text = "Tap", }, bpmText = new OsuSpriteText { - Font = OsuFont.Torus.With(size: 14), + Font = OsuFont.Torus.With(size: 23, weight: FontWeight.Regular), Anchor = Anchor.Centre, Origin = Anchor.TopCentre, + Y = -1, }, } }, @@ -200,7 +201,7 @@ namespace osu.Game.Screens.Edit.Timing { hoverLayer.FadeIn(500, Easing.OutQuint); textContainer.FadeColour(textColour, 500, Easing.OutQuint); - return base.OnHover(e); + return true; } protected override void OnHoverLost(HoverLostEvent e) @@ -322,12 +323,14 @@ namespace osu.Game.Screens.Edit.Timing Size = new Vector2(0.98f); // Avoid bleed into masking edge. + const float angular_gap = 0.007f; + InternalChildren = new Drawable[] { new CircularProgress { RelativeSizeAxes = Axes.Both, - Current = { Value = 1f / light_count - 0.01f }, + Current = { Value = 1f / light_count - angular_gap }, Colour = colourProvider.Background2, }, fillContent = new Container @@ -340,7 +343,7 @@ namespace osu.Game.Screens.Edit.Timing new CircularProgress { RelativeSizeAxes = Axes.Both, - Current = { Value = 1f / light_count - 0.01f }, + Current = { Value = 1f / light_count - angular_gap }, Blending = BlendingParameters.Additive }, Glow = new CircularProgress diff --git a/osu.Game/Screens/Edit/Timing/TapTimingControl.cs b/osu.Game/Screens/Edit/Timing/TapTimingControl.cs index 6640eeaa38..f8975c5b11 100644 --- a/osu.Game/Screens/Edit/Timing/TapTimingControl.cs +++ b/osu.Game/Screens/Edit/Timing/TapTimingControl.cs @@ -7,10 +7,14 @@ 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.Events; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Graphics; +using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterfaceV2; using osu.Game.Overlays; +using osuTK; namespace osu.Game.Screens.Edit.Timing { @@ -32,6 +36,8 @@ namespace osu.Game.Screens.Edit.Timing [BackgroundDependencyLoader] private void load(OverlayColourProvider colourProvider, OsuColour colours) { + const float padding = 10; + RelativeSizeAxes = Axes.X; AutoSizeAxes = Axes.Y; @@ -52,9 +58,8 @@ namespace osu.Game.Screens.Edit.Timing RowDimensions = new[] { new Dimension(GridSizeMode.Absolute, 200), - new Dimension(GridSizeMode.Absolute, 60), - new Dimension(GridSizeMode.Absolute, 60), - new Dimension(GridSizeMode.Absolute, 120), + new Dimension(GridSizeMode.Absolute, 50), + new Dimension(GridSizeMode.Absolute, TapButton.SIZE + padding), }, Content = new[] { @@ -63,6 +68,7 @@ namespace osu.Game.Screens.Edit.Timing new Container { RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding(padding), Children = new Drawable[] { new GridContainer @@ -94,15 +100,14 @@ namespace osu.Game.Screens.Edit.Timing new Container { RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding(10), + Padding = new MarginPadding { Bottom = padding, Horizontal = padding }, Children = new Drawable[] { new TimingAdjustButton(1) { Text = "Offset", - RelativeSizeAxes = Axes.X, - Width = 0.48f, - Height = 50, + RelativeSizeAxes = Axes.Both, + Size = new Vector2(0.48f, 1), Action = adjustOffset, }, new TimingAdjustButton(0.1) @@ -110,9 +115,8 @@ namespace osu.Game.Screens.Edit.Timing Anchor = Anchor.TopRight, Origin = Anchor.TopRight, Text = "BPM", - RelativeSizeAxes = Axes.X, - Width = 0.48f, - Height = 50, + RelativeSizeAxes = Axes.Both, + Size = new Vector2(0.48f, 1), Action = adjustBpm, } } @@ -123,39 +127,47 @@ namespace osu.Game.Screens.Edit.Timing new Container { RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding(10), + Padding = new MarginPadding { Bottom = padding, Horizontal = padding }, Children = new Drawable[] { - new RoundedButton + new Container { - Text = "Reset", - BackgroundColour = colours.Pink, - RelativeSizeAxes = Axes.X, - Width = 0.3f, - Action = reset, + RelativeSizeAxes = Axes.Y, + Anchor = Anchor.Centre, + Origin = Anchor.CentreRight, + Height = 0.98f, + Width = TapButton.SIZE / 1.3f, + Masking = true, + CornerRadius = 15, + Children = new Drawable[] + { + new LessRoundedButton(FontAwesome.Solid.Stop, Anchor.TopLeft) + { + BackgroundColour = colourProvider.Background1, + RelativeSizeAxes = Axes.Both, + Height = 0.49f, + Action = reset, + }, + new LessRoundedButton(FontAwesome.Solid.Play, Anchor.BottomLeft) + { + BackgroundColour = colourProvider.Background1, + RelativeSizeAxes = Axes.Both, + Height = 0.49f, + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + Action = start, + }, + }, }, - new RoundedButton + new TapButton { - Anchor = Anchor.TopRight, - Origin = Anchor.TopRight, - Text = "Play from start", - RelativeSizeAxes = Axes.X, - BackgroundColour = colourProvider.Background1, - Width = 0.68f, - Action = tap, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + IsHandlingTapping = { BindTarget = isHandlingTapping } } } }, }, - new Drawable[] - { - new TapButton - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - IsHandlingTapping = { BindTarget = isHandlingTapping } - } - } } }, }; @@ -165,13 +177,78 @@ namespace osu.Game.Screens.Edit.Timing metronome.EnableClicking = !handling.NewValue; if (handling.NewValue) - { - editorClock.Seek(selectedGroup.Value.Time); - editorClock.Start(); - } + start(); }, true); } + private class LessRoundedButton : OsuButton + { + private readonly IconUsage icon; + private readonly Anchor anchor; + + private SpriteIcon spriteIcon; + + public LessRoundedButton(IconUsage icon, Anchor anchor) + { + this.icon = icon; + this.anchor = anchor; + } + + [Resolved] + private OverlayColourProvider colourProvider { get; set; } + + protected override void LoadComplete() + { + base.LoadComplete(); + + Content.CornerRadius = 0; + Content.Masking = false; + + BackgroundColour = colourProvider.Background2; + + Content.Add(new Container + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding(15), + Children = new Drawable[] + { + spriteIcon = new SpriteIcon + { + Icon = icon, + Size = new Vector2(22), + Anchor = anchor, + Origin = anchor, + Colour = colourProvider.Background1, + }, + } + }); + } + + protected override bool OnMouseDown(MouseDownEvent e) + { + // scale looks bad so don't call base. + return false; + } + + protected override bool OnHover(HoverEvent e) + { + spriteIcon.FadeColour(colourProvider.Content2, 200, Easing.OutQuint); + return base.OnHover(e); + } + + protected override void OnHoverLost(HoverLostEvent e) + { + spriteIcon.FadeColour(colourProvider.Background1, 200, Easing.OutQuint); + base.OnHoverLost(e); + } + } + + private void start() + { + editorClock.Seek(selectedGroup.Value.Time); + editorClock.Start(); + } + private void adjustOffset(double adjust) { // VERY TEMPORARY @@ -201,12 +278,6 @@ namespace osu.Game.Screens.Edit.Timing timing.BeatLength = 60000 / (timing.BPM + adjust); } - private void tap() - { - editorClock.Seek(selectedGroup.Value.Time); - editorClock.Start(); - } - private void reset() { editorClock.Stop(); From 0c493dd3592f96abd63f3390bb1160f9a99ecf6d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 2 Jun 2022 12:27:11 +0900 Subject: [PATCH 333/395] Add key binding for tap button --- .../Input/Bindings/GlobalActionContainer.cs | 4 +++ .../GlobalActionKeyBindingStrings.cs | 5 ++++ osu.Game/Screens/Edit/Timing/TapButton.cs | 25 +++++++++++++++++-- 3 files changed, 32 insertions(+), 2 deletions(-) diff --git a/osu.Game/Input/Bindings/GlobalActionContainer.cs b/osu.Game/Input/Bindings/GlobalActionContainer.cs index 69ea6b00ca..3da5f3212e 100644 --- a/osu.Game/Input/Bindings/GlobalActionContainer.cs +++ b/osu.Game/Input/Bindings/GlobalActionContainer.cs @@ -80,6 +80,7 @@ namespace osu.Game.Input.Bindings new KeyBinding(new[] { InputKey.K }, GlobalAction.EditorNudgeRight), new KeyBinding(new[] { InputKey.G }, GlobalAction.EditorCycleGridDisplayMode), new KeyBinding(new[] { InputKey.F5 }, GlobalAction.EditorTestGameplay), + new KeyBinding(new[] { InputKey.T }, GlobalAction.EditorTapForBPM), new KeyBinding(new[] { InputKey.Control, InputKey.H }, GlobalAction.EditorFlipHorizontally), new KeyBinding(new[] { InputKey.Control, InputKey.J }, GlobalAction.EditorFlipVertically), new KeyBinding(new[] { InputKey.Control, InputKey.Alt, InputKey.MouseWheelDown }, GlobalAction.EditorDecreaseDistanceSpacing), @@ -322,5 +323,8 @@ namespace osu.Game.Input.Bindings [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.DeselectAllMods))] DeselectAllMods, + + [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.EditorTapForBPM))] + EditorTapForBPM, } } diff --git a/osu.Game/Localisation/GlobalActionKeyBindingStrings.cs b/osu.Game/Localisation/GlobalActionKeyBindingStrings.cs index e392ae619f..586e29a432 100644 --- a/osu.Game/Localisation/GlobalActionKeyBindingStrings.cs +++ b/osu.Game/Localisation/GlobalActionKeyBindingStrings.cs @@ -174,6 +174,11 @@ namespace osu.Game.Localisation /// public static LocalisableString EditorTimingMode => new TranslatableString(getKey(@"editor_timing_mode"), @"Timing mode"); + /// + /// "Tap for BPM" + /// + public static LocalisableString EditorTapForBPM => new TranslatableString(getKey(@"editor_tap_for_bpm"), @"Tap for BPM"); + /// /// "Cycle grid display mode" /// diff --git a/osu.Game/Screens/Edit/Timing/TapButton.cs b/osu.Game/Screens/Edit/Timing/TapButton.cs index d18f65c2eb..614e7aecde 100644 --- a/osu.Game/Screens/Edit/Timing/TapButton.cs +++ b/osu.Game/Screens/Edit/Timing/TapButton.cs @@ -15,18 +15,21 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.UserInterface; +using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; using osu.Framework.Threading; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; +using osu.Game.Input.Bindings; using osu.Game.Overlays; using osuTK; using osuTK.Graphics; +using osuTK.Input; namespace osu.Game.Screens.Edit.Timing { - internal class TapButton : CircularContainer + internal class TapButton : CircularContainer, IKeyBindingHandler { public const float SIZE = 140; @@ -57,6 +60,8 @@ namespace osu.Game.Screens.Edit.Timing private const int light_count = 6; + private readonly List tapTimings = new List(); + [BackgroundDependencyLoader] private void load() { @@ -257,7 +262,23 @@ namespace osu.Game.Screens.Edit.Timing base.OnMouseUp(e); } - private readonly List tapTimings = new List(); + public bool OnPressed(KeyBindingPressEvent e) + { + if (e.Action == GlobalAction.EditorTapForBPM && !e.Repeat) + { + // Direct through mouse handling to achieve animation + OnMouseDown(new MouseDownEvent(e.CurrentState, MouseButton.Left)); + return true; + } + + return false; + } + + public void OnReleased(KeyBindingReleaseEvent e) + { + if (e.Action == GlobalAction.EditorTapForBPM) + OnMouseUp(new MouseUpEvent(e.CurrentState, MouseButton.Left)); + } private void reset() { From 943e904c71f74feda0765075798140c2370ac46c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 2 Jun 2022 12:55:03 +0900 Subject: [PATCH 334/395] Fix reset happening on mouse down instead of mouse up Also some reorganisation of file content for legibility --- osu.Game/Screens/Edit/Timing/TapButton.cs | 49 ++++++++++++----------- 1 file changed, 26 insertions(+), 23 deletions(-) diff --git a/osu.Game/Screens/Edit/Timing/TapButton.cs b/osu.Game/Screens/Edit/Timing/TapButton.cs index 614e7aecde..7f913e386e 100644 --- a/osu.Game/Screens/Edit/Timing/TapButton.cs +++ b/osu.Game/Screens/Edit/Timing/TapButton.cs @@ -60,6 +60,10 @@ namespace osu.Game.Screens.Edit.Timing private const int light_count = 6; + private const int initial_taps_to_ignore = 4; + + private const int max_taps_to_consider = 8; + private readonly List tapTimings = new List(); [BackgroundDependencyLoader] @@ -223,10 +227,9 @@ namespace osu.Game.Screens.Edit.Timing grabbedMouseDown = true; IsHandlingTapping.Value = true; - handleTap(); - resetDelegate?.Cancel(); - resetDelegate = Scheduler.AddDelayed(reset, 1000); + + handleTap(); textContainer.FadeColour(textColour, in_duration, Easing.OutQuint); @@ -259,6 +262,9 @@ namespace osu.Game.Screens.Edit.Timing scaleContainer.ScaleTo(1, out_duration, Easing.OutQuint); innerCircle.ScaleTo(1, out_duration, Easing.OutQuint); innerCircleHighlight.FadeOut(out_duration, Easing.OutQuint); + + resetDelegate = Scheduler.AddDelayed(reset, 1000); + base.OnMouseUp(e); } @@ -280,26 +286,6 @@ namespace osu.Game.Screens.Edit.Timing OnMouseUp(new MouseUpEvent(e.CurrentState, MouseButton.Left)); } - private void reset() - { - bpmText.FadeOut(500, Easing.OutQuint); - - using (BeginDelayedSequence(tapTimings.Count > 0 ? 500 : 0)) - { - Schedule(() => bpmText.Text = "the beat!"); - bpmText.FadeIn(800, Easing.OutQuint); - } - - foreach (var light in lights) - light.Hide(); - - tapTimings.Clear(); - IsHandlingTapping.Value = false; - } - - private const int initial_taps_to_ignore = 4; - private const int max_taps_to_consider = 8; - private void handleTap() { tapTimings.Add(Clock.CurrentTime); @@ -326,6 +312,23 @@ namespace osu.Game.Screens.Edit.Timing } } + private void reset() + { + bpmText.FadeOut(500, Easing.OutQuint); + + using (BeginDelayedSequence(tapTimings.Count > 0 ? 500 : 0)) + { + Schedule(() => bpmText.Text = "the beat!"); + bpmText.FadeIn(800, Easing.OutQuint); + } + + foreach (var light in lights) + light.Hide(); + + tapTimings.Clear(); + IsHandlingTapping.Value = false; + } + private class Light : CompositeDrawable { public Drawable Glow { get; private set; } = null!; From d99d37c0a60c6eb95d43e920331210d4af315d6f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 2 Jun 2022 13:06:18 +0900 Subject: [PATCH 335/395] Apply current track rate to calculated BPM --- osu.Game/Screens/Edit/Timing/TapButton.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Timing/TapButton.cs b/osu.Game/Screens/Edit/Timing/TapButton.cs index 7f913e386e..6b9ac7be5c 100644 --- a/osu.Game/Screens/Edit/Timing/TapButton.cs +++ b/osu.Game/Screens/Edit/Timing/TapButton.cs @@ -18,6 +18,7 @@ using osu.Framework.Graphics.UserInterface; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; using osu.Framework.Threading; +using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; @@ -41,6 +42,9 @@ namespace osu.Game.Screens.Edit.Timing [Resolved(canBeNull: true)] private Bindable? selectedGroup { get; set; } + [Resolved(canBeNull: true)] + private IBeatSyncProvider? beatSyncSource { get; set; } + private Circle hoverLayer = null!; private CircularContainer innerCircle = null!; @@ -299,7 +303,10 @@ namespace osu.Game.Screens.Edit.Timing return; } - double bpm = Math.Round(60000 / ((tapTimings.Last() - tapTimings.Skip(initial_taps_to_ignore).First()) / (tapTimings.Count - initial_taps_to_ignore - 1))); + double averageBeatLength = (tapTimings.Last() - tapTimings.Skip(initial_taps_to_ignore).First()) / (tapTimings.Count - initial_taps_to_ignore - 1); + double clockRate = beatSyncSource?.Clock?.Rate ?? 1; + + double bpm = Math.Round(60000 / averageBeatLength / clockRate); bpmText.Text = $"{bpm} BPM"; From ac3793f340d14cd3a9d0a7b4dc738f3ee6c96f35 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 2 Jun 2022 13:10:50 +0900 Subject: [PATCH 336/395] Move inline class to end and apply NRT --- .../Screens/Edit/Timing/TapTimingControl.cs | 108 +++++++++--------- 1 file changed, 55 insertions(+), 53 deletions(-) diff --git a/osu.Game/Screens/Edit/Timing/TapTimingControl.cs b/osu.Game/Screens/Edit/Timing/TapTimingControl.cs index f8975c5b11..9b5574d3cb 100644 --- a/osu.Game/Screens/Edit/Timing/TapTimingControl.cs +++ b/osu.Game/Screens/Edit/Timing/TapTimingControl.cs @@ -1,6 +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 enable + using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -21,17 +23,17 @@ namespace osu.Game.Screens.Edit.Timing public class TapTimingControl : CompositeDrawable { [Resolved] - private EditorClock editorClock { get; set; } + private EditorClock editorClock { get; set; } = null!; [Resolved] - private EditorBeatmap beatmap { get; set; } + private EditorBeatmap beatmap { get; set; } = null!; [Resolved] - private Bindable selectedGroup { get; set; } + private Bindable selectedGroup { get; set; } = null!; private readonly BindableBool isHandlingTapping = new BindableBool(); - private MetronomeDisplay metronome; + private MetronomeDisplay metronome = null!; [BackgroundDependencyLoader] private void load(OverlayColourProvider colourProvider, OsuColour colours) @@ -141,14 +143,14 @@ namespace osu.Game.Screens.Edit.Timing CornerRadius = 15, Children = new Drawable[] { - new LessRoundedButton(FontAwesome.Solid.Stop, Anchor.TopLeft) + new InlineButton(FontAwesome.Solid.Stop, Anchor.TopLeft) { BackgroundColour = colourProvider.Background1, RelativeSizeAxes = Axes.Both, Height = 0.49f, Action = reset, }, - new LessRoundedButton(FontAwesome.Solid.Play, Anchor.BottomLeft) + new InlineButton(FontAwesome.Solid.Play, Anchor.BottomLeft) { BackgroundColour = colourProvider.Background1, RelativeSizeAxes = Axes.Both, @@ -181,22 +183,63 @@ namespace osu.Game.Screens.Edit.Timing }, true); } - private class LessRoundedButton : OsuButton + private void start() + { + editorClock.Seek(selectedGroup.Value.Time); + editorClock.Start(); + } + + private void reset() + { + editorClock.Stop(); + editorClock.Seek(selectedGroup.Value.Time); + } + + private void adjustOffset(double adjust) + { + // VERY TEMPORARY + var currentGroupItems = selectedGroup.Value.ControlPoints.ToArray(); + + beatmap.ControlPointInfo.RemoveGroup(selectedGroup.Value); + + double newOffset = selectedGroup.Value.Time + adjust; + + foreach (var cp in currentGroupItems) + beatmap.ControlPointInfo.Add(newOffset, cp); + + // the control point might not necessarily exist yet, if currentGroupItems was empty. + selectedGroup.Value = beatmap.ControlPointInfo.GroupAt(newOffset, true); + + if (!editorClock.IsRunning) + editorClock.Seek(newOffset); + } + + private void adjustBpm(double adjust) + { + var timing = selectedGroup.Value.ControlPoints.OfType().FirstOrDefault(); + + if (timing == null) + return; + + timing.BeatLength = 60000 / (timing.BPM + adjust); + } + + private class InlineButton : OsuButton { private readonly IconUsage icon; private readonly Anchor anchor; - private SpriteIcon spriteIcon; + private SpriteIcon spriteIcon = null!; - public LessRoundedButton(IconUsage icon, Anchor anchor) + [Resolved] + private OverlayColourProvider colourProvider { get; set; } = null!; + + public InlineButton(IconUsage icon, Anchor anchor) { this.icon = icon; this.anchor = anchor; } - [Resolved] - private OverlayColourProvider colourProvider { get; set; } - protected override void LoadComplete() { base.LoadComplete(); @@ -242,46 +285,5 @@ namespace osu.Game.Screens.Edit.Timing base.OnHoverLost(e); } } - - private void start() - { - editorClock.Seek(selectedGroup.Value.Time); - editorClock.Start(); - } - - private void adjustOffset(double adjust) - { - // VERY TEMPORARY - var currentGroupItems = selectedGroup.Value.ControlPoints.ToArray(); - - beatmap.ControlPointInfo.RemoveGroup(selectedGroup.Value); - - double newOffset = selectedGroup.Value.Time + adjust; - - foreach (var cp in currentGroupItems) - beatmap.ControlPointInfo.Add(newOffset, cp); - - // the control point might not necessarily exist yet, if currentGroupItems was empty. - selectedGroup.Value = beatmap.ControlPointInfo.GroupAt(newOffset, true); - - if (!editorClock.IsRunning) - editorClock.Seek(newOffset); - } - - private void adjustBpm(double adjust) - { - var timing = selectedGroup.Value.ControlPoints.OfType().FirstOrDefault(); - - if (timing == null) - return; - - timing.BeatLength = 60000 / (timing.BPM + adjust); - } - - private void reset() - { - editorClock.Stop(); - editorClock.Seek(selectedGroup.Value.Time); - } } } From 15f8d318eb05355d09fce8e457d0bbfb9660377a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 2 Jun 2022 13:13:00 +0900 Subject: [PATCH 337/395] Add note about glow code (please look away) --- osu.Game/Screens/Edit/Timing/TapButton.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Screens/Edit/Timing/TapButton.cs b/osu.Game/Screens/Edit/Timing/TapButton.cs index 6b9ac7be5c..0cb2ac3d1a 100644 --- a/osu.Game/Screens/Edit/Timing/TapButton.cs +++ b/osu.Game/Screens/Edit/Timing/TapButton.cs @@ -377,6 +377,9 @@ namespace osu.Game.Screens.Edit.Timing Current = { Value = 1f / light_count - angular_gap }, Blending = BlendingParameters.Additive }, + // Please do not try and make sense of this. + // Getting the visual effect I was going for relies on what I can only imagine is broken implementation + // of `PadExtent`. If that's ever fixed in the future this will likely need to be adjusted. Glow = new CircularProgress { RelativeSizeAxes = Axes.Both, From 060372a129a4e42fc3d2f67a9118874e3aa13fb3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 2 Jun 2022 13:16:31 +0900 Subject: [PATCH 338/395] Split out transition length constants --- osu.Game/Screens/Edit/Timing/TapButton.cs | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/osu.Game/Screens/Edit/Timing/TapButton.cs b/osu.Game/Screens/Edit/Timing/TapButton.cs index 0cb2ac3d1a..4eeb358066 100644 --- a/osu.Game/Screens/Edit/Timing/TapButton.cs +++ b/osu.Game/Screens/Edit/Timing/TapButton.cs @@ -68,6 +68,8 @@ namespace osu.Game.Screens.Edit.Timing private const int max_taps_to_consider = 8; + private const double transition_length = 500; + private readonly List tapTimings = new List(); [BackgroundDependencyLoader] @@ -212,15 +214,15 @@ namespace osu.Game.Screens.Edit.Timing protected override bool OnHover(HoverEvent e) { - hoverLayer.FadeIn(500, Easing.OutQuint); - textContainer.FadeColour(textColour, 500, Easing.OutQuint); + hoverLayer.FadeIn(transition_length, Easing.OutQuint); + textContainer.FadeColour(textColour, transition_length, Easing.OutQuint); return true; } protected override void OnHoverLost(HoverLostEvent e) { - hoverLayer.FadeOut(500, Easing.OutQuint); - textContainer.FadeColour(textColour, 500, Easing.OutQuint); + hoverLayer.FadeOut(transition_length, Easing.OutQuint); + textContainer.FadeColour(textColour, transition_length, Easing.OutQuint); base.OnHoverLost(e); } @@ -265,6 +267,7 @@ namespace osu.Game.Screens.Edit.Timing scaleContainer.ScaleTo(1, out_duration, Easing.OutQuint); innerCircle.ScaleTo(1, out_duration, Easing.OutQuint); + innerCircleHighlight.FadeOut(out_duration, Easing.OutQuint); resetDelegate = Scheduler.AddDelayed(reset, 1000); @@ -321,9 +324,9 @@ namespace osu.Game.Screens.Edit.Timing private void reset() { - bpmText.FadeOut(500, Easing.OutQuint); + bpmText.FadeOut(transition_length, Easing.OutQuint); - using (BeginDelayedSequence(tapTimings.Count > 0 ? 500 : 0)) + using (BeginDelayedSequence(tapTimings.Count > 0 ? transition_length : 0)) { Schedule(() => bpmText.Text = "the beat!"); bpmText.FadeIn(800, Easing.OutQuint); From 5bd9d882195dbe1f1f60032f2456a062a2072501 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 2 Jun 2022 16:34:23 +0900 Subject: [PATCH 339/395] Fix tutorial download state not matching correctly when already available locally Closes https://github.com/ppy/osu/issues/18468. This doesn't stop the tutorial from downloading a second time, but at least displays the correct status afterwards. Avoiding the download is a bit more involved and requires a change to the flow. Probably not worth it just yet. To test, recommend switching to production environment, as dev server doesn't have correct metadata for tutorial resulting in weirdness. --- osu.Game/Beatmaps/Drawables/BundledBeatmapDownloader.cs | 2 ++ osu.Game/Overlays/FirstRunSetup/ScreenBeatmaps.cs | 9 ++++++--- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/osu.Game/Beatmaps/Drawables/BundledBeatmapDownloader.cs b/osu.Game/Beatmaps/Drawables/BundledBeatmapDownloader.cs index 84903d381a..5ebdee0b09 100644 --- a/osu.Game/Beatmaps/Drawables/BundledBeatmapDownloader.cs +++ b/osu.Game/Beatmaps/Drawables/BundledBeatmapDownloader.cs @@ -85,6 +85,8 @@ namespace osu.Game.Beatmaps.Drawables downloadTrackers.Add(beatmapDownloadTracker); AddInternal(beatmapDownloadTracker); + // Note that this is downloading the beatmaps even if they are already downloaded. + // We could rely more on `BeatmapDownloadTracker`'s exposed state to avoid this. beatmapDownloader.Download(beatmapSet); } } diff --git a/osu.Game/Overlays/FirstRunSetup/ScreenBeatmaps.cs b/osu.Game/Overlays/FirstRunSetup/ScreenBeatmaps.cs index 17e04c0c99..ddcee7c040 100644 --- a/osu.Game/Overlays/FirstRunSetup/ScreenBeatmaps.cs +++ b/osu.Game/Overlays/FirstRunSetup/ScreenBeatmaps.cs @@ -154,12 +154,15 @@ namespace osu.Game.Overlays.FirstRunSetup var downloadTracker = tutorialDownloader.DownloadTrackers.First(); + downloadTracker.State.BindValueChanged(state => + { + if (state.NewValue == DownloadState.LocallyAvailable) + downloadTutorialButton.Complete(); + }, true); + downloadTracker.Progress.BindValueChanged(progress => { downloadTutorialButton.SetProgress(progress.NewValue, false); - - if (progress.NewValue == 1) - downloadTutorialButton.Complete(); }, true); } From 055845d4f56db74f7840adf74d76cbce1fe6b687 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 2 Jun 2022 16:34:36 +0900 Subject: [PATCH 340/395] Throw exception on access before ApplyBeatmap() --- osu.Game/Rulesets/Scoring/ScoreProcessor.cs | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index 853f43ddb6..dd977a5c50 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -93,7 +93,18 @@ namespace osu.Game.Rulesets.Scoring /// /// Scoring values for a perfect play. /// - public ScoringValues MaximumScoringValues { get; private set; } + public ScoringValues MaximumScoringValues + { + get + { + if (!beatmapApplied) + throw new InvalidOperationException($"Cannot access maximum scoring values before calling {nameof(ApplyBeatmap)}."); + + return maximumScoringValues; + } + } + + private ScoringValues maximumScoringValues; /// /// Scoring values for the current play assuming all perfect hits. @@ -255,7 +266,7 @@ namespace osu.Game.Rulesets.Scoring private void updateScore() { Accuracy.Value = currentMaximumScoringValues.BaseScore > 0 ? currentScoringValues.BaseScore / currentMaximumScoringValues.BaseScore : 1; - TotalScore.Value = ComputeScore(Mode.Value, currentScoringValues, MaximumScoringValues); + TotalScore.Value = ComputeScore(Mode.Value, currentScoringValues, maximumScoringValues); } /// @@ -406,7 +417,7 @@ namespace osu.Game.Rulesets.Scoring lastHitObject = null; if (storeResults) - MaximumScoringValues = currentScoringValues; + maximumScoringValues = currentScoringValues; currentScoringValues = default; currentMaximumScoringValues = default; From a638392e81e9656b305a0b4c9ed8b0c8d4cd347d Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 2 Jun 2022 16:35:50 +0900 Subject: [PATCH 341/395] Rename member --- osu.Game/Rulesets/Scoring/ScoreProcessor.cs | 22 ++++++++++----------- osu.Game/Scoring/ScoringValues.cs | 2 +- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index dd977a5c50..4a5897c621 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -211,7 +211,7 @@ namespace osu.Game.Rulesets.Scoring scoringValues.BaseScore += result.IsHit() ? Judgement.ToNumericResult(result) : 0; if (result.IsBasic()) - scoringValues.HitObjects++; + scoringValues.CountBasicHitObjects++; } /// @@ -260,7 +260,7 @@ namespace osu.Game.Rulesets.Scoring scoringValues.BaseScore -= result.IsHit() ? Judgement.ToNumericResult(result) : 0; if (result.IsBasic()) - scoringValues.HitObjects--; + scoringValues.CountBasicHitObjects--; } private void updateScore() @@ -340,7 +340,7 @@ namespace osu.Game.Rulesets.Scoring if (scoreInfo.IsLegacyScore && scoreInfo.Ruleset.OnlineID == 3 && maximum.BaseScore > 0) accuracyRatio = current.BaseScore / maximum.BaseScore; - return ComputeScore(mode, accuracyRatio, comboRatio, current.BonusScore, maximum.HitObjects); + return ComputeScore(mode, accuracyRatio, comboRatio, current.BonusScore, maximum.CountBasicHitObjects); } /// @@ -355,7 +355,7 @@ namespace osu.Game.Rulesets.Scoring { double accuracyRatio = maximum.BaseScore > 0 ? current.BaseScore / maximum.BaseScore : 1; double comboRatio = maximum.MaxCombo > 0 ? (double)current.MaxCombo / maximum.MaxCombo : 1; - return ComputeScore(mode, accuracyRatio, comboRatio, current.BonusScore, maximum.HitObjects); + return ComputeScore(mode, accuracyRatio, comboRatio, current.BonusScore, maximum.CountBasicHitObjects); } /// @@ -479,11 +479,11 @@ namespace osu.Game.Rulesets.Scoring /// This method is useful in a variety of situations, with a few drawbacks that need to be considered: /// /// The maximum will always be 0. - /// The current and maximum will always be the same value. + /// The current and maximum will always be the same value. /// /// Consumers are expected to more accurately fill in the above values through external means. /// - /// Ensure to fill in the maximum for use in + /// Ensure to fill in the maximum for use in /// . /// /// @@ -504,11 +504,11 @@ namespace osu.Game.Rulesets.Scoring /// This method is useful in a variety of situations, with a few drawbacks that need to be considered: /// /// The maximum will always be 0. - /// The current and maximum will always be the same value. + /// The current and maximum will always be the same value. /// /// Consumers are expected to more accurately fill in the above values through external means. /// - /// Ensure to fill in the maximum for use in + /// Ensure to fill in the maximum for use in /// . /// /// @@ -530,7 +530,7 @@ namespace osu.Game.Rulesets.Scoring /// /// The current will always be 0. /// The maximum will always be 0. - /// The current and maximum will always be the same value. + /// The current and maximum will always be the same value. /// /// Consumers are expected to more accurately fill in the above values (especially the current ) via external means (e.g. ). /// @@ -582,8 +582,8 @@ namespace osu.Game.Rulesets.Scoring if (result.IsBasic()) { - current.HitObjects += count; - maximum.HitObjects += count; + current.CountBasicHitObjects += count; + maximum.CountBasicHitObjects += count; } } } diff --git a/osu.Game/Scoring/ScoringValues.cs b/osu.Game/Scoring/ScoringValues.cs index 4b562c20e4..09ecd8e336 100644 --- a/osu.Game/Scoring/ScoringValues.cs +++ b/osu.Game/Scoring/ScoringValues.cs @@ -33,6 +33,6 @@ namespace osu.Game.Scoring /// The count of "basic" s. See: . /// [Key(3)] - public int HitObjects; + public int CountBasicHitObjects; } } From 773ba971173ef2a79fa5cfe2cdc4a91b2c676af4 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 2 Jun 2022 16:39:50 +0900 Subject: [PATCH 342/395] Add xmldoc to ScoringValues --- osu.Game/Scoring/ScoringValues.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Scoring/ScoringValues.cs b/osu.Game/Scoring/ScoringValues.cs index 09ecd8e336..d31cd7c68b 100644 --- a/osu.Game/Scoring/ScoringValues.cs +++ b/osu.Game/Scoring/ScoringValues.cs @@ -8,6 +8,9 @@ using osu.Game.Rulesets.Scoring; namespace osu.Game.Scoring { + /// + /// Stores the required scoring data that fulfils the minimum requirements for a to calculate score. + /// [MessagePackObject] public struct ScoringValues { From 4abfb3561142fef1eb9e2a8b8cb2169bff0955c3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 2 Jun 2022 16:58:14 +0900 Subject: [PATCH 343/395] Improve light rotational alignment and increase light count to 8 --- osu.Game/Screens/Edit/Timing/TapButton.cs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/osu.Game/Screens/Edit/Timing/TapButton.cs b/osu.Game/Screens/Edit/Timing/TapButton.cs index 4eeb358066..3b4d63f7b8 100644 --- a/osu.Game/Screens/Edit/Timing/TapButton.cs +++ b/osu.Game/Screens/Edit/Timing/TapButton.cs @@ -62,7 +62,7 @@ namespace osu.Game.Screens.Edit.Timing private ScheduledDelegate? resetDelegate; - private const int light_count = 6; + private const int light_count = 8; private const int initial_taps_to_ignore = 4; @@ -70,6 +70,8 @@ namespace osu.Game.Screens.Edit.Timing private const double transition_length = 500; + private const float angular_light_gap = 0.007f; + private readonly List tapTimings = new List(); [BackgroundDependencyLoader] @@ -185,7 +187,7 @@ namespace osu.Game.Screens.Edit.Timing { var light = new Light { - Rotation = i * (360f / light_count) + Rotation = (i + 1) * (360f / light_count) + 360 * angular_light_gap / 2, }; lights.Add(light); @@ -336,6 +338,7 @@ namespace osu.Game.Screens.Edit.Timing light.Hide(); tapTimings.Clear(); + currentLight = 0; IsHandlingTapping.Value = false; } @@ -357,14 +360,12 @@ namespace osu.Game.Screens.Edit.Timing Size = new Vector2(0.98f); // Avoid bleed into masking edge. - const float angular_gap = 0.007f; - InternalChildren = new Drawable[] { new CircularProgress { RelativeSizeAxes = Axes.Both, - Current = { Value = 1f / light_count - angular_gap }, + Current = { Value = 1f / light_count - angular_light_gap }, Colour = colourProvider.Background2, }, fillContent = new Container @@ -377,7 +378,7 @@ namespace osu.Game.Screens.Edit.Timing new CircularProgress { RelativeSizeAxes = Axes.Both, - Current = { Value = 1f / light_count - angular_gap }, + Current = { Value = 1f / light_count - angular_light_gap }, Blending = BlendingParameters.Additive }, // Please do not try and make sense of this. From e75609dfb9dd33cc30ffb5779da80e9988c7031b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 2 Jun 2022 17:03:48 +0900 Subject: [PATCH 344/395] Increase taps to consider for better results for longer tap periods --- osu.Game/Screens/Edit/Timing/TapButton.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Timing/TapButton.cs b/osu.Game/Screens/Edit/Timing/TapButton.cs index 3b4d63f7b8..5453fcd68d 100644 --- a/osu.Game/Screens/Edit/Timing/TapButton.cs +++ b/osu.Game/Screens/Edit/Timing/TapButton.cs @@ -66,7 +66,7 @@ namespace osu.Game.Screens.Edit.Timing private const int initial_taps_to_ignore = 4; - private const int max_taps_to_consider = 8; + private const int max_taps_to_consider = 16; private const double transition_length = 500; From ee4beefd9584358229f2478f0aee7ef3e98cc093 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 2 Jun 2022 17:27:51 +0900 Subject: [PATCH 345/395] Increase max taps to 128 for now Will revisit this in the future with a more sound algorithm. --- osu.Game/Screens/Edit/Timing/TapButton.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Timing/TapButton.cs b/osu.Game/Screens/Edit/Timing/TapButton.cs index 5453fcd68d..a6227cbe27 100644 --- a/osu.Game/Screens/Edit/Timing/TapButton.cs +++ b/osu.Game/Screens/Edit/Timing/TapButton.cs @@ -66,7 +66,7 @@ namespace osu.Game.Screens.Edit.Timing private const int initial_taps_to_ignore = 4; - private const int max_taps_to_consider = 16; + private const int max_taps_to_consider = 128; private const double transition_length = 500; From c42485cea9314356e93c507d42516cc00ac36c6e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 2 Jun 2022 17:29:39 +0900 Subject: [PATCH 346/395] Fix test button references --- .../Editing/TestSceneTapTimingControl.cs | 54 +++++++++---------- 1 file changed, 24 insertions(+), 30 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneTapTimingControl.cs b/osu.Game.Tests/Visual/Editing/TestSceneTapTimingControl.cs index 8dd368f2a9..a1218aa3e7 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneTapTimingControl.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneTapTimingControl.cs @@ -11,7 +11,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Testing; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Graphics.Sprites; -using osu.Game.Graphics.UserInterfaceV2; +using osu.Game.Graphics.UserInterface; using osu.Game.Overlays; using osu.Game.Screens.Edit; using osu.Game.Screens.Edit.Timing; @@ -77,34 +77,6 @@ namespace osu.Game.Tests.Visual.Editing timingInfo.Text = $"offset: {selectedGroup.Value.Time:N2} bpm: {selectedGroup.Value.ControlPoints.OfType().First().BPM:N2}"; } - [Test] - public void TestNoop() - { - AddStep("do nothing", () => { }); - } - - [Test] - public void TestTapThenReset() - { - AddStep("click tap button", () => - { - control.ChildrenOfType() - .Last() - .TriggerClick(); - }); - - AddUntilStep("wait for track playing", () => Clock.IsRunning); - - AddStep("click reset button", () => - { - control.ChildrenOfType() - .First() - .TriggerClick(); - }); - - AddUntilStep("wait for track stopped", () => !Clock.IsRunning); - } - [Test] public void TestBasic() { @@ -115,7 +87,7 @@ namespace osu.Game.Tests.Visual.Editing AddStep("click tap button", () => { - control.ChildrenOfType() + control.ChildrenOfType() .Last() .TriggerClick(); }); @@ -129,6 +101,28 @@ namespace osu.Game.Tests.Visual.Editing }); } + [Test] + public void TestTapThenReset() + { + AddStep("click tap button", () => + { + control.ChildrenOfType() + .Last() + .TriggerClick(); + }); + + AddUntilStep("wait for track playing", () => Clock.IsRunning); + + AddStep("click reset button", () => + { + control.ChildrenOfType() + .First() + .TriggerClick(); + }); + + AddUntilStep("wait for track stopped", () => !Clock.IsRunning); + } + protected override void Dispose(bool isDisposing) { Beatmap.Disabled = false; From 5a0f716bf24b8613564f75eb293e9b76699144fd Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 2 Jun 2022 18:14:26 +0900 Subject: [PATCH 347/395] Fix timing screen crash when attempting to add group to self Closes #18527. --- osu.Game/Screens/Edit/Timing/TimingScreen.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Timing/TimingScreen.cs b/osu.Game/Screens/Edit/Timing/TimingScreen.cs index 40e6e8082a..8b1f881f88 100644 --- a/osu.Game/Screens/Edit/Timing/TimingScreen.cs +++ b/osu.Game/Screens/Edit/Timing/TimingScreen.cs @@ -189,7 +189,7 @@ namespace osu.Game.Screens.Edit.Timing // Try and create matching types from the currently selected control point. var selected = selectedGroup.Value; - if (selected != null) + if (selected != null && selected != group) { foreach (var controlPoint in selected.ControlPoints) { From 0b125ade4c439b1c3a664b683f1040f95cb58efc Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 2 Jun 2022 18:18:18 +0900 Subject: [PATCH 348/395] Allow clicking away to deselect the current control point group --- osu.Game/Screens/Edit/Timing/TimingScreen.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/osu.Game/Screens/Edit/Timing/TimingScreen.cs b/osu.Game/Screens/Edit/Timing/TimingScreen.cs index 40e6e8082a..fabc087952 100644 --- a/osu.Game/Screens/Edit/Timing/TimingScreen.cs +++ b/osu.Game/Screens/Edit/Timing/TimingScreen.cs @@ -7,6 +7,7 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; +using osu.Framework.Input.Events; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterface; @@ -132,6 +133,12 @@ namespace osu.Game.Screens.Edit.Timing }, true); } + protected override bool OnClick(ClickEvent e) + { + selectedGroup.Value = null; + return true; + } + protected override void Update() { base.Update(); From 2fa4d46f73f2e4ec778605acf9b9fc6c69b1fad5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 2 Jun 2022 18:28:13 +0900 Subject: [PATCH 349/395] Change text on timing group add button when it is going to clone instead Also disables the button when it would otherwise have no effect. --- osu.Game/Screens/Edit/Timing/TimingScreen.cs | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Edit/Timing/TimingScreen.cs b/osu.Game/Screens/Edit/Timing/TimingScreen.cs index 40e6e8082a..c6ac859cce 100644 --- a/osu.Game/Screens/Edit/Timing/TimingScreen.cs +++ b/osu.Game/Screens/Edit/Timing/TimingScreen.cs @@ -51,6 +51,8 @@ namespace osu.Game.Screens.Edit.Timing private readonly IBindableList controlPointGroups = new BindableList(); + private RoundedButton addButton; + [Resolved] private EditorClock clock { get; set; } @@ -105,9 +107,8 @@ namespace osu.Game.Screens.Edit.Timing Anchor = Anchor.BottomRight, Origin = Anchor.BottomRight, }, - new RoundedButton + addButton = new RoundedButton { - Text = "+ Add at current time", Action = addNew, Size = new Vector2(160, 30), Anchor = Anchor.BottomRight, @@ -122,7 +123,14 @@ namespace osu.Game.Screens.Edit.Timing { base.LoadComplete(); - selectedGroup.BindValueChanged(selected => { deleteButton.Enabled.Value = selected.NewValue != null; }, true); + selectedGroup.BindValueChanged(selected => + { + deleteButton.Enabled.Value = selected.NewValue != null; + + addButton.Text = selected.NewValue != null + ? "+ Clone to current time" + : "+ Add at current time"; + }, true); controlPointGroups.BindTo(Beatmap.ControlPointInfo.Groups); controlPointGroups.BindCollectionChanged((sender, args) => @@ -137,6 +145,8 @@ namespace osu.Game.Screens.Edit.Timing base.Update(); trackActivePoint(); + + addButton.Enabled.Value = clock.CurrentTimeAccurate != selectedGroup.Value?.Time; } /// From ad3c093a084cf035b2982a4497937fcbdfaa2551 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 2 Jun 2022 18:33:09 +0900 Subject: [PATCH 350/395] Improve group tracking logic to avoid switching which point type unnecessarily --- osu.Game/Screens/Edit/Timing/TimingScreen.cs | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Edit/Timing/TimingScreen.cs b/osu.Game/Screens/Edit/Timing/TimingScreen.cs index 40e6e8082a..aba6b2f23e 100644 --- a/osu.Game/Screens/Edit/Timing/TimingScreen.cs +++ b/osu.Game/Screens/Edit/Timing/TimingScreen.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; @@ -139,6 +140,8 @@ namespace osu.Game.Screens.Edit.Timing trackActivePoint(); } + private Type trackedType; + /// /// Given the user has selected a control point group, we want to track any group which is /// active at the current point in time which matches the type the user has selected. @@ -149,16 +152,27 @@ namespace osu.Game.Screens.Edit.Timing private void trackActivePoint() { // For simplicity only match on the first type of the active control point. - var selectedPointType = selectedGroup.Value?.ControlPoints.FirstOrDefault()?.GetType(); + if (selectedGroup.Value == null) + trackedType = null; + else + { + // If the selected group only has one control point, update the tracking type. + if (selectedGroup.Value.ControlPoints.Count == 1) + trackedType = selectedGroup.Value?.ControlPoints.Single().GetType(); + // If the selected group has more than one control point, choose the first as the tracking type + // if we don't already have a singular tracked type. + else if (trackedType == null) + trackedType = selectedGroup.Value?.ControlPoints.FirstOrDefault()?.GetType(); + } - if (selectedPointType != null) + if (trackedType != null) { // We don't have an efficient way of looking up groups currently, only individual point types. // To improve the efficiency of this in the future, we should reconsider the overall structure of ControlPointInfo. // Find the next group which has the same type as the selected one. var found = Beatmap.ControlPointInfo.Groups - .Where(g => g.ControlPoints.Any(cp => cp.GetType() == selectedPointType)) + .Where(g => g.ControlPoints.Any(cp => cp.GetType() == trackedType)) .LastOrDefault(g => g.Time <= clock.CurrentTimeAccurate); if (found != null) From b1ffffc1acfbf216103b4a683db98800d7f883ca Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 2 Jun 2022 19:16:31 +0900 Subject: [PATCH 351/395] Calculate true combo value in ManiaDifficultyCalculator --- .../Difficulty/ManiaDifficultyCalculator.cs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs b/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs index b17aa7fc4d..88f51bf961 100644 --- a/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs @@ -52,10 +52,18 @@ namespace osu.Game.Rulesets.Mania.Difficulty // This is done the way it is to introduce fractional differences in order to match osu-stable for the time being. GreatHitWindow = Math.Ceiling((int)(getHitWindow300(mods) * clockRate) / clockRate), ScoreMultiplier = getScoreMultiplier(mods), - MaxCombo = beatmap.HitObjects.Sum(h => h is HoldNote ? 2 : 1), + MaxCombo = beatmap.HitObjects.Sum(maxComboForObject) }; } + private static int maxComboForObject(HitObject hitObject) + { + if (hitObject is HoldNote hold) + return 1 + (int)((hold.EndTime - hold.StartTime) / 100); + + return 1; + } + protected override IEnumerable CreateDifficultyHitObjects(IBeatmap beatmap, double clockRate) { var sortedObjects = beatmap.HitObjects.ToArray(); From a287fd73bbc81ca412d7b67fb33783483ed5b964 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 2 Jun 2022 19:16:38 +0900 Subject: [PATCH 352/395] Write MaxCombo attribute for mania --- .../Difficulty/ManiaDifficultyAttributes.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyAttributes.cs b/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyAttributes.cs index 5b7a460079..c35a3dcdc2 100644 --- a/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyAttributes.cs +++ b/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyAttributes.cs @@ -29,7 +29,7 @@ namespace osu.Game.Rulesets.Mania.Difficulty foreach (var v in base.ToDatabaseAttributes()) yield return v; - // Todo: osu!mania doesn't output MaxCombo attribute for some reason. + yield return (ATTRIB_ID_MAX_COMBO, MaxCombo); yield return (ATTRIB_ID_DIFFICULTY, StarRating); yield return (ATTRIB_ID_GREAT_HIT_WINDOW, GreatHitWindow); yield return (ATTRIB_ID_SCORE_MULTIPLIER, ScoreMultiplier); @@ -39,6 +39,7 @@ namespace osu.Game.Rulesets.Mania.Difficulty { base.FromDatabaseAttributes(values); + MaxCombo = (int)values[ATTRIB_ID_MAX_COMBO]; StarRating = values[ATTRIB_ID_DIFFICULTY]; GreatHitWindow = values[ATTRIB_ID_GREAT_HIT_WINDOW]; ScoreMultiplier = values[ATTRIB_ID_SCORE_MULTIPLIER]; From ec24b32fa69a85fccd9df7da892397eb2b90e41b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 2 Jun 2022 20:36:28 +0200 Subject: [PATCH 353/395] Add NRT coverage for `FileChooserLabelledTextBox` --- .../Screens/Edit/Setup/FileChooserLabelledTextBox.cs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Edit/Setup/FileChooserLabelledTextBox.cs b/osu.Game/Screens/Edit/Setup/FileChooserLabelledTextBox.cs index aae19396db..790613fc74 100644 --- a/osu.Game/Screens/Edit/Setup/FileChooserLabelledTextBox.cs +++ b/osu.Game/Screens/Edit/Setup/FileChooserLabelledTextBox.cs @@ -1,6 +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 enable + using System; using System.Collections.Generic; using System.IO; @@ -27,10 +29,10 @@ namespace osu.Game.Screens.Edit.Setup public IEnumerable HandledExtensions => handledExtensions; - private readonly Bindable currentFile = new Bindable(); + private readonly Bindable currentFile = new Bindable(); [Resolved] - private OsuGameBase game { get; set; } + private OsuGameBase game { get; set; } = null!; public FileChooserLabelledTextBox(params string[] handledExtensions) { @@ -45,7 +47,7 @@ namespace osu.Game.Screens.Edit.Setup currentFile.BindValueChanged(onFileSelected); } - private void onFileSelected(ValueChangedEvent file) + private void onFileSelected(ValueChangedEvent file) { if (file.NewValue == null) return; @@ -72,7 +74,7 @@ namespace osu.Game.Screens.Edit.Setup private class FileChooserPopover : OsuPopover { - public FileChooserPopover(string[] handledExtensions, Bindable currentFile) + public FileChooserPopover(string[] handledExtensions, Bindable currentFile) { Child = new Container { From cf9b78ea2a11f1c44d90bfa1702ad3daad4d4173 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 2 Jun 2022 20:38:14 +0200 Subject: [PATCH 354/395] Improve safety of `FileChooserLabelledTextBox` disposal --- osu.Game/Screens/Edit/Setup/FileChooserLabelledTextBox.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Setup/FileChooserLabelledTextBox.cs b/osu.Game/Screens/Edit/Setup/FileChooserLabelledTextBox.cs index 790613fc74..fd916894ea 100644 --- a/osu.Game/Screens/Edit/Setup/FileChooserLabelledTextBox.cs +++ b/osu.Game/Screens/Edit/Setup/FileChooserLabelledTextBox.cs @@ -11,6 +11,7 @@ using System.Threading.Tasks; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions; +using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.UserInterface; @@ -67,7 +68,9 @@ namespace osu.Game.Screens.Edit.Setup protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); - game.UnregisterImportHandler(this); + + if (game.IsNotNull()) + game.UnregisterImportHandler(this); } public override Popover GetPopover() => new FileChooserPopover(handledExtensions, currentFile); From 60fb5d5e6c721b6a975356524869a4785bee2f70 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 3 Jun 2022 02:03:04 +0300 Subject: [PATCH 355/395] Revert "Fix timeline objects disappearing prematurely on wide-screens" This reverts commit 02baf9a97abe0fb6e94af53b83708a15a508d437. --- osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs index 733b6c5f24..89e9fb2404 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs @@ -304,7 +304,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline /// /// The total amount of time visible on the timeline. /// - public double VisibleRange => (DisplayableContent / Content.DrawWidth) * track.Length; + public double VisibleRange => track.Length / Zoom; public SnapResult FindSnappedPositionAndTime(Vector2 screenSpacePosition, SnapType snapType = SnapType.All) => new SnapResult(screenSpacePosition, beatSnapProvider.SnapTime(getTimeFromPosition(Content.ToLocalSpace(screenSpacePosition)))); From ca68751fdcd1c3b0a01ead44e776600f0127e05b Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 3 Jun 2022 02:22:16 +0300 Subject: [PATCH 356/395] Update test to match expectation --- osu.Game.Tests/Visual/Editing/TestSceneTimelineZoom.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneTimelineZoom.cs b/osu.Game.Tests/Visual/Editing/TestSceneTimelineZoom.cs index 10f1f2fceb..d726bd004e 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneTimelineZoom.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneTimelineZoom.cs @@ -12,7 +12,7 @@ namespace osu.Game.Tests.Visual.Editing public override Drawable CreateTestComponent() => Empty(); [Test] - public void TestVisibleRangeViaZoom() + public void TestVisibleRangeUpdatesOnZoomChange() { double initialVisibleRange = 0; @@ -29,7 +29,7 @@ namespace osu.Game.Tests.Visual.Editing } [Test] - public void TestVisibleRangeViaTimelineSize() + public void TestVisibleRangeConstantOnSizeChange() { double initialVisibleRange = 0; @@ -37,12 +37,12 @@ namespace osu.Game.Tests.Visual.Editing AddStep("get initial range", () => initialVisibleRange = TimelineArea.Timeline.VisibleRange); AddStep("scale timeline size", () => TimelineArea.Timeline.Width = 2); - AddAssert("range doubled", () => TimelineArea.Timeline.VisibleRange == initialVisibleRange * 2); + AddAssert("same range", () => TimelineArea.Timeline.VisibleRange == initialVisibleRange); AddStep("descale timeline size", () => TimelineArea.Timeline.Width = 0.5f); - AddAssert("range halved", () => TimelineArea.Timeline.VisibleRange == initialVisibleRange / 2); + AddAssert("same range", () => TimelineArea.Timeline.VisibleRange == initialVisibleRange); AddStep("restore timeline size", () => TimelineArea.Timeline.Width = 1); - AddAssert("range restored", () => TimelineArea.Timeline.VisibleRange == initialVisibleRange); + AddAssert("same range", () => TimelineArea.Timeline.VisibleRange == initialVisibleRange); } } } From 588151f48b3afde6d8e006daaa235367d5583a7d Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 3 Jun 2022 02:26:46 +0300 Subject: [PATCH 357/395] Add new failing test coverage --- .../Editing/TestSceneZoomableScrollContainer.cs | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneZoomableScrollContainer.cs b/osu.Game.Tests/Visual/Editing/TestSceneZoomableScrollContainer.cs index 95d11d6909..2d056bafdd 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneZoomableScrollContainer.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneZoomableScrollContainer.cs @@ -44,7 +44,12 @@ namespace osu.Game.Tests.Visual.Editing RelativeSizeAxes = Axes.Both, Colour = OsuColour.Gray(30) }, - scrollContainer = new ZoomableScrollContainer { RelativeSizeAxes = Axes.Both } + scrollContainer = new ZoomableScrollContainer + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + } } }, new MenuCursor() @@ -62,7 +67,15 @@ namespace osu.Game.Tests.Visual.Editing [Test] public void TestWidthInitialization() { - AddAssert("Inner container width was initialized", () => innerBox.DrawWidth > 0); + AddAssert("Inner container width was initialized", () => innerBox.DrawWidth == scrollContainer.DrawWidth); + } + + [Test] + public void TestWidthUpdatesOnDrawSizeChanges() + { + AddStep("Shrink scroll container", () => scrollContainer.Width = 0.5f); + AddAssert("Scroll container width shrunk", () => scrollContainer.DrawWidth == scrollContainer.Parent.DrawWidth / 2); + AddAssert("Inner container width matches scroll container", () => innerBox.DrawWidth == scrollContainer.DrawWidth); } [Test] From 21385655fed9137e350811f66b5ff9ae418f0ada Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 3 Jun 2022 02:27:16 +0300 Subject: [PATCH 358/395] Fix `ZoomableScrollContainer` not updating on parent size changes --- .../Timeline/ZoomableScrollContainer.cs | 21 +++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs index 35d103ddf1..d008368b69 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs @@ -7,6 +7,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Transforms; using osu.Framework.Input.Events; +using osu.Framework.Layout; using osu.Framework.Timing; using osu.Framework.Utils; using osu.Game.Graphics.Containers; @@ -40,10 +41,14 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline [Resolved(canBeNull: true)] private IFrameBasedClock editorClock { get; set; } + private readonly LayoutValue zoomedContentWidthCache = new LayoutValue(Invalidation.DrawSize); + public ZoomableScrollContainer() : base(Direction.Horizontal) { base.Content.Add(zoomedContent = new Container { RelativeSizeAxes = Axes.Y }); + + AddLayout(zoomedContentWidthCache); } private float minZoom = 1; @@ -103,12 +108,12 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline } } - protected override void LoadComplete() + protected override void Update() { - base.LoadComplete(); + base.Update(); - // This width only gets updated on the application of a transform, so this needs to be initialized here. - updateZoomedContentWidth(); + if (!zoomedContentWidthCache.IsValid) + updateZoomedContentWidth(); } protected override bool OnScroll(ScrollEvent e) @@ -128,7 +133,11 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline return base.OnScroll(e); } - private void updateZoomedContentWidth() => zoomedContent.Width = DrawWidth * currentZoom; + private void updateZoomedContentWidth() + { + zoomedContent.Width = DrawWidth * currentZoom; + zoomedContentWidthCache.Validate(); + } private float zoomTarget = 1; @@ -199,8 +208,8 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline float targetOffset = expectedWidth * (focusPoint / contentSize) - focusOffset; d.currentZoom = newZoom; - d.updateZoomedContentWidth(); + // Temporarily here to make sure ScrollTo gets the correct DrawSize for scrollable area. // TODO: Make sure draw size gets invalidated properly on the framework side, and remove this once it is. d.Invalidate(Invalidation.DrawSize); From efbde06c11229a3a8501ece576b1a10c13cc70ae Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 3 Jun 2022 05:02:00 +0300 Subject: [PATCH 359/395] Split button repeating logic from `TimingAdjustButton` to own component --- .../Edit/Timing/RepeatingButtonBehaviour.cs | 93 +++++++++++++++++++ .../Screens/Edit/Timing/TimingAdjustButton.cs | 80 ++++------------ 2 files changed, 110 insertions(+), 63 deletions(-) create mode 100644 osu.Game/Screens/Edit/Timing/RepeatingButtonBehaviour.cs diff --git a/osu.Game/Screens/Edit/Timing/RepeatingButtonBehaviour.cs b/osu.Game/Screens/Edit/Timing/RepeatingButtonBehaviour.cs new file mode 100644 index 0000000000..5c9f384a2e --- /dev/null +++ b/osu.Game/Screens/Edit/Timing/RepeatingButtonBehaviour.cs @@ -0,0 +1,93 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using osu.Framework.Allocation; +using osu.Framework.Audio; +using osu.Framework.Audio.Sample; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Input.Events; +using osu.Framework.Threading; + +namespace osu.Game.Screens.Edit.Timing +{ + /// + /// Represents a component that provides the behaviour of triggering button clicks repeatedly while holding with mouse. + /// + public class RepeatingButtonBehaviour : CompositeDrawable + { + private const double initial_delay = 300; + private const double minimum_delay = 80; + + private readonly Drawable button; + + private Sample sample; + + /// + /// An additive modifier for the frequency of the sample played on next actuation. + /// This can be adjusted during the button's event to affect the repeat sample playback of that click. + /// + public double SampleFrequencyModifier { get; set; } + + public RepeatingButtonBehaviour(Drawable button) + { + this.button = button; + + RelativeSizeAxes = Axes.Both; + } + + [BackgroundDependencyLoader] + private void load(AudioManager audio) + { + sample = audio.Samples.Get(@"UI/notch-tick"); + } + + protected override bool OnMouseDown(MouseDownEvent e) + { + beginRepeat(); + return true; + } + + protected override void OnMouseUp(MouseUpEvent e) + { + adjustDelegate?.Cancel(); + base.OnMouseUp(e); + } + + private ScheduledDelegate adjustDelegate; + private double adjustDelay = initial_delay; + + private void beginRepeat() + { + adjustDelegate?.Cancel(); + + adjustDelay = initial_delay; + adjustNext(); + + void adjustNext() + { + if (IsHovered) + { + button.TriggerClick(); + adjustDelay = Math.Max(minimum_delay, adjustDelay * 0.9f); + + var channel = sample?.GetChannel(); + + if (channel != null) + { + double repeatModifier = 0.05f * (Math.Abs(adjustDelay - initial_delay) / minimum_delay); + channel.Frequency.Value = 1 + repeatModifier + SampleFrequencyModifier; + channel.Play(); + } + } + else + { + adjustDelay = initial_delay; + } + + adjustDelegate = Scheduler.AddDelayed(adjustNext, adjustDelay); + } + } + } +} diff --git a/osu.Game/Screens/Edit/Timing/TimingAdjustButton.cs b/osu.Game/Screens/Edit/Timing/TimingAdjustButton.cs index 9fc7e56a3d..6e7338fa15 100644 --- a/osu.Game/Screens/Edit/Timing/TimingAdjustButton.cs +++ b/osu.Game/Screens/Edit/Timing/TimingAdjustButton.cs @@ -4,14 +4,11 @@ using System; using System.Linq; using osu.Framework.Allocation; -using osu.Framework.Audio; -using osu.Framework.Audio.Sample; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Events; using osu.Framework.Localisation; -using osu.Framework.Threading; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Overlays; @@ -26,32 +23,24 @@ namespace osu.Game.Screens.Edit.Timing public Action Action; private readonly double adjustAmount; - private ScheduledDelegate adjustDelegate; private const int max_multiplier = 10; - private const int adjust_levels = 4; - private const double initial_delay = 300; - - private const double minimum_delay = 80; - public Container Content { get; set; } - private double adjustDelay = initial_delay; - private readonly Box background; private readonly OsuSpriteText text; - private Sample sample; - public LocalisableString Text { get => text.Text; set => text.Text = value; } + private readonly RepeatingButtonBehaviour repeatBehaviour; + [Resolved] private OverlayColourProvider colourProvider { get; set; } @@ -82,13 +71,13 @@ namespace osu.Game.Screens.Edit.Timing } } }); + + AddInternal(repeatBehaviour = new RepeatingButtonBehaviour(this)); } [BackgroundDependencyLoader] - private void load(AudioManager audio) + private void load() { - sample = audio.Samples.Get(@"UI/notch-tick"); - background.Colour = colourProvider.Background3; for (int i = 1; i <= adjust_levels; i++) @@ -98,57 +87,22 @@ namespace osu.Game.Screens.Edit.Timing } } - protected override bool OnMouseDown(MouseDownEvent e) + protected override bool OnHover(HoverEvent e) => true; + + protected override bool OnClick(ClickEvent e) { - beginRepeat(); + var hoveredBox = Content.OfType().FirstOrDefault(d => d.IsHovered); + if (hoveredBox == null) + return false; + + Action(adjustAmount * hoveredBox.Multiplier); + + hoveredBox.Flash(); + + repeatBehaviour.SampleFrequencyModifier = (hoveredBox.Multiplier / max_multiplier) * 0.2; return true; } - protected override void OnMouseUp(MouseUpEvent e) - { - adjustDelegate?.Cancel(); - base.OnMouseUp(e); - } - - private void beginRepeat() - { - adjustDelegate?.Cancel(); - - adjustDelay = initial_delay; - adjustNext(); - - void adjustNext() - { - var hoveredBox = Content.OfType().FirstOrDefault(d => d.IsHovered); - - if (hoveredBox != null) - { - Action(adjustAmount * hoveredBox.Multiplier); - - adjustDelay = Math.Max(minimum_delay, adjustDelay * 0.9f); - - hoveredBox.Flash(); - - var channel = sample?.GetChannel(); - - if (channel != null) - { - double repeatModifier = 0.05f * (Math.Abs(adjustDelay - initial_delay) / minimum_delay); - double multiplierModifier = (hoveredBox.Multiplier / max_multiplier) * 0.2f; - - channel.Frequency.Value = 1 + multiplierModifier + repeatModifier; - channel.Play(); - } - } - else - { - adjustDelay = initial_delay; - } - - adjustDelegate = Scheduler.AddDelayed(adjustNext, adjustDelay); - } - } - private class IncrementBox : CompositeDrawable { public readonly float Multiplier; From b51e0a50472e2415928a38dd9263bdfa04e3e8d5 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 3 Jun 2022 05:03:22 +0300 Subject: [PATCH 360/395] Share button repeating logic with `TimelineButton` for better UX --- .../Components/Timeline/TimelineButton.cs | 47 ++----------------- 1 file changed, 5 insertions(+), 42 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineButton.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineButton.cs index 5550c6a748..4c0c0335dd 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineButton.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineButton.cs @@ -6,13 +6,11 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; -using osu.Framework.Input.Events; -using osu.Framework.Threading; using osu.Game.Graphics; using osu.Game.Graphics.UserInterface; +using osu.Game.Screens.Edit.Timing; using osuTK; using osuTK.Graphics; -using osuTK.Input; namespace osu.Game.Screens.Edit.Compose.Components.Timeline { @@ -27,7 +25,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline set => button.Icon = value; } - private readonly IconButton button; + private readonly TimelineIconButton button; public TimelineButton() { @@ -54,46 +52,11 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline IconHoverColour = Color4.White; HoverColour = OsuColour.Gray(0.25f); FlashColour = OsuColour.Gray(0.5f); + + Add(new RepeatingButtonBehaviour(this)); } - private ScheduledDelegate repeatSchedule; - - /// - /// The initial delay before mouse down repeat begins. - /// - private const int repeat_initial_delay = 250; - - /// - /// The delay between mouse down repeats after the initial repeat. - /// - private const int repeat_tick_rate = 70; - - protected override bool OnClick(ClickEvent e) - { - // don't actuate a click since we are manually handling repeats. - return true; - } - - protected override bool OnMouseDown(MouseDownEvent e) - { - if (e.Button == MouseButton.Left) - { - Action clickAction = () => base.OnClick(new ClickEvent(e.CurrentState, e.Button)); - - // run once for initial down - clickAction(); - - Scheduler.Add(repeatSchedule = new ScheduledDelegate(clickAction, Clock.CurrentTime + repeat_initial_delay, repeat_tick_rate)); - } - - return base.OnMouseDown(e); - } - - protected override void OnMouseUp(MouseUpEvent e) - { - repeatSchedule?.Cancel(); - base.OnMouseUp(e); - } + protected override HoverSounds CreateHoverSounds(HoverSampleSet sampleSet) => new HoverSounds(sampleSet); } } } From 1b4c89c418a66aecadac765be1560f1ad3d4a215 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 3 Jun 2022 14:01:04 +0900 Subject: [PATCH 361/395] Update realm to latest version Contains minor changes to async usage in line with upstream API changes. I believe a feedback issue we were seeing with offset changes (the only component using async write flow) may have been resolved by these upstream changes (see [release notes](https://github.com/realm/realm-dotnet/releases/tag/10.14.0)) but am not investigating further just yet. --- osu.Game.Tests/Database/BeatmapImporterTests.cs | 2 +- osu.Game/Database/RealmAccess.cs | 2 +- osu.Game/osu.Game.csproj | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Database/BeatmapImporterTests.cs b/osu.Game.Tests/Database/BeatmapImporterTests.cs index 00276955aa..d4956e97e0 100644 --- a/osu.Game.Tests/Database/BeatmapImporterTests.cs +++ b/osu.Game.Tests/Database/BeatmapImporterTests.cs @@ -710,7 +710,7 @@ namespace osu.Game.Tests.Database var imported = await LoadOszIntoStore(importer, realm.Realm); - realm.Realm.Write(() => + await realm.Realm.WriteAsync(() => { foreach (var b in imported.Beatmaps) b.OnlineID = -1; diff --git a/osu.Game/Database/RealmAccess.cs b/osu.Game/Database/RealmAccess.cs index dbd3b96763..086ec52d80 100644 --- a/osu.Game/Database/RealmAccess.cs +++ b/osu.Game/Database/RealmAccess.cs @@ -392,7 +392,7 @@ namespace osu.Game.Database { total_writes_async.Value++; using (var realm = getRealmInstance()) - await realm.WriteAsync(action); + await realm.WriteAsync(() => action(realm)); } /// diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index eb47d0468f..97a36e9bc6 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -35,7 +35,7 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - + From f224a3e922d5d8953b5c1446bb3aad4d0d11d363 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 3 Jun 2022 14:18:47 +0900 Subject: [PATCH 362/395] Update silly mobile project references --- osu.Android.props | 2 +- osu.iOS.props | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index 116c7dbfcd..35d902493e 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -56,6 +56,6 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index ccecad6f82..64ba7fe22e 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -89,6 +89,6 @@ - + From 3aa8bc933dc45f8c5428e0f8a5e9a73aa51e651f Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 3 Jun 2022 08:21:35 +0300 Subject: [PATCH 363/395] Add sentry tag for selected beatmap --- osu.Game/Utils/SentryLogger.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Utils/SentryLogger.cs b/osu.Game/Utils/SentryLogger.cs index ecc7fc4774..bba1e514c5 100644 --- a/osu.Game/Utils/SentryLogger.cs +++ b/osu.Game/Utils/SentryLogger.cs @@ -159,6 +159,7 @@ namespace osu.Game.Utils Game = game.Clock.CurrentTime, }; + scope.SetTag(@"beatmap", $"{beatmap.OnlineID}"); scope.SetTag(@"ruleset", ruleset.ShortName); scope.SetTag(@"os", $"{RuntimeInfo.OS} ({Environment.OSVersion})"); scope.SetTag(@"processor count", Environment.ProcessorCount.ToString()); From 6cb8b2d6f47f64591d9aa3e6768a68f23c582a1e Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 3 Jun 2022 09:37:17 +0300 Subject: [PATCH 364/395] Set default window mode to "Borderless" rather than "Fullscreen" on macOS --- osu.Game/OsuGame.cs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 785881d97a..1c25c45bc3 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -9,6 +9,7 @@ using System.Threading; using System.Threading.Tasks; using Humanizer; using JetBrains.Annotations; +using osu.Framework; using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Bindables; @@ -658,11 +659,14 @@ namespace osu.Game } protected override IDictionary GetFrameworkConfigDefaults() - => new Dictionary + { + return new Dictionary { - // General expectation that osu! starts in fullscreen by default (also gives the most predictable performance) - { FrameworkSetting.WindowMode, WindowMode.Fullscreen } + // General expectation that osu! starts in fullscreen by default (also gives the most predictable performance). + // However, macOS is bound to have issues when using exclusive fullscreen as it takes full control away from OS, therefore borderless is default there. + { FrameworkSetting.WindowMode, RuntimeInfo.IsApple ? WindowMode.Borderless : WindowMode.Fullscreen } }; + } protected override void LoadComplete() { From 1af51a2b1919a39bb90d63b1edfd1e29f6f18dad Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 3 Jun 2022 16:00:40 +0900 Subject: [PATCH 365/395] Update framework --- osu.Android.props | 2 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index 35d902493e..d5f0d32b80 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -52,7 +52,7 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 97a36e9bc6..45e7ff91c0 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -36,7 +36,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/osu.iOS.props b/osu.iOS.props index 64ba7fe22e..16b7d49f3f 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -61,7 +61,7 @@ - + @@ -84,7 +84,7 @@ - + From eb2d822530227d303d2d10f661a2a544c3f1a3ae Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 2 Jun 2022 14:28:48 +0900 Subject: [PATCH 366/395] Fix web requests potentially being performed after cancelled Closes https://github.com/ppy/osu/issues/18524. --- osu.Game/Online/API/APIRequest.cs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/osu.Game/Online/API/APIRequest.cs b/osu.Game/Online/API/APIRequest.cs index 776ff5fd8f..451ea117d5 100644 --- a/osu.Game/Online/API/APIRequest.cs +++ b/osu.Game/Online/API/APIRequest.cs @@ -121,8 +121,16 @@ namespace osu.Game.Online.API if (isFailing) return; - Logger.Log($@"Performing request {this}", LoggingTarget.Network); - WebRequest.Perform(); + try + { + Logger.Log($@"Performing request {this}", LoggingTarget.Network); + WebRequest.Perform(); + } + catch (OperationCanceledException) + { + // ignore this. internally Perform is running async and the fail state may have changed since + // the last check of `isFailing` above. + } if (isFailing) return; From 54a32bde44fd1fdc421a47fbd2b9cc89978d5703 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 3 Jun 2022 16:29:55 +0900 Subject: [PATCH 367/395] Don't report sentry errors from builds targetting a different server --- osu.Game/OsuGameBase.cs | 5 ++++- osu.Game/Utils/SentryLogger.cs | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 666413004a..5dbdf6f602 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -80,6 +80,9 @@ namespace osu.Game public virtual bool UseDevelopmentServer => DebugUtils.IsDebugBuild; + internal EndpointConfiguration CreateEndpoints() => + UseDevelopmentServer ? (EndpointConfiguration)new DevelopmentEndpointConfiguration() : new ProductionEndpointConfiguration(); + public virtual Version AssemblyVersion => Assembly.GetEntryAssembly()?.GetName().Version ?? new Version(); /// @@ -268,7 +271,7 @@ namespace osu.Game dependencies.Cache(SkinManager = new SkinManager(Storage, realm, Host, Resources, Audio, Scheduler)); dependencies.CacheAs(SkinManager); - EndpointConfiguration endpoints = UseDevelopmentServer ? (EndpointConfiguration)new DevelopmentEndpointConfiguration() : new ProductionEndpointConfiguration(); + EndpointConfiguration endpoints = CreateEndpoints(); MessageFormatter.WebsiteRootUrl = endpoints.WebsiteRootUrl; diff --git a/osu.Game/Utils/SentryLogger.cs b/osu.Game/Utils/SentryLogger.cs index bba1e514c5..c12fd607b4 100644 --- a/osu.Game/Utils/SentryLogger.cs +++ b/osu.Game/Utils/SentryLogger.cs @@ -43,7 +43,7 @@ namespace osu.Game.Utils sentrySession = SentrySdk.Init(options => { // Not setting the dsn will completely disable sentry. - if (game.IsDeployedBuild) + if (game.IsDeployedBuild && game.CreateEndpoints().WebsiteRootUrl.EndsWith(@".ppy.sh", StringComparison.Ordinal)) options.Dsn = "https://ad9f78529cef40ac874afb95a9aca04e@sentry.ppy.sh/2"; options.AutoSessionTracking = true; From 1a835f062213c8d86fddaa73e30f33f75245996d Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 3 Jun 2022 10:23:38 +0300 Subject: [PATCH 368/395] Add warning note when running fullscreen on macOS --- osu.Game/Localisation/LayoutSettingsStrings.cs | 5 +++++ .../Overlays/Settings/Sections/Graphics/LayoutSettings.cs | 7 +++++++ 2 files changed, 12 insertions(+) diff --git a/osu.Game/Localisation/LayoutSettingsStrings.cs b/osu.Game/Localisation/LayoutSettingsStrings.cs index 5ac28f19b3..8b7ad5918b 100644 --- a/osu.Game/Localisation/LayoutSettingsStrings.cs +++ b/osu.Game/Localisation/LayoutSettingsStrings.cs @@ -24,6 +24,11 @@ namespace osu.Game.Localisation /// public static LocalisableString UnableToRunExclusiveFullscreen => new TranslatableString(getKey(@"unable_to_run_exclusive_fullscreen"), @"Unable to run exclusive fullscreen. You'll still experience some input latency."); + /// + /// "Running fullscreen on macOS takes full control away from the system, this could lead to system-wide freezes if the game becomes unresponsive." + /// + public static LocalisableString FullscreenMacOSNote => new TranslatableString(getKey(@"fullscreen_macos_note"), @"Running fullscreen on macOS takes full control away from the system, this could lead to system-wide freezes if the game becomes unresponsive."); + private static string getKey(string key) => $@"{prefix}:{key}"; } } \ No newline at end of file diff --git a/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs b/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs index d79ba593f7..53b6f89c1f 100644 --- a/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs @@ -4,6 +4,7 @@ using System; using System.Drawing; using System.Linq; +using osu.Framework; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Configuration; @@ -230,6 +231,12 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics return; } + if (RuntimeInfo.OS == RuntimeInfo.Platform.macOS) + { + windowModeDropdown.SetNoticeText(LayoutSettingsStrings.FullscreenMacOSNote, true); + return; + } + if (host.Window is WindowsWindow) { switch (fullscreenCapability.Value) From 3ad1180c48c1856a5f10640d4688e4c2be25b245 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 3 Jun 2022 10:32:43 +0300 Subject: [PATCH 369/395] Use `macOS` instead of `IsApple` for better safety --- 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 1c25c45bc3..b91d523151 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -664,7 +664,7 @@ namespace osu.Game { // General expectation that osu! starts in fullscreen by default (also gives the most predictable performance). // However, macOS is bound to have issues when using exclusive fullscreen as it takes full control away from OS, therefore borderless is default there. - { FrameworkSetting.WindowMode, RuntimeInfo.IsApple ? WindowMode.Borderless : WindowMode.Fullscreen } + { FrameworkSetting.WindowMode, RuntimeInfo.OS == RuntimeInfo.Platform.macOS ? WindowMode.Borderless : WindowMode.Fullscreen } }; } From e779b460e42cb89f4a196035164dda9f4cae9dd5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 3 Jun 2022 16:49:08 +0900 Subject: [PATCH 370/395] Use `Component` instead of `CompositeDrawable` --- osu.Game/Screens/Edit/Timing/RepeatingButtonBehaviour.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game/Screens/Edit/Timing/RepeatingButtonBehaviour.cs b/osu.Game/Screens/Edit/Timing/RepeatingButtonBehaviour.cs index 5c9f384a2e..595305b20f 100644 --- a/osu.Game/Screens/Edit/Timing/RepeatingButtonBehaviour.cs +++ b/osu.Game/Screens/Edit/Timing/RepeatingButtonBehaviour.cs @@ -6,7 +6,6 @@ using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Audio.Sample; using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; using osu.Framework.Input.Events; using osu.Framework.Threading; @@ -15,7 +14,7 @@ namespace osu.Game.Screens.Edit.Timing /// /// Represents a component that provides the behaviour of triggering button clicks repeatedly while holding with mouse. /// - public class RepeatingButtonBehaviour : CompositeDrawable + public class RepeatingButtonBehaviour : Component { private const double initial_delay = 300; private const double minimum_delay = 80; From 902a0a3255d2773988e74eaa27dc9a061c79b56d Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 3 Jun 2022 16:50:42 +0900 Subject: [PATCH 371/395] Update max combo test value --- .../ManiaDifficultyCalculatorTest.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Mania.Tests/ManiaDifficultyCalculatorTest.cs b/osu.Game.Rulesets.Mania.Tests/ManiaDifficultyCalculatorTest.cs index 715614a201..a5bd126782 100644 --- a/osu.Game.Rulesets.Mania.Tests/ManiaDifficultyCalculatorTest.cs +++ b/osu.Game.Rulesets.Mania.Tests/ManiaDifficultyCalculatorTest.cs @@ -14,11 +14,11 @@ namespace osu.Game.Rulesets.Mania.Tests { protected override string ResourceAssembly => "osu.Game.Rulesets.Mania"; - [TestCase(2.3449735700206298d, 151, "diffcalc-test")] + [TestCase(2.3449735700206298d, 242, "diffcalc-test")] public void Test(double expectedStarRating, int expectedMaxCombo, string name) => base.Test(expectedStarRating, expectedMaxCombo, name); - [TestCase(2.7879104989252959d, 151, "diffcalc-test")] + [TestCase(2.7879104989252959d, 242, "diffcalc-test")] public void TestClockRateAdjusted(double expectedStarRating, int expectedMaxCombo, string name) => Test(expectedStarRating, expectedMaxCombo, name, new ManiaModDoubleTime()); From 7b28451e3d872d03fac0411e189da2642a0426a2 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 3 Jun 2022 10:50:52 +0300 Subject: [PATCH 372/395] Improve warning message for user --- osu.Game/Localisation/LayoutSettingsStrings.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Localisation/LayoutSettingsStrings.cs b/osu.Game/Localisation/LayoutSettingsStrings.cs index 8b7ad5918b..b4326b8e39 100644 --- a/osu.Game/Localisation/LayoutSettingsStrings.cs +++ b/osu.Game/Localisation/LayoutSettingsStrings.cs @@ -25,10 +25,10 @@ namespace osu.Game.Localisation public static LocalisableString UnableToRunExclusiveFullscreen => new TranslatableString(getKey(@"unable_to_run_exclusive_fullscreen"), @"Unable to run exclusive fullscreen. You'll still experience some input latency."); /// - /// "Running fullscreen on macOS takes full control away from the system, this could lead to system-wide freezes if the game becomes unresponsive." + /// "Using fullscreen on macOS makes interacting with the menu bar and spaces no longer work, and may lead to freezes if a system dialog is presented. Using borderless is recommended." /// - public static LocalisableString FullscreenMacOSNote => new TranslatableString(getKey(@"fullscreen_macos_note"), @"Running fullscreen on macOS takes full control away from the system, this could lead to system-wide freezes if the game becomes unresponsive."); + public static LocalisableString FullscreenMacOSNote => new TranslatableString(getKey(@"fullscreen_macos_note"), @"Using fullscreen on macOS makes interacting with the menu bar and spaces no longer work, and may lead to freezes if a system dialog is presented. Using borderless is recommended."); private static string getKey(string key) => $@"{prefix}:{key}"; } -} \ No newline at end of file +} From cd999cf7ac936756f055fe57e88b20d6af6c47ed Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 3 Jun 2022 10:51:20 +0300 Subject: [PATCH 373/395] Fix back-to-front conditional --- .../Settings/Sections/Graphics/LayoutSettings.cs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs b/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs index 53b6f89c1f..05890ad882 100644 --- a/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs @@ -225,15 +225,19 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics private void updateScreenModeWarning() { - if (windowModeDropdown.Current.Value != WindowMode.Fullscreen) + if (RuntimeInfo.OS == RuntimeInfo.Platform.macOS) { - windowModeDropdown.SetNoticeText(GraphicsSettingsStrings.NotFullscreenNote, true); + if (windowModeDropdown.Current.Value == WindowMode.Fullscreen) + windowModeDropdown.SetNoticeText(LayoutSettingsStrings.FullscreenMacOSNote, true); + else + windowModeDropdown.ClearNoticeText(); + return; } - if (RuntimeInfo.OS == RuntimeInfo.Platform.macOS) + if (windowModeDropdown.Current.Value != WindowMode.Fullscreen) { - windowModeDropdown.SetNoticeText(LayoutSettingsStrings.FullscreenMacOSNote, true); + windowModeDropdown.SetNoticeText(GraphicsSettingsStrings.NotFullscreenNote, true); return; } From cd3edc869c3455fd77aa0348566faccd079a1b97 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 3 Jun 2022 17:00:00 +0900 Subject: [PATCH 374/395] Remove unnecessary nesting of `IconButton` and update design a touch --- .../Components/Timeline/TimelineButton.cs | 63 +++++-------------- 1 file changed, 15 insertions(+), 48 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineButton.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineButton.cs index 4c0c0335dd..e0b21b2e22 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineButton.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineButton.cs @@ -1,62 +1,29 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; -using osu.Framework.Bindables; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Sprites; -using osu.Game.Graphics; +using osu.Framework.Allocation; using osu.Game.Graphics.UserInterface; +using osu.Game.Overlays; using osu.Game.Screens.Edit.Timing; -using osuTK; -using osuTK.Graphics; namespace osu.Game.Screens.Edit.Compose.Components.Timeline { - public class TimelineButton : CompositeDrawable + public class TimelineButton : IconButton { - public Action Action; - public readonly BindableBool Enabled = new BindableBool(true); - - public IconUsage Icon + [BackgroundDependencyLoader] + private void load(OverlayColourProvider colourProvider) { - get => button.Icon; - set => button.Icon = value; + // These are using colourProvider but don't match the design. + // Just something to fit until someone implements the updated design. + IconColour = colourProvider.Background1; + IconHoverColour = colourProvider.Content2; + + HoverColour = colourProvider.Background1; + FlashColour = colourProvider.Content2; + + Add(new RepeatingButtonBehaviour(this)); } - private readonly TimelineIconButton button; - - public TimelineButton() - { - InternalChild = button = new TimelineIconButton { Action = () => Action?.Invoke() }; - - button.Enabled.BindTo(Enabled); - Width = button.Width; - } - - protected override void Update() - { - base.Update(); - - button.Size = new Vector2(button.Width, DrawHeight); - } - - private class TimelineIconButton : IconButton - { - public TimelineIconButton() - { - Anchor = Anchor.Centre; - Origin = Anchor.Centre; - IconColour = OsuColour.Gray(0.35f); - IconHoverColour = Color4.White; - HoverColour = OsuColour.Gray(0.25f); - FlashColour = OsuColour.Gray(0.5f); - - Add(new RepeatingButtonBehaviour(this)); - } - - protected override HoverSounds CreateHoverSounds(HoverSampleSet sampleSet) => new HoverSounds(sampleSet); - } + protected override HoverSounds CreateHoverSounds(HoverSampleSet sampleSet) => new HoverSounds(sampleSet); } } From 2aabcf51ae9271d51ed97d6b425115d31d8bea9d Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 3 Jun 2022 18:47:07 +0900 Subject: [PATCH 375/395] Update framework again --- osu.Android.props | 2 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index d5f0d32b80..0058bb3577 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -52,7 +52,7 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 45e7ff91c0..2285f61331 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -36,7 +36,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/osu.iOS.props b/osu.iOS.props index 16b7d49f3f..b134a7e356 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -61,7 +61,7 @@ - + @@ -84,7 +84,7 @@ - + From 646f5f0f33c47efb4fcfd6656bc470209ff538c3 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 3 Jun 2022 19:17:31 +0900 Subject: [PATCH 376/395] Isolate "server-side" multiplayer rooms in testing --- osu.Game/Online/Rooms/Room.cs | 7 +++++++ .../Tests/Visual/OnlinePlay/TestRoomRequestsHandler.cs | 10 +++++----- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/osu.Game/Online/Rooms/Room.cs b/osu.Game/Online/Rooms/Room.cs index 60c0503ddd..1933267efd 100644 --- a/osu.Game/Online/Rooms/Room.cs +++ b/osu.Game/Online/Rooms/Room.cs @@ -162,6 +162,13 @@ namespace osu.Game.Online.Rooms Password.BindValueChanged(p => HasPassword.Value = !string.IsNullOrEmpty(p.NewValue)); } + /// + /// Copies values from another into this one. + /// + /// + /// **Beware**: This will store references between s. + /// + /// The to copy values from. public void CopyFrom(Room other) { RoomID.Value = other.RoomID.Value; diff --git a/osu.Game/Tests/Visual/OnlinePlay/TestRoomRequestsHandler.cs b/osu.Game/Tests/Visual/OnlinePlay/TestRoomRequestsHandler.cs index 8290af8f78..392f32badc 100644 --- a/osu.Game/Tests/Visual/OnlinePlay/TestRoomRequestsHandler.cs +++ b/osu.Game/Tests/Visual/OnlinePlay/TestRoomRequestsHandler.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Linq; +using Newtonsoft.Json; using osu.Game.Beatmaps; using osu.Game.Online.API; using osu.Game.Online.API.Requests; @@ -44,9 +45,7 @@ namespace osu.Game.Tests.Visual.OnlinePlay switch (request) { case CreateRoomRequest createRoomRequest: - var apiRoom = new Room(); - - apiRoom.CopyFrom(createRoomRequest.Room); + var apiRoom = cloneRoom(createRoomRequest.Room); // Passwords are explicitly not copied between rooms. apiRoom.HasPassword.Value = !string.IsNullOrEmpty(createRoomRequest.Room.Password.Value); @@ -178,12 +177,13 @@ namespace osu.Game.Tests.Visual.OnlinePlay private Room createResponseRoom(Room room, bool withParticipants) { - var responseRoom = new Room(); - responseRoom.CopyFrom(room); + var responseRoom = cloneRoom(room); responseRoom.Password.Value = null; if (!withParticipants) responseRoom.RecentParticipants.Clear(); return responseRoom; } + + private Room cloneRoom(Room source) => JsonConvert.DeserializeObject(JsonConvert.SerializeObject(source)); } } From 77289c72249566cea9fd1fc10854af4cddca4cb5 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 3 Jun 2022 20:36:11 +0900 Subject: [PATCH 377/395] Fix inability to serialise-then-deserialise playlist items --- osu.Game/Online/Rooms/PlaylistItem.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/osu.Game/Online/Rooms/PlaylistItem.cs b/osu.Game/Online/Rooms/PlaylistItem.cs index 6ec884d79c..12091bae88 100644 --- a/osu.Game/Online/Rooms/PlaylistItem.cs +++ b/osu.Game/Online/Rooms/PlaylistItem.cs @@ -61,7 +61,13 @@ namespace osu.Game.Online.Rooms /// Used for serialising to the API. /// [JsonProperty("beatmap_id")] - private int onlineBeatmapId => Beatmap.OnlineID; + private int onlineBeatmapId + { + get => Beatmap.OnlineID; + // This setter is only required for client-side serialise-then-deserialise operations. + // Serialisation is supposed to emit only a `beatmap_id`, but a (non-null) `beatmap` is required on deserialise. + set => Beatmap = new APIBeatmap { OnlineID = value }; + } /// /// A beatmap representing this playlist item. From 41ff170b6043d67308673182765837e10fabca6c Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 3 Jun 2022 21:12:09 +0900 Subject: [PATCH 378/395] Fix playlist IDs not being returned --- .../Visual/OnlinePlay/TestRoomRequestsHandler.cs | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/osu.Game/Tests/Visual/OnlinePlay/TestRoomRequestsHandler.cs b/osu.Game/Tests/Visual/OnlinePlay/TestRoomRequestsHandler.cs index 392f32badc..3a668d6931 100644 --- a/osu.Game/Tests/Visual/OnlinePlay/TestRoomRequestsHandler.cs +++ b/osu.Game/Tests/Visual/OnlinePlay/TestRoomRequestsHandler.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using Newtonsoft.Json; using osu.Game.Beatmaps; @@ -184,6 +185,18 @@ namespace osu.Game.Tests.Visual.OnlinePlay return responseRoom; } - private Room cloneRoom(Room source) => JsonConvert.DeserializeObject(JsonConvert.SerializeObject(source)); + private Room cloneRoom(Room source) + { + var result = JsonConvert.DeserializeObject(JsonConvert.SerializeObject(source)); + Debug.Assert(result != null); + + // Playlist item IDs aren't serialised. + if (source.CurrentPlaylistItem.Value != null) + result.CurrentPlaylistItem.Value.ID = source.CurrentPlaylistItem.Value.ID; + for (int i = 0; i < source.Playlist.Count; i++) + result.Playlist[i].ID = source.Playlist[i].ID; + + return result; + } } } From 06ac3c1ad353bdbe25caba972759eb531bd5c5d2 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 3 Jun 2022 21:12:28 +0900 Subject: [PATCH 379/395] Make MultiplayerClient update CurrentPlaylistItem --- osu.Game/Online/Multiplayer/MultiplayerClient.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Online/Multiplayer/MultiplayerClient.cs b/osu.Game/Online/Multiplayer/MultiplayerClient.cs index cae675b406..c95f3fa579 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerClient.cs @@ -197,6 +197,7 @@ namespace osu.Game.Online.Multiplayer APIRoom.Playlist.Clear(); APIRoom.Playlist.AddRange(joinedRoom.Playlist.Select(createPlaylistItem)); + APIRoom.CurrentPlaylistItem.Value = APIRoom.Playlist.Single(item => item.ID == joinedRoom.Settings.PlaylistItemId); Debug.Assert(LocalUser != null); addUserToAPIRoom(LocalUser); @@ -737,6 +738,7 @@ namespace osu.Game.Online.Multiplayer APIRoom.Type.Value = Room.Settings.MatchType; APIRoom.QueueMode.Value = Room.Settings.QueueMode; APIRoom.AutoStartDuration.Value = Room.Settings.AutoStartDuration; + APIRoom.CurrentPlaylistItem.Value = APIRoom.Playlist.Single(item => item.ID == settings.PlaylistItemId); RoomUpdated?.Invoke(); } From 3df4d1b0e6328da0d20d1d24e36508666bdb1afb Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 3 Jun 2022 21:17:47 +0900 Subject: [PATCH 380/395] Fix incorrect HasPassword value for returned rooms --- osu.Game/Tests/Visual/OnlinePlay/TestRoomRequestsHandler.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/osu.Game/Tests/Visual/OnlinePlay/TestRoomRequestsHandler.cs b/osu.Game/Tests/Visual/OnlinePlay/TestRoomRequestsHandler.cs index 3a668d6931..8fea77833e 100644 --- a/osu.Game/Tests/Visual/OnlinePlay/TestRoomRequestsHandler.cs +++ b/osu.Game/Tests/Visual/OnlinePlay/TestRoomRequestsHandler.cs @@ -179,9 +179,15 @@ namespace osu.Game.Tests.Visual.OnlinePlay private Room createResponseRoom(Room room, bool withParticipants) { var responseRoom = cloneRoom(room); + + // Password is hidden from the response, and is only propagated via HasPassword. + bool hadPassword = responseRoom.HasPassword.Value; responseRoom.Password.Value = null; + responseRoom.HasPassword.Value = hadPassword; + if (!withParticipants) responseRoom.RecentParticipants.Clear(); + return responseRoom; } From 365819865e04b490c30784b8fec3805a7a1fdb66 Mon Sep 17 00:00:00 2001 From: Jamie Taylor Date: Wed, 1 Jun 2022 21:00:14 +0900 Subject: [PATCH 381/395] Remove 'submit' sample usages --- osu.Game/Beatmaps/Drawables/Cards/BeatmapCard.cs | 2 +- osu.Game/Graphics/UserInterface/BackButton.cs | 2 +- osu.Game/Graphics/UserInterface/DialogButton.cs | 4 ++-- osu.Game/Graphics/UserInterface/ExternalLinkButton.cs | 2 +- osu.Game/Graphics/UserInterface/HoverSampleSet.cs | 3 --- osu.Game/Online/Chat/DrawableLinkCompiler.cs | 1 - osu.Game/Overlays/News/NewsCard.cs | 2 -- osu.Game/Overlays/News/Sidebar/MonthSection.cs | 2 -- .../Overlays/Profile/Sections/BeatmapMetadataContainer.cs | 2 -- osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs | 2 +- osu.Game/Screens/Select/Options/BeatmapOptionsButton.cs | 2 +- osu.Game/Users/Drawables/ClickableAvatar.cs | 6 ------ osu.Game/Users/Drawables/UpdateableFlag.cs | 2 +- osu.Game/Users/UserPanel.cs | 2 +- 14 files changed, 9 insertions(+), 25 deletions(-) diff --git a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCard.cs b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCard.cs index 9a17d6dc9b..08aaa8da42 100644 --- a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCard.cs +++ b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCard.cs @@ -35,7 +35,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards protected readonly BeatmapDownloadTracker DownloadTracker; protected BeatmapCard(APIBeatmapSet beatmapSet, bool allowExpansion = true) - : base(HoverSampleSet.Submit) + : base(HoverSampleSet.Button) { Expanded = new BindableBool { Disabled = !allowExpansion }; diff --git a/osu.Game/Graphics/UserInterface/BackButton.cs b/osu.Game/Graphics/UserInterface/BackButton.cs index 1b564ef1b4..2b59ee0282 100644 --- a/osu.Game/Graphics/UserInterface/BackButton.cs +++ b/osu.Game/Graphics/UserInterface/BackButton.cs @@ -21,7 +21,7 @@ namespace osu.Game.Graphics.UserInterface { Size = TwoLayerButton.SIZE_EXTENDED; - Child = button = new TwoLayerButton(HoverSampleSet.Submit) + Child = button = new TwoLayerButton { Anchor = Anchor.TopLeft, Origin = Anchor.TopLeft, diff --git a/osu.Game/Graphics/UserInterface/DialogButton.cs b/osu.Game/Graphics/UserInterface/DialogButton.cs index ad69ec4078..69fbd744c9 100644 --- a/osu.Game/Graphics/UserInterface/DialogButton.cs +++ b/osu.Game/Graphics/UserInterface/DialogButton.cs @@ -56,8 +56,8 @@ namespace osu.Game.Graphics.UserInterface private readonly SpriteText spriteText; private Vector2 hoverSpacing => new Vector2(3f, 0f); - public DialogButton() - : base(HoverSampleSet.Submit) + public DialogButton(HoverSampleSet sampleSet = HoverSampleSet.Button) + : base(sampleSet) { RelativeSizeAxes = Axes.X; diff --git a/osu.Game/Graphics/UserInterface/ExternalLinkButton.cs b/osu.Game/Graphics/UserInterface/ExternalLinkButton.cs index 0df69a5b54..1730e1478f 100644 --- a/osu.Game/Graphics/UserInterface/ExternalLinkButton.cs +++ b/osu.Game/Graphics/UserInterface/ExternalLinkButton.cs @@ -36,7 +36,7 @@ namespace osu.Game.Graphics.UserInterface Icon = FontAwesome.Solid.ExternalLinkAlt, RelativeSizeAxes = Axes.Both }, - new HoverClickSounds(HoverSampleSet.Submit) + new HoverClickSounds() }; } diff --git a/osu.Game/Graphics/UserInterface/HoverSampleSet.cs b/osu.Game/Graphics/UserInterface/HoverSampleSet.cs index a5ea6fcfbf..b88f81a143 100644 --- a/osu.Game/Graphics/UserInterface/HoverSampleSet.cs +++ b/osu.Game/Graphics/UserInterface/HoverSampleSet.cs @@ -10,9 +10,6 @@ namespace osu.Game.Graphics.UserInterface [Description("default")] Default, - [Description("submit")] - Submit, - [Description("button")] Button, diff --git a/osu.Game/Online/Chat/DrawableLinkCompiler.cs b/osu.Game/Online/Chat/DrawableLinkCompiler.cs index 8356b36667..3b0d049528 100644 --- a/osu.Game/Online/Chat/DrawableLinkCompiler.cs +++ b/osu.Game/Online/Chat/DrawableLinkCompiler.cs @@ -38,7 +38,6 @@ namespace osu.Game.Online.Chat } public DrawableLinkCompiler(IEnumerable parts) - : base(HoverSampleSet.Submit) { Parts = parts.ToList(); } diff --git a/osu.Game/Overlays/News/NewsCard.cs b/osu.Game/Overlays/News/NewsCard.cs index 68d0704825..5ce0b9df9c 100644 --- a/osu.Game/Overlays/News/NewsCard.cs +++ b/osu.Game/Overlays/News/NewsCard.cs @@ -14,7 +14,6 @@ using osu.Framework.Platform; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; -using osu.Game.Graphics.UserInterface; using osu.Game.Online.API.Requests.Responses; namespace osu.Game.Overlays.News @@ -29,7 +28,6 @@ namespace osu.Game.Overlays.News private TextFlowContainer main; public NewsCard(APINewsPost post) - : base(HoverSampleSet.Submit) { this.post = post; diff --git a/osu.Game/Overlays/News/Sidebar/MonthSection.cs b/osu.Game/Overlays/News/Sidebar/MonthSection.cs index aa83f89689..e2ce25660e 100644 --- a/osu.Game/Overlays/News/Sidebar/MonthSection.cs +++ b/osu.Game/Overlays/News/Sidebar/MonthSection.cs @@ -18,7 +18,6 @@ using System.Diagnostics; using osu.Framework.Audio; using osu.Framework.Audio.Sample; using osu.Framework.Platform; -using osu.Game.Graphics.UserInterface; namespace osu.Game.Overlays.News.Sidebar { @@ -129,7 +128,6 @@ namespace osu.Game.Overlays.News.Sidebar private readonly APINewsPost post; public PostButton(APINewsPost post) - : base(HoverSampleSet.Submit) { this.post = post; diff --git a/osu.Game/Overlays/Profile/Sections/BeatmapMetadataContainer.cs b/osu.Game/Overlays/Profile/Sections/BeatmapMetadataContainer.cs index 94ef5e5d86..13465f3bf8 100644 --- a/osu.Game/Overlays/Profile/Sections/BeatmapMetadataContainer.cs +++ b/osu.Game/Overlays/Profile/Sections/BeatmapMetadataContainer.cs @@ -6,7 +6,6 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Beatmaps; using osu.Game.Graphics.Containers; -using osu.Game.Graphics.UserInterface; namespace osu.Game.Overlays.Profile.Sections { @@ -18,7 +17,6 @@ namespace osu.Game.Overlays.Profile.Sections private readonly IBeatmapInfo beatmapInfo; protected BeatmapMetadataContainer(IBeatmapInfo beatmapInfo) - : base(HoverSampleSet.Submit) { this.beatmapInfo = beatmapInfo; diff --git a/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs b/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs index 35c903eb0c..0015cf8bf9 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs @@ -55,7 +55,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge private void load(AudioManager audio) { sampleSelect = audio.Samples.Get($@"UI/{HoverSampleSet.Default.GetDescription()}-select"); - sampleJoin = audio.Samples.Get($@"UI/{HoverSampleSet.Submit.GetDescription()}-select"); + sampleJoin = audio.Samples.Get($@"UI/{HoverSampleSet.Button.GetDescription()}-select"); AddRangeInternal(new Drawable[] { diff --git a/osu.Game/Screens/Select/Options/BeatmapOptionsButton.cs b/osu.Game/Screens/Select/Options/BeatmapOptionsButton.cs index 6ecb96f723..df7dd47ef3 100644 --- a/osu.Game/Screens/Select/Options/BeatmapOptionsButton.cs +++ b/osu.Game/Screens/Select/Options/BeatmapOptionsButton.cs @@ -77,7 +77,7 @@ namespace osu.Game.Screens.Select.Options public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => box.ReceivePositionalInputAt(screenSpacePos); public BeatmapOptionsButton() - : base(HoverSampleSet.Submit) + : base(HoverSampleSet.Button) { Width = width; RelativeSizeAxes = Axes.Y; diff --git a/osu.Game/Users/Drawables/ClickableAvatar.cs b/osu.Game/Users/Drawables/ClickableAvatar.cs index 0dd135b500..d85648c078 100644 --- a/osu.Game/Users/Drawables/ClickableAvatar.cs +++ b/osu.Game/Users/Drawables/ClickableAvatar.cs @@ -7,7 +7,6 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Input.Events; using osu.Framework.Localisation; using osu.Game.Graphics.Containers; -using osu.Game.Graphics.UserInterface; using osu.Game.Online.API.Requests.Responses; namespace osu.Game.Users.Drawables @@ -74,11 +73,6 @@ namespace osu.Game.Users.Drawables { private LocalisableString tooltip = default_tooltip_text; - public ClickableArea() - : base(HoverSampleSet.Submit) - { - } - public override LocalisableString TooltipText { get => Enabled.Value ? tooltip : default; diff --git a/osu.Game/Users/Drawables/UpdateableFlag.cs b/osu.Game/Users/Drawables/UpdateableFlag.cs index e5debc0683..d0ef760e59 100644 --- a/osu.Game/Users/Drawables/UpdateableFlag.cs +++ b/osu.Game/Users/Drawables/UpdateableFlag.cs @@ -49,7 +49,7 @@ namespace osu.Game.Users.Drawables { RelativeSizeAxes = Axes.Both }, - new HoverClickSounds(HoverSampleSet.Submit) + new HoverClickSounds() } }; } diff --git a/osu.Game/Users/UserPanel.cs b/osu.Game/Users/UserPanel.cs index 248debf1d3..40d70ca406 100644 --- a/osu.Game/Users/UserPanel.cs +++ b/osu.Game/Users/UserPanel.cs @@ -32,7 +32,7 @@ namespace osu.Game.Users protected Drawable Background { get; private set; } protected UserPanel(APIUser user) - : base(HoverSampleSet.Submit) + : base(HoverSampleSet.Button) { if (user == null) throw new ArgumentNullException(nameof(user)); From 2878bb592fc317b36b6f49127da42543027fb28b Mon Sep 17 00:00:00 2001 From: Jamie Taylor Date: Fri, 3 Jun 2022 22:22:32 +0900 Subject: [PATCH 382/395] Use more appropriate sounds for certain components --- .../UserInterface/ShearedToggleButton.cs | 8 ++++-- .../Overlays/BeatmapListing/FilterTabItem.cs | 2 +- .../Chat/Listing/ChannelListingItem.cs | 27 +++++++++++++++++-- .../Header/Components/ExpandDetailsButton.cs | 2 +- 4 files changed, 33 insertions(+), 6 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/ShearedToggleButton.cs b/osu.Game/Graphics/UserInterface/ShearedToggleButton.cs index 4780270f66..ee59da7279 100644 --- a/osu.Game/Graphics/UserInterface/ShearedToggleButton.cs +++ b/osu.Game/Graphics/UserInterface/ShearedToggleButton.cs @@ -13,6 +13,7 @@ namespace osu.Game.Graphics.UserInterface { public class ShearedToggleButton : ShearedButton { + private Sample? sampleClick; private Sample? sampleOff; private Sample? sampleOn; @@ -39,8 +40,9 @@ namespace osu.Game.Graphics.UserInterface [BackgroundDependencyLoader] private void load(AudioManager audio) { - sampleOn = audio.Samples.Get(@"UI/check-on"); - sampleOff = audio.Samples.Get(@"UI/check-off"); + sampleClick = audio.Samples.Get(@"UI/default-select"); + sampleOn = audio.Samples.Get(@"UI/dropdown-open"); + sampleOff = audio.Samples.Get(@"UI/dropdown-close"); } protected override HoverSounds CreateHoverSounds(HoverSampleSet sampleSet) => new HoverSounds(sampleSet); @@ -67,6 +69,8 @@ namespace osu.Game.Graphics.UserInterface private void playSample() { + sampleClick?.Play(); + if (Active.Value) sampleOn?.Play(); else diff --git a/osu.Game/Overlays/BeatmapListing/FilterTabItem.cs b/osu.Game/Overlays/BeatmapListing/FilterTabItem.cs index 52dfcad2cc..ff43170207 100644 --- a/osu.Game/Overlays/BeatmapListing/FilterTabItem.cs +++ b/osu.Game/Overlays/BeatmapListing/FilterTabItem.cs @@ -40,7 +40,7 @@ namespace osu.Game.Overlays.BeatmapListing Font = OsuFont.GetFont(size: 13, weight: FontWeight.Regular), Text = LabelFor(Value) }, - new HoverClickSounds() + new HoverClickSounds(HoverSampleSet.TabSelect) }); Enabled.Value = true; diff --git a/osu.Game/Overlays/Chat/Listing/ChannelListingItem.cs b/osu.Game/Overlays/Chat/Listing/ChannelListingItem.cs index 86c81d5d79..2a21d30a4a 100644 --- a/osu.Game/Overlays/Chat/Listing/ChannelListingItem.cs +++ b/osu.Game/Overlays/Chat/Listing/ChannelListingItem.cs @@ -6,6 +6,8 @@ using System; using System.Collections.Generic; using osu.Framework.Allocation; +using osu.Framework.Audio; +using osu.Framework.Audio.Sample; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -16,6 +18,7 @@ using osu.Framework.Localisation; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; using osu.Game.Online.Chat; using osuTK; @@ -32,6 +35,8 @@ namespace osu.Game.Overlays.Chat.Listing public IEnumerable FilterTerms => new LocalisableString[] { Channel.Name, Channel.Topic ?? string.Empty }; public bool MatchingFilter { set => this.FadeTo(value ? 1f : 0f, 100); } + protected override HoverSounds CreateHoverSounds(HoverSampleSet sampleSet) => new HoverSounds(); + private Box hoverBox = null!; private SpriteIcon checkbox = null!; private OsuSpriteText channelText = null!; @@ -46,14 +51,20 @@ namespace osu.Game.Overlays.Chat.Listing private const float vertical_margin = 1.5f; + private Sample? sampleJoin; + private Sample? sampleLeave; + public ChannelListingItem(Channel channel) { Channel = channel; } [BackgroundDependencyLoader] - private void load() + private void load(AudioManager audio) { + sampleJoin = audio.Samples.Get(@"UI/check-on"); + sampleLeave = audio.Samples.Get(@"UI/check-off"); + Masking = true; CornerRadius = 5; RelativeSizeAxes = Axes.X; @@ -156,7 +167,19 @@ namespace osu.Game.Overlays.Chat.Listing } }, true); - Action = () => (channelJoined.Value ? OnRequestLeave : OnRequestJoin)?.Invoke(Channel); + Action = () => + { + if (channelJoined.Value) + { + OnRequestLeave?.Invoke(Channel); + sampleLeave?.Play(); + } + else + { + OnRequestJoin?.Invoke(Channel); + sampleJoin?.Play(); + } + }; } protected override bool OnHover(HoverEvent e) diff --git a/osu.Game/Overlays/Profile/Header/Components/ExpandDetailsButton.cs b/osu.Game/Overlays/Profile/Header/Components/ExpandDetailsButton.cs index b4a5d5e31b..4cfdf5cc86 100644 --- a/osu.Game/Overlays/Profile/Header/Components/ExpandDetailsButton.cs +++ b/osu.Game/Overlays/Profile/Header/Components/ExpandDetailsButton.cs @@ -25,7 +25,7 @@ namespace osu.Game.Overlays.Profile.Header.Components private Sample sampleOpen; private Sample sampleClose; - protected override HoverSounds CreateHoverSounds(HoverSampleSet sampleSet) => new HoverSounds(); + protected override HoverSounds CreateHoverSounds(HoverSampleSet sampleSet) => new HoverClickSounds(); public ExpandDetailsButton() { From 6115275bc240e72a1744c8917e59d0ecef653cbb Mon Sep 17 00:00:00 2001 From: Jamie Taylor Date: Fri, 3 Jun 2022 22:28:36 +0900 Subject: [PATCH 383/395] Use 'default' sampleset for toolbar and repurpose 'toolbar' select sample temporarily --- osu.Game/Overlays/Toolbar/ToolbarButton.cs | 2 -- osu.Game/Overlays/Toolbar/ToolbarClock.cs | 2 -- osu.Game/Screens/Select/FooterButton.cs | 2 +- 3 files changed, 1 insertion(+), 5 deletions(-) diff --git a/osu.Game/Overlays/Toolbar/ToolbarButton.cs b/osu.Game/Overlays/Toolbar/ToolbarButton.cs index 4a839b048c..b686f11c13 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarButton.cs @@ -20,7 +20,6 @@ using osu.Game.Graphics; using osu.Game.Graphics.Backgrounds; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; -using osu.Game.Graphics.UserInterface; using osu.Game.Input.Bindings; using osuTK; using osuTK.Graphics; @@ -83,7 +82,6 @@ namespace osu.Game.Overlays.Toolbar private RealmAccess realm { get; set; } protected ToolbarButton() - : base(HoverSampleSet.Toolbar) { Width = Toolbar.HEIGHT; RelativeSizeAxes = Axes.Y; diff --git a/osu.Game/Overlays/Toolbar/ToolbarClock.cs b/osu.Game/Overlays/Toolbar/ToolbarClock.cs index 308359570f..12529da07f 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarClock.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarClock.cs @@ -11,7 +11,6 @@ using osu.Framework.Input.Events; using osu.Game.Configuration; using osu.Game.Graphics; using osu.Game.Graphics.Containers; -using osu.Game.Graphics.UserInterface; using osuTK; using osuTK.Graphics; @@ -29,7 +28,6 @@ namespace osu.Game.Overlays.Toolbar private AnalogClockDisplay analog; public ToolbarClock() - : base(HoverSampleSet.Toolbar) { RelativeSizeAxes = Axes.Y; AutoSizeAxes = Axes.X; diff --git a/osu.Game/Screens/Select/FooterButton.cs b/osu.Game/Screens/Select/FooterButton.cs index 9cb178ca8b..1587f43258 100644 --- a/osu.Game/Screens/Select/FooterButton.cs +++ b/osu.Game/Screens/Select/FooterButton.cs @@ -66,7 +66,7 @@ namespace osu.Game.Screens.Select private readonly Box light; public FooterButton() - : base(HoverSampleSet.Button) + : base(HoverSampleSet.Toolbar) { AutoSizeAxes = Axes.Both; Shear = SHEAR; From 396a566a0e577b0f2b1a2ba71ee2f1aa687b6c7b Mon Sep 17 00:00:00 2001 From: Jamie Taylor Date: Fri, 3 Jun 2022 22:29:03 +0900 Subject: [PATCH 384/395] Add some randomness to click samples --- osu.Game/Graphics/UserInterface/HoverClickSounds.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/osu.Game/Graphics/UserInterface/HoverClickSounds.cs b/osu.Game/Graphics/UserInterface/HoverClickSounds.cs index 12819840e5..ba253a7c71 100644 --- a/osu.Game/Graphics/UserInterface/HoverClickSounds.cs +++ b/osu.Game/Graphics/UserInterface/HoverClickSounds.cs @@ -7,6 +7,7 @@ using osu.Framework.Audio; using osu.Framework.Audio.Sample; using osu.Framework.Extensions; using osu.Framework.Input.Events; +using osu.Framework.Utils; using osuTK.Input; namespace osu.Game.Graphics.UserInterface @@ -37,7 +38,10 @@ namespace osu.Game.Graphics.UserInterface protected override bool OnClick(ClickEvent e) { if (buttons.Contains(e.Button) && Contains(e.ScreenSpaceMousePosition)) - sampleClick?.Play(); + { + sampleClick.Frequency.Value = 0.99 + RNG.NextDouble(0.02); + sampleClick.Play(); + } return base.OnClick(e); } From 045e0446370a7ff0c4b4d7d5035ae7ca92ec3189 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 4 Jun 2022 01:14:41 +0300 Subject: [PATCH 385/395] Expose `LegacySkinTransformer`'s underlying skin for pattern-matching --- osu.Game/Skinning/LegacySkinTransformer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Skinning/LegacySkinTransformer.cs b/osu.Game/Skinning/LegacySkinTransformer.cs index 9481fc7182..34714b9dc5 100644 --- a/osu.Game/Skinning/LegacySkinTransformer.cs +++ b/osu.Game/Skinning/LegacySkinTransformer.cs @@ -23,7 +23,7 @@ namespace osu.Game.Skinning /// The which is being transformed. /// [NotNull] - protected internal ISkin Skin { get; } + public ISkin Skin { get; } protected LegacySkinTransformer([NotNull] ISkin skin) { From fe96e15b1b32301d405b8755431f301e0555a80f Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 4 Jun 2022 01:15:03 +0300 Subject: [PATCH 386/395] Update spinner approach circle to handle `LegacySkinTransformer` --- .../Skinning/Legacy/LegacyNewStyleSpinner.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyNewStyleSpinner.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyNewStyleSpinner.cs index 1e170036e4..a58f62736b 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyNewStyleSpinner.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyNewStyleSpinner.cs @@ -78,7 +78,9 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy } }); - if (!(source.FindProvider(s => s.GetTexture("spinner-top") != null) is DefaultLegacySkin)) + var topProvider = source.FindProvider(s => s.GetTexture("spinner-top") != null); + + if (topProvider is LegacySkinTransformer transformer && !(transformer.Skin is DefaultLegacySkin)) { AddInternal(ApproachCircle = new Sprite { From b342aad24a906f15da6652935fefdb9aee91c776 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Thu, 2 Jun 2022 20:53:14 -0700 Subject: [PATCH 387/395] Revert/fix some incorrectly used sorting localisation --- osu.Game/Screens/Select/Filter/SortMode.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Select/Filter/SortMode.cs b/osu.Game/Screens/Select/Filter/SortMode.cs index 5dfa2a2664..3dd3381059 100644 --- a/osu.Game/Screens/Select/Filter/SortMode.cs +++ b/osu.Game/Screens/Select/Filter/SortMode.cs @@ -15,7 +15,7 @@ namespace osu.Game.Screens.Select.Filter [Description("Author")] Author, - [LocalisableDescription(typeof(BeatmapsetsStrings), nameof(BeatmapsetsStrings.ShowStatsBpm))] + [LocalisableDescription(typeof(SortStrings), nameof(SortStrings.ArtistTracksBpm))] BPM, [Description("Date Added")] @@ -28,10 +28,10 @@ namespace osu.Game.Screens.Select.Filter Length, // todo: pending support (https://github.com/ppy/osu/issues/4917) - // [LocalisableDescription(typeof(BeatmapsStrings), nameof(BeatmapsStrings.ListingSearchFiltersRank))] + // [Description("Rank Achieved")] // RankAchieved, - [LocalisableDescription(typeof(BeatmapsetsStrings), nameof(BeatmapsetsStrings.ShowInfoSource))] + [Description("Source")] Source, [LocalisableDescription(typeof(BeatmapsStrings), nameof(BeatmapsStrings.ListingSearchSortingTitle))] From 58c8562cb0d64ef5287a5a5d7dc8ff022dd3ce11 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Thu, 2 Jun 2022 20:30:56 -0700 Subject: [PATCH 388/395] Fix username placeholder text casing and missing localisation on registration form --- osu.Game/Overlays/AccountCreation/ScreenEntry.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/AccountCreation/ScreenEntry.cs b/osu.Game/Overlays/AccountCreation/ScreenEntry.cs index 1be1321d85..f4f958e4a4 100644 --- a/osu.Game/Overlays/AccountCreation/ScreenEntry.cs +++ b/osu.Game/Overlays/AccountCreation/ScreenEntry.cs @@ -5,6 +5,7 @@ using System; using System.Linq; using System.Threading.Tasks; using osu.Framework.Allocation; +using osu.Framework.Extensions.LocalisationExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Utils; @@ -69,7 +70,7 @@ namespace osu.Game.Overlays.AccountCreation }, usernameTextBox = new OsuTextBox { - PlaceholderText = UsersStrings.LoginUsername, + PlaceholderText = UsersStrings.LoginUsername.ToLower(), RelativeSizeAxes = Axes.X, TabbableContentContainer = this }, @@ -91,7 +92,7 @@ namespace osu.Game.Overlays.AccountCreation }, passwordTextBox = new OsuPasswordTextBox { - PlaceholderText = "password", + PlaceholderText = UsersStrings.LoginPassword.ToLower(), RelativeSizeAxes = Axes.X, TabbableContentContainer = this, }, From 4b54fedd88e5389eda73e2f363fc6c8a115032b0 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Thu, 2 Jun 2022 20:28:59 -0700 Subject: [PATCH 389/395] Add back hide common string using new `ToSentence()` extension method --- osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs index 9772b1feb3..98b885eb43 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs @@ -8,6 +8,7 @@ using System.Threading; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Extensions.LocalisationExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; @@ -244,7 +245,7 @@ namespace osu.Game.Screens.Select.Carousel } if (hideRequested != null) - items.Add(new OsuMenuItem("Hide", MenuItemType.Destructive, () => hideRequested(beatmapInfo))); + items.Add(new OsuMenuItem(CommonStrings.ButtonsHide.ToSentence(), MenuItemType.Destructive, () => hideRequested(beatmapInfo))); return items.ToArray(); } From 30cf6bffad8a0848ed32ca60f0fd0c8f6c914a71 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Fri, 3 Jun 2022 21:41:34 -0700 Subject: [PATCH 390/395] Add tooltips to beatmap card icon pills --- .../Drawables/Cards/BeatmapCardExtra.cs | 5 ++--- .../Drawables/Cards/BeatmapCardNormal.cs | 5 ++--- osu.Game/Beatmaps/Drawables/Cards/IconPill.cs | 8 ++++++-- .../Drawables/Cards/StoryboardIconPill.cs | 19 +++++++++++++++++++ .../Beatmaps/Drawables/Cards/VideoIconPill.cs | 19 +++++++++++++++++++ 5 files changed, 48 insertions(+), 8 deletions(-) create mode 100644 osu.Game/Beatmaps/Drawables/Cards/StoryboardIconPill.cs create mode 100644 osu.Game/Beatmaps/Drawables/Cards/VideoIconPill.cs diff --git a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardExtra.cs b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardExtra.cs index 58c1ebee0f..7826d64296 100644 --- a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardExtra.cs +++ b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardExtra.cs @@ -6,7 +6,6 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Sprites; using osu.Framework.Localisation; using osu.Game.Beatmaps.Drawables.Cards.Statistics; using osu.Game.Graphics; @@ -245,10 +244,10 @@ namespace osu.Game.Beatmaps.Drawables.Cards }); if (BeatmapSet.HasVideo) - leftIconArea.Add(new IconPill(FontAwesome.Solid.Film) { IconSize = new Vector2(20) }); + leftIconArea.Add(new VideoIconPill { IconSize = new Vector2(20) }); if (BeatmapSet.HasStoryboard) - leftIconArea.Add(new IconPill(FontAwesome.Solid.Image) { IconSize = new Vector2(20) }); + leftIconArea.Add(new StoryboardIconPill { IconSize = new Vector2(20) }); if (BeatmapSet.FeaturedInSpotlight) { diff --git a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardNormal.cs b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardNormal.cs index 3d7e81de21..c1ba521925 100644 --- a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardNormal.cs +++ b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardNormal.cs @@ -7,7 +7,6 @@ using System.Collections.Generic; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Sprites; using osu.Framework.Localisation; using osu.Game.Beatmaps.Drawables.Cards.Statistics; using osu.Game.Graphics; @@ -226,10 +225,10 @@ namespace osu.Game.Beatmaps.Drawables.Cards }); if (BeatmapSet.HasVideo) - leftIconArea.Add(new IconPill(FontAwesome.Solid.Film) { IconSize = new Vector2(20) }); + leftIconArea.Add(new VideoIconPill { IconSize = new Vector2(20) }); if (BeatmapSet.HasStoryboard) - leftIconArea.Add(new IconPill(FontAwesome.Solid.Image) { IconSize = new Vector2(20) }); + leftIconArea.Add(new StoryboardIconPill { IconSize = new Vector2(20) }); if (BeatmapSet.FeaturedInSpotlight) { diff --git a/osu.Game/Beatmaps/Drawables/Cards/IconPill.cs b/osu.Game/Beatmaps/Drawables/Cards/IconPill.cs index c55e9c0a28..1b2c5d3ffc 100644 --- a/osu.Game/Beatmaps/Drawables/Cards/IconPill.cs +++ b/osu.Game/Beatmaps/Drawables/Cards/IconPill.cs @@ -3,14 +3,16 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; +using osu.Framework.Localisation; using osuTK; using osuTK.Graphics; namespace osu.Game.Beatmaps.Drawables.Cards { - public class IconPill : CircularContainer + public abstract class IconPill : CircularContainer, IHasTooltip { public Vector2 IconSize { @@ -20,7 +22,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards private readonly Container iconContainer; - public IconPill(IconUsage icon) + protected IconPill(IconUsage icon) { AutoSizeAxes = Axes.Both; Masking = true; @@ -47,5 +49,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards }, }; } + + public abstract LocalisableString TooltipText { get; } } } diff --git a/osu.Game/Beatmaps/Drawables/Cards/StoryboardIconPill.cs b/osu.Game/Beatmaps/Drawables/Cards/StoryboardIconPill.cs new file mode 100644 index 0000000000..2ebf9107f5 --- /dev/null +++ b/osu.Game/Beatmaps/Drawables/Cards/StoryboardIconPill.cs @@ -0,0 +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 osu.Framework.Graphics.Sprites; +using osu.Framework.Localisation; +using osu.Game.Resources.Localisation.Web; + +namespace osu.Game.Beatmaps.Drawables.Cards +{ + public class StoryboardIconPill : IconPill + { + public StoryboardIconPill() + : base(FontAwesome.Solid.Image) + { + } + + public override LocalisableString TooltipText => BeatmapsetsStrings.ShowInfoStoryboard; + } +} diff --git a/osu.Game/Beatmaps/Drawables/Cards/VideoIconPill.cs b/osu.Game/Beatmaps/Drawables/Cards/VideoIconPill.cs new file mode 100644 index 0000000000..b81e18b0dd --- /dev/null +++ b/osu.Game/Beatmaps/Drawables/Cards/VideoIconPill.cs @@ -0,0 +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 osu.Framework.Graphics.Sprites; +using osu.Framework.Localisation; +using osu.Game.Resources.Localisation.Web; + +namespace osu.Game.Beatmaps.Drawables.Cards +{ + public class VideoIconPill : IconPill + { + public VideoIconPill() + : base(FontAwesome.Solid.Film) + { + } + + public override LocalisableString TooltipText => BeatmapsetsStrings.ShowInfoVideo; + } +} From 8ad588d9279058f0746c204e9dfe08e7dbdc43f9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 4 Jun 2022 22:12:53 +0900 Subject: [PATCH 391/395] Update framework --- osu.Android.props | 2 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index 0058bb3577..ab96504e94 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -52,7 +52,7 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 2285f61331..fd6715c58a 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -36,7 +36,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/osu.iOS.props b/osu.iOS.props index b134a7e356..9a36e1073b 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -61,7 +61,7 @@ - + @@ -84,7 +84,7 @@ - + From c0aaeff2b3cfbf7ab75d17ba839af27165986afd Mon Sep 17 00:00:00 2001 From: Jai Sharma Date: Sat, 4 Jun 2022 16:11:49 +0100 Subject: [PATCH 392/395] Update `DaySeparator` to use new design throughout Moves `DaySeparator` chat component to it's own file and update it to match new chat design. Makes use of several virtual attributes that can be overridden to update spacing and layout in other usage contexts. Remove redundant usage of `ChatOverlayDaySeparator`, since the new design is now part of the base class. Create `StandAloneDaySeparator` to use in `StandAloneChatDisplay` which overrides attributes to match correct spacing and layout for its design. Ensure that `DrawableChannel.CreateDaySeparator` returns type of `DaySeparator` instead of `Drawable`. --- .../Online/TestSceneStandAloneChatDisplay.cs | 4 +- osu.Game/Online/Chat/StandAloneChatDisplay.cs | 34 ++++-- .../Chat/ChatOverlayDrawableChannel.cs | 82 +------------- osu.Game/Overlays/Chat/DaySeparator.cs | 105 ++++++++++++++++++ osu.Game/Overlays/Chat/DrawableChannel.cs | 80 +------------ 5 files changed, 138 insertions(+), 167 deletions(-) create mode 100644 osu.Game/Overlays/Chat/DaySeparator.cs diff --git a/osu.Game.Tests/Visual/Online/TestSceneStandAloneChatDisplay.cs b/osu.Game.Tests/Visual/Online/TestSceneStandAloneChatDisplay.cs index 860ef5d565..cb52f41c33 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneStandAloneChatDisplay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneStandAloneChatDisplay.cs @@ -128,11 +128,11 @@ namespace osu.Game.Tests.Visual.Online AddAssert("Ensure no adjacent day separators", () => { - var indices = chatDisplay.FillFlow.OfType().Select(ds => chatDisplay.FillFlow.IndexOf(ds)); + var indices = chatDisplay.FillFlow.OfType().Select(ds => chatDisplay.FillFlow.IndexOf(ds)); foreach (int i in indices) { - if (i < chatDisplay.FillFlow.Count && chatDisplay.FillFlow[i + 1] is DrawableChannel.DaySeparator) + if (i < chatDisplay.FillFlow.Count && chatDisplay.FillFlow[i + 1] is DaySeparator) return false; } diff --git a/osu.Game/Online/Chat/StandAloneChatDisplay.cs b/osu.Game/Online/Chat/StandAloneChatDisplay.cs index b7e1bc999b..f57ffcfe25 100644 --- a/osu.Game/Online/Chat/StandAloneChatDisplay.cs +++ b/osu.Game/Online/Chat/StandAloneChatDisplay.cs @@ -155,9 +155,6 @@ namespace osu.Game.Online.Chat { public Func CreateChatLineAction; - [Resolved] - private OsuColour colours { get; set; } - public StandAloneDrawableChannel(Channel channel) : base(channel) { @@ -166,25 +163,40 @@ namespace osu.Game.Online.Chat [BackgroundDependencyLoader] private void load() { + // TODO: Remove once DrawableChannel & ChatLine padding is fixed ChatLineFlow.Padding = new MarginPadding { Horizontal = 0 }; } protected override ChatLine CreateChatLine(Message m) => CreateChatLineAction(m); - protected override Drawable CreateDaySeparator(DateTimeOffset time) => new DaySeparator(time) + protected override DaySeparator CreateDaySeparator(DateTimeOffset time) => new StandAloneDaySeparator(time); + } + + protected class StandAloneDaySeparator : DaySeparator + { + protected override float TextSize => 14; + protected override float LineHeight => 1; + protected override float Spacing => 10; + protected override float DateAlign => 120; + + public StandAloneDaySeparator(DateTimeOffset time) + : base(time) { - TextSize = 14, - Colour = colours.Yellow, - LineHeight = 1, - Padding = new MarginPadding { Horizontal = 10 }, - Margin = new MarginPadding { Vertical = 5 }, - }; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + Height = 25; + Colour = colours.Yellow; + // TODO: Remove once DrawableChannel & ChatLine padding is fixed + Padding = new MarginPadding { Horizontal = 10 }; + } } protected class StandAloneMessage : ChatLine { protected override float TextSize => 15; - protected override float HorizontalPadding => 10; protected override float MessagePadding => 120; protected override float TimestampPadding => 50; diff --git a/osu.Game/Overlays/Chat/ChatOverlayDrawableChannel.cs b/osu.Game/Overlays/Chat/ChatOverlayDrawableChannel.cs index 3b47adc4b7..a5d22f678e 100644 --- a/osu.Game/Overlays/Chat/ChatOverlayDrawableChannel.cs +++ b/osu.Game/Overlays/Chat/ChatOverlayDrawableChannel.cs @@ -6,10 +6,6 @@ using System; using osu.Framework.Allocation; using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; -using osu.Game.Graphics; -using osu.Game.Graphics.Sprites; using osu.Game.Online.Chat; namespace osu.Game.Overlays.Chat @@ -24,85 +20,19 @@ namespace osu.Game.Overlays.Chat [BackgroundDependencyLoader] private void load() { + // TODO: Remove once DrawableChannel & ChatLine padding is fixed ChatLineFlow.Padding = new MarginPadding(0); } - protected override Drawable CreateDaySeparator(DateTimeOffset time) => new ChatOverlayDaySeparator(time); + protected override DaySeparator CreateDaySeparator(DateTimeOffset time) => new ChatOverlayDaySeparator(time); - private class ChatOverlayDaySeparator : Container + private class ChatOverlayDaySeparator : DaySeparator { - private readonly DateTimeOffset time; - public ChatOverlayDaySeparator(DateTimeOffset time) + : base(time) { - this.time = time; - } - - [BackgroundDependencyLoader] - private void load(OverlayColourProvider colourProvider) - { - RelativeSizeAxes = Axes.X; - AutoSizeAxes = Axes.Y; - Padding = new MarginPadding { Horizontal = 15, Vertical = 20 }; - Child = new GridContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - ColumnDimensions = new[] - { - new Dimension(GridSizeMode.Absolute, 200), - new Dimension(GridSizeMode.Absolute, 15), - new Dimension(), - }, - Content = new[] - { - new[] - { - new GridContainer - { - RelativeSizeAxes = Axes.Both, - ColumnDimensions = new[] - { - new Dimension(), - new Dimension(GridSizeMode.Absolute, 15), - new Dimension(GridSizeMode.AutoSize), - }, - Content = new[] - { - new[] - { - new Circle - { - Anchor = Anchor.CentreRight, - Origin = Anchor.CentreRight, - Colour = colourProvider.Background5, - RelativeSizeAxes = Axes.X, - Height = 2, - }, - Drawable.Empty(), - new OsuSpriteText - { - Anchor = Anchor.CentreRight, - Origin = Anchor.CentreRight, - Text = time.ToLocalTime().ToString("dd MMMM yyyy").ToUpper(), - Font = OsuFont.Torus.With(size: 15, weight: FontWeight.SemiBold), - Colour = colourProvider.Content1, - }, - }, - }, - }, - Drawable.Empty(), - new Circle - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Colour = colourProvider.Background5, - RelativeSizeAxes = Axes.X, - Height = 2, - }, - }, - }, - }; + // TODO: Remove once DrawableChannel & ChatLine padding is fixed + Padding = new MarginPadding { Horizontal = 15 }; } } } diff --git a/osu.Game/Overlays/Chat/DaySeparator.cs b/osu.Game/Overlays/Chat/DaySeparator.cs new file mode 100644 index 0000000000..2e3796151c --- /dev/null +++ b/osu.Game/Overlays/Chat/DaySeparator.cs @@ -0,0 +1,105 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +#nullable enable + +using System; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; + +namespace osu.Game.Overlays.Chat +{ + public class DaySeparator : Container + { + protected virtual float TextSize => 15; + + protected virtual float LineHeight => 2; + + protected virtual float DateAlign => 200; + + protected virtual float Spacing => 15; + + private readonly DateTimeOffset time; + + [Resolved(CanBeNull = true)] + private OverlayColourProvider? colourProvider { get; set; } + + public DaySeparator(DateTimeOffset time) + { + this.time = time; + Height = 40; + } + + [BackgroundDependencyLoader] + private void load() + { + RelativeSizeAxes = Axes.X; + Child = new GridContainer + { + RelativeSizeAxes = Axes.Both, + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + RowDimensions = new[] { new Dimension() }, + ColumnDimensions = new[] + { + new Dimension(GridSizeMode.Absolute, DateAlign), + new Dimension(GridSizeMode.Absolute, Spacing), + new Dimension(), + }, + Content = new[] + { + new[] + { + new GridContainer + { + RelativeSizeAxes = Axes.Both, + RowDimensions = new[] { new Dimension() }, + ColumnDimensions = new[] + { + new Dimension(), + new Dimension(GridSizeMode.Absolute, Spacing), + new Dimension(GridSizeMode.AutoSize), + }, + Content = new[] + { + new[] + { + new Circle + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.X, + Height = LineHeight, + Colour = colourProvider?.Background5 ?? Colour4.White, + }, + Drawable.Empty(), + new OsuSpriteText + { + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + Text = time.ToLocalTime().ToString("dd MMMM yyyy").ToUpper(), + Font = OsuFont.Torus.With(size: TextSize, weight: FontWeight.SemiBold), + Colour = colourProvider?.Content1 ?? Colour4.White, + }, + } + }, + }, + Drawable.Empty(), + new Circle + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.X, + Height = LineHeight, + Colour = colourProvider?.Background5 ?? Colour4.White, + }, + } + } + }; + } + } +} diff --git a/osu.Game/Overlays/Chat/DrawableChannel.cs b/osu.Game/Overlays/Chat/DrawableChannel.cs index f2d4a3e301..f0d4d12f4f 100644 --- a/osu.Game/Overlays/Chat/DrawableChannel.cs +++ b/osu.Game/Overlays/Chat/DrawableChannel.cs @@ -7,14 +7,9 @@ using System.Diagnostics; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; -using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; -using osu.Framework.Graphics.Sprites; -using osu.Game.Graphics; using osu.Game.Graphics.Cursor; -using osu.Game.Graphics.Sprites; using osu.Game.Online.Chat; using osuTK.Graphics; @@ -40,9 +35,6 @@ namespace osu.Game.Overlays.Chat } } - [Resolved] - private OsuColour colours { get; set; } - public DrawableChannel(Channel channel) { Channel = channel; @@ -67,7 +59,7 @@ namespace osu.Game.Overlays.Chat Padding = new MarginPadding { Bottom = 5 }, Child = ChatLineFlow = new FillFlowContainer { - Padding = new MarginPadding { Left = 20, Right = 20 }, + Padding = new MarginPadding { Horizontal = 15 }, RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, Direction = FillDirection.Vertical, @@ -121,11 +113,7 @@ namespace osu.Game.Overlays.Chat protected virtual ChatLine CreateChatLine(Message m) => new ChatLine(m); - protected virtual Drawable CreateDaySeparator(DateTimeOffset time) => new DaySeparator(time) - { - Colour = colours.ChatBlue.Lighten(0.7f), - Margin = new MarginPadding { Vertical = 10 }, - }; + protected virtual DaySeparator CreateDaySeparator(DateTimeOffset time) => new DaySeparator(time); private void newMessagesArrived(IEnumerable newMessages) => Schedule(() => { @@ -203,69 +191,5 @@ namespace osu.Game.Overlays.Chat }); private IEnumerable chatLines => ChatLineFlow.Children.OfType(); - - public class DaySeparator : Container - { - public float TextSize - { - get => text.Font.Size; - set => text.Font = text.Font.With(size: value); - } - - private float lineHeight = 2; - - public float LineHeight - { - get => lineHeight; - set => lineHeight = leftBox.Height = rightBox.Height = value; - } - - private readonly SpriteText text; - private readonly Box leftBox; - private readonly Box rightBox; - - public DaySeparator(DateTimeOffset time) - { - RelativeSizeAxes = Axes.X; - AutoSizeAxes = Axes.Y; - Child = new GridContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - ColumnDimensions = new[] - { - new Dimension(), - new Dimension(GridSizeMode.AutoSize), - new Dimension(), - }, - RowDimensions = new[] { new Dimension(GridSizeMode.AutoSize), }, - Content = new[] - { - new Drawable[] - { - leftBox = new Box - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.X, - Height = lineHeight, - }, - text = new OsuSpriteText - { - Margin = new MarginPadding { Horizontal = 10 }, - Text = time.ToLocalTime().ToString("dd MMM yyyy"), - }, - rightBox = new Box - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.X, - Height = lineHeight, - }, - } - } - }; - } - } } } From f1af3205ca2ee2a92727e9c95b0cc8044b215a41 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 5 Jun 2022 13:15:05 +0900 Subject: [PATCH 393/395] Update framework --- osu.Android.props | 2 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index ab96504e94..aad8cf10d0 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -52,7 +52,7 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index fd6715c58a..ca92d06aed 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -36,7 +36,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/osu.iOS.props b/osu.iOS.props index 9a36e1073b..a0fafa635b 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -61,7 +61,7 @@ - + @@ -84,7 +84,7 @@ - + From 6351f652a2eeaeb2128254638bb9e4143edb5f59 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 6 Jun 2022 17:56:25 +0900 Subject: [PATCH 394/395] Fix combo starting at 0 when spectating --- osu.Game/Rulesets/Scoring/ScoreProcessor.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index 4a5897c621..df094ddb7c 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -460,6 +460,7 @@ namespace osu.Game.Rulesets.Scoring currentMaximumScoringValues.BaseScore = maximum.BaseScore; currentMaximumScoringValues.MaxCombo = maximum.MaxCombo; + Combo.Value = frame.Header.Combo; HighestCombo.Value = frame.Header.MaxCombo; scoreResultCounts.Clear(); From 4e35ac8d4c5bba0c1577f9ab859b4d319c34dc7c Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 6 Jun 2022 18:01:52 +0900 Subject: [PATCH 395/395] Add test --- osu.Game.Tests/Gameplay/TestSceneScoreProcessor.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game.Tests/Gameplay/TestSceneScoreProcessor.cs b/osu.Game.Tests/Gameplay/TestSceneScoreProcessor.cs index af4b002bc9..97be1dcfaa 100644 --- a/osu.Game.Tests/Gameplay/TestSceneScoreProcessor.cs +++ b/osu.Game.Tests/Gameplay/TestSceneScoreProcessor.cs @@ -59,12 +59,14 @@ namespace osu.Game.Tests.Gameplay scoreProcessor.ApplyResult(new JudgementResult(beatmap.HitObjects[0], new TestJudgement(HitResult.Great)) { Type = HitResult.Great }); Assert.That(scoreProcessor.TotalScore.Value, Is.EqualTo(1_000_000)); Assert.That(scoreProcessor.JudgedHits, Is.EqualTo(1)); + Assert.That(scoreProcessor.Combo.Value, Is.EqualTo(1)); // No header shouldn't cause any change scoreProcessor.ResetFromReplayFrame(new OsuReplayFrame()); Assert.That(scoreProcessor.TotalScore.Value, Is.EqualTo(1_000_000)); Assert.That(scoreProcessor.JudgedHits, Is.EqualTo(1)); + Assert.That(scoreProcessor.Combo.Value, Is.EqualTo(1)); // Reset with a miss instead. scoreProcessor.ResetFromReplayFrame(new OsuReplayFrame @@ -74,6 +76,7 @@ namespace osu.Game.Tests.Gameplay Assert.That(scoreProcessor.TotalScore.Value, Is.Zero); Assert.That(scoreProcessor.JudgedHits, Is.EqualTo(1)); + Assert.That(scoreProcessor.Combo.Value, Is.EqualTo(0)); // Reset with no judged hit. scoreProcessor.ResetFromReplayFrame(new OsuReplayFrame @@ -83,6 +86,7 @@ namespace osu.Game.Tests.Gameplay Assert.That(scoreProcessor.TotalScore.Value, Is.Zero); Assert.That(scoreProcessor.JudgedHits, Is.Zero); + Assert.That(scoreProcessor.Combo.Value, Is.EqualTo(0)); } private class TestJudgement : Judgement