From cabbc486e9d1940f4d95429c6691758ff7ee6b85 Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Fri, 1 Apr 2022 11:36:20 +0800 Subject: [PATCH 01/54] 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 02/54] 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 03/54] 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 04/54] 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 05/54] 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 06/54] 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 07/54] 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 08/54] 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 09/54] 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 10/54] 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 11/54] 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 12/54] 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 3183621d3d20e05d65d4f6b9ea383d7cc6df3512 Mon Sep 17 00:00:00 2001 From: sw1tchbl4d3 Date: Thu, 26 May 2022 22:57:24 +0200 Subject: [PATCH 13/54] 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 14/54] 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 15/54] 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 4967d036063f954cdd37ac33134288ebfb26a881 Mon Sep 17 00:00:00 2001 From: sw1tchbl4d3 Date: Fri, 27 May 2022 09:58:40 +0200 Subject: [PATCH 16/54] 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 17/54] 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 6a0cf26722cfa9ab08de4f8f00156657c798afbd Mon Sep 17 00:00:00 2001 From: sw1tchbl4d3 Date: Fri, 27 May 2022 12:36:43 +0200 Subject: [PATCH 18/54] 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 19/54] 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 e2951d70d1eac5ee0f2291c2192ac9d8f8598269 Mon Sep 17 00:00:00 2001 From: sw1tchbl4d3 Date: Fri, 27 May 2022 16:38:54 +0200 Subject: [PATCH 20/54] 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 320b6ca6317139acb9b75df645c4bf4722266c9d Mon Sep 17 00:00:00 2001 From: Jai Sharma Date: Fri, 27 May 2022 16:06:23 +0100 Subject: [PATCH 21/54] 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 22/54] 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 23/54] 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 c5845f307a7cac07a13f39b52ef9ac71fd7e261c Mon Sep 17 00:00:00 2001 From: Jai Sharma Date: Fri, 27 May 2022 20:12:00 +0100 Subject: [PATCH 24/54] 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 25/54] 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 26/54] 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 eec9248cdee0257912d936086147344d66eb2983 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 28 May 2022 02:10:18 +0300 Subject: [PATCH 27/54] 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 28/54] 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 29/54] 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 30/54] 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 31/54] 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 32/54] 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 33/54] 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 34/54] 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 35/54] 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 36/54] 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 37/54] 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 c7570fbce516ad0b35def7d696c885fd80f347f1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 29 May 2022 15:15:03 +0900 Subject: [PATCH 38/54] 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 39/54] 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 40/54] 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 41/54] 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 42/54] 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 43/54] 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 44/54] 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 45/54] 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 46/54] 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 47/54] 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 48/54] 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 49/54] 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 50/54] 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 f65d2db77f4374d57694f1e15583dfa95f0575a9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 30 May 2022 17:54:09 +0900 Subject: [PATCH 51/54] 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 82a1ba1d46650bf11164e26537ddfa64ae995c84 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 30 May 2022 17:42:27 +0900 Subject: [PATCH 52/54] 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 53/54] 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 c0804803fd530beddd1f07ced27720b02ad92273 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 31 May 2022 18:09:26 +0900 Subject: [PATCH 54/54] 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,