diff --git a/osu.Android.props b/osu.Android.props
index ecc929a66d..116c7dbfcd 100644
--- a/osu.Android.props
+++ b/osu.Android.props
@@ -52,7 +52,7 @@
-
+
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.Rulesets.Osu/Mods/OsuModRandom.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs
index 23500f5da6..79ff222a89 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs
@@ -50,7 +50,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
diff --git a/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils.cs b/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils.cs
index da73c2addb..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));
@@ -146,5 +148,41 @@ 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, 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;
+
+ // 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);
+
+ 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, measured in radians, to rotate the vector by.
+ /// The rotated vector.
+ private static Vector2 rotateVector(Vector2 vector, float rotation)
+ {
+ float angle = MathF.Atan2(vector.Y, vector.X) + rotation;
+ float length = vector.Length;
+ return new Vector2(
+ 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 d1bc3b45df..a77d1f8b0f 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;
@@ -37,15 +38,23 @@ 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;
- positionInfos.Add(new ObjectPositionInfo(hitObject)
+ ObjectPositionInfo positionInfo;
+ positionInfos.Add(positionInfo = new ObjectPositionInfo(hitObject)
{
RelativeAngle = relativeAngle,
DistanceFromPrevious = relativePosition.Length
});
+ if (hitObject is Slider slider)
+ {
+ float absoluteRotation = getSliderRotation(slider);
+ positionInfo.Rotation = absoluteRotation - absoluteAngle;
+ absoluteAngle = absoluteRotation;
+ }
+
previousPosition = hitObject.EndPosition;
previousAngle = absoluteAngle;
}
@@ -70,7 +79,7 @@ namespace osu.Game.Rulesets.Osu.Utils
if (hitObject is Spinner)
{
- previous = null;
+ previous = current;
continue;
}
@@ -124,16 +133,23 @@ 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 = 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;
@@ -141,6 +157,19 @@ namespace osu.Game.Rulesets.Osu.Utils
posRelativeToPrev = RotateAwayFromEdge(lastEndPosition, posRelativeToPrev);
current.PositionModified = lastEndPosition + posRelativeToPrev;
+
+ if (!(current.HitObject is Slider slider))
+ return;
+
+ 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 = MathF.Atan2(centreOfMassModified.Y, centreOfMassModified.X) - MathF.Atan2(centreOfMassOriginal.Y, centreOfMassOriginal.X);
+ if (!Precision.AlmostEquals(relativeRotation, 0))
+ RotateSlider(slider, relativeRotation);
}
///
@@ -172,13 +201,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);
@@ -287,6 +316,45 @@ 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)
+ {
+ 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 += sample_step)
+ {
+ sum += slider.Path.PositionAt(i / pathDistance);
+ count++;
+ }
+
+ return sum / count;
+ }
+
+ ///
+ /// 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.Path.PositionAt(1);
+ return MathF.Atan2(endPositionVector.Y, endPositionVector.X);
+ }
+
public class ObjectPositionInfo
{
///
@@ -309,6 +377,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 the end of its path, 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 .
///
diff --git a/osu.Game.Tests/Visual/Editing/TestSceneTimelineHitObjectBlueprint.cs b/osu.Game.Tests/Visual/Editing/TestSceneTimelineHitObjectBlueprint.cs
index e6fad33a51..d55852ec43 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;
+
+ 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,
}
});
}
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);
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/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/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/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
index 4d1dee1650..4edbb9f215 100644
--- a/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs
+++ b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs
@@ -1,19 +1,22 @@
-// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
-using System.Collections.Generic;
using System.Linq;
+using System.Collections.Generic;
using System.Net;
+using System.Threading;
using JetBrains.Annotations;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
-using osu.Framework.Graphics.UserInterface;
using osu.Framework.Input;
+using osu.Framework.Logging;
using osu.Framework.Testing;
+using osu.Framework.Utils;
+using osu.Game.Configuration;
using osu.Game.Graphics.UserInterface;
using osu.Game.Online.API;
using osu.Game.Online.API.Requests;
@@ -21,387 +24,223 @@ 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 osu.Game.Overlays.Chat.Listing;
+using osu.Game.Overlays.Chat.ChannelList;
+using osuTK;
using osuTK.Input;
namespace osu.Game.Tests.Visual.Online
{
+ [TestFixture]
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 APIUser testUser;
+ private Channel testPMChannel;
+ private Channel[] testChannels;
- 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];
+ private Channel testChannel1 => testChannels[0];
+ private Channel testChannel2 => testChannels[1];
- [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();
- }
+ [Resolved]
+ private OsuConfigManager config { get; set; } = null!;
[SetUp]
- public void Setup()
+ public void SetUp() => Schedule(() =>
{
- Schedule(() =>
+ testUser = new APIUser { Username = "test user", Id = 5071479 };
+ testPMChannel = new Channel(testUser);
+ testChannels = Enumerable.Range(1, 10).Select(createPublicChannel).ToArray();
+
+ Child = new DependencyProvidingContainer
{
- ChannelManagerContainer container;
-
- Child = container = new ChannelManagerContainer(channels)
+ RelativeSizeAxes = Axes.Both,
+ CachedDependencies = new (Type, object)[]
{
- RelativeSizeAxes = Axes.Both,
- };
-
- chatOverlay = container.ChatOverlay;
- channelManager = container.ChannelManager;
- });
- }
+ (typeof(ChannelManager), channelManager = new ChannelManager()),
+ },
+ Children = new Drawable[]
+ {
+ channelManager,
+ chatOverlay = new TestChatOverlay(),
+ },
+ };
+ });
[SetUpSteps]
public void SetUpSteps()
{
- AddStep("register request handling", () =>
+ AddStep("Setup request handler", () =>
{
- onGetMessages = null;
-
((DummyAPIAccess)API).HandleRequest = req =>
{
switch (req)
{
+ case GetUpdatesRequest getUpdates:
+ getUpdates.TriggerFailure(new WebException());
+ return true;
+
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());
- }
-
+ case LeaveChannelRequest leaveChannel:
+ leaveChannel.TriggerSuccess();
return true;
case GetMessagesRequest getMessages:
- var messages = onGetMessages?.Invoke(getMessages.Channel);
- if (messages != null)
- getMessages.TriggerSuccess(messages);
+ getMessages.TriggerSuccess(createChannelMessages(getMessages.Channel));
return true;
- }
- return false;
+ case GetUserRequest getUser:
+ if (getUser.Lookup == testUser.Username)
+ getUser.TriggerSuccess(testUser);
+ else
+ getUser.TriggerFailure(new WebException());
+ return true;
+
+ case PostMessageRequest postMessage:
+ postMessage.TriggerSuccess(new Message(RNG.Next(0, 10000000))
+ {
+ Content = postMessage.Message.Content,
+ ChannelId = postMessage.Message.ChannelId,
+ Sender = postMessage.Message.Sender,
+ Timestamp = new DateTimeOffset(DateTime.Now),
+ });
+ return true;
+
+ default:
+ Logger.Log($"Unhandled Request Type: {req.GetType()}");
+ return false;
+ }
};
});
+
+ AddStep("Add test channels", () =>
+ {
+ (channelManager.AvailableChannels as BindableList)?.AddRange(testChannels);
+ });
}
[Test]
- public void TestHideOverlay()
+ public void TestBasic()
{
- AddStep("Open chat overlay", () => chatOverlay.Show());
+ AddStep("Show overlay with channel", () =>
+ {
+ chatOverlay.Show();
+ Channel joinedChannel = channelManager.JoinChannel(testChannel1);
+ channelManager.CurrentChannel.Value = joinedChannel;
+ });
+ AddAssert("Overlay is visible", () => chatOverlay.State.Value == Visibility.Visible);
+ waitForChannel1Visible();
+ }
- AddAssert("Chat overlay is visible", () => chatOverlay.State.Value == Visibility.Visible);
- AddAssert("Selector is visible", () => chatOverlay.SelectionOverlayState == Visibility.Visible);
+ [Test]
+ public void TestShowHide()
+ {
+ AddStep("Show overlay", () => chatOverlay.Show());
+ AddAssert("Overlay is visible", () => chatOverlay.State.Value == Visibility.Visible);
+ AddStep("Hide overlay", () => chatOverlay.Hide());
+ AddAssert("Overlay is hidden", () => chatOverlay.State.Value == Visibility.Hidden);
+ }
- AddStep("Close chat overlay", () => chatOverlay.Hide());
+ [Test]
+ public void TestChatHeight()
+ {
+ BindableFloat configChatHeight = new BindableFloat();
- AddAssert("Chat overlay was hidden", () => chatOverlay.State.Value == Visibility.Hidden);
- AddAssert("Channel selection overlay was hidden", () => chatOverlay.SelectionOverlayState == Visibility.Hidden);
+ float newHeight = 0;
+
+ 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", () =>
+ {
+ InputManager.MoveMouseTo(chatOverlayTopBar);
+ InputManager.PressButton(MouseButton.Left);
+ });
+ AddStep("Drag overlay to new height", () => InputManager.MoveMouseTo(chatOverlayTopBar, new Vector2(0, -300)));
+ AddStep("Stop dragging", () => InputManager.ReleaseButton(MouseButton.Left));
+ AddStep("Store new height", () => newHeight = chatOverlay.Height);
+ AddAssert("Config height changed", () => !configChatHeight.IsDefault && configChatHeight.Value == newHeight);
+ AddStep("Hide overlay", () => chatOverlay.Hide());
+ AddStep("Show overlay", () => chatOverlay.Show());
+ AddAssert("Overlay uses new height", () => chatOverlay.Height == newHeight);
}
[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);
+ AddStep("Show overlay", () => chatOverlay.Show());
+ AddAssert("Listing is visible", () => listingIsVisible);
+ AddStep("Join channel 1", () => channelManager.JoinChannel(testChannel1));
+ AddStep("Select channel 1", () => clickDrawable(getChannelListItem(testChannel1)));
+ waitForChannel1Visible();
}
[Test]
- public void TestSearchInSelector()
+ public void TestSearchInListing()
{
- AddStep("Open chat overlay", () => chatOverlay.Show());
- AddStep("Search for 'no. 2'", () => chatOverlay.ChildrenOfType().First().Text = "no. 2");
+ AddStep("Show overlay", () => chatOverlay.Show());
+ AddAssert("Listing is visible", () => listingIsVisible);
+ AddStep("Search for 'number 2'", () => chatOverlayTextBox.Text = "number 2");
AddUntilStep("Only channel 2 visible", () =>
{
- var listItems = chatOverlay.ChildrenOfType().Where(c => c.IsPresent);
- return listItems.Count() == 1 && listItems.Single().Channel == channel2;
+ IEnumerable listingItems = chatOverlay.ChildrenOfType()
+ .Where(item => item.IsPresent);
+ return listingItems.Count() == 1 && listingItems.Single().Channel == testChannel2;
});
}
- [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", () =>
+ AddStep("Show overlay", () => chatOverlay.Show());
+ AddStep("Join PM and public channels", () =>
{
- channelManager.JoinChannel(channel1);
- channelManager.JoinChannel(channel2);
+ channelManager.JoinChannel(testChannel1);
+ channelManager.JoinChannel(testPMChannel);
});
-
- // 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", () =>
+ AddStep("Select PM channel", () => clickDrawable(getChannelListItem(testPMChannel)));
+ AddStep("Click close button", () =>
{
- channelManager.JoinChannel(channel1);
- channelManager.JoinChannel(channel2);
+ ChannelListItemCloseButton closeButton = getChannelListItem(testPMChannel).ChildrenOfType().Single();
+ clickDrawable(closeButton);
});
-
- // 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", () =>
+ AddAssert("PM channel closed", () => !channelManager.JoinedChannels.Contains(testPMChannel));
+ AddStep("Select normal channel", () => clickDrawable(getChannelListItem(testChannel1)));
+ AddStep("Click close button", () =>
{
- channelManager.JoinChannel(channel1);
- channelManager.JoinChannel(channel2);
+ ChannelListItemCloseButton closeButton = getChannelListItem(testChannel1).ChildrenOfType().Single();
+ clickDrawable(closeButton);
});
-
- // 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);
+ AddAssert("Normal channel closed", () => !channelManager.JoinedChannels.Contains(testChannel1));
}
[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"));
+ AddStep("Show overlay", () => chatOverlay.Show());
+ AddStep("Join channel 1", () => channelManager.JoinChannel(testChannel1));
+ AddStep("Select channel 1", () => clickDrawable(getChannelListItem(testChannel1)));
+ AddStep("Open chat with user", () => channelManager.PostCommand($"chat {testUser.Username}"));
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"));
+ channelManager.CurrentChannel.Value.Type == ChannelType.PM && channelManager.CurrentChannel.Value.Users.Single() == testUser);
+ AddStep("Open chat with non-existent user", () => channelManager.PostCommand("chat user_doesnt_exist"));
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("Select channel 1", () => clickDrawable(getChannelListItem(testChannel1)));
AddStep("Unregister request handling", () => ((DummyAPIAccess)API).HandleRequest = null);
- AddStep("Open chat with user", () => channelManager.PostCommand("chat some body"));
+ AddStep("Open chat with user", () => channelManager.PostCommand($"chat {testUser.Username}"));
AddAssert("PM channel is selected", () =>
- channelManager.CurrentChannel.Value.Type == ChannelType.PM && channelManager.CurrentChannel.Value.Users.Single().Username == "some body");
+ channelManager.CurrentChannel.Value.Type == ChannelType.PM && channelManager.CurrentChannel.Value.Users.Single() == testUser);
}
[Test]
@@ -409,20 +248,17 @@ namespace osu.Game.Tests.Visual.Online
{
Channel multiplayerChannel = null;
- AddStep("open chat overlay", () => chatOverlay.Show());
-
- AddStep("join multiplayer channel", () => channelManager.JoinChannel(multiplayerChannel = new Channel(new APIUser())
+ AddStep("Show 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));
+ AddAssert("Channel is joined", () => channelManager.JoinedChannels.Contains(multiplayerChannel));
+ AddUntilStep("Channel not present in listing", () => !chatOverlay.ChildrenOfType()
+ .Where(item => item.IsPresent)
+ .Select(item => item.Channel)
+ .Contains(multiplayerChannel));
}
[Test]
@@ -430,26 +266,21 @@ namespace osu.Game.Tests.Visual.Online
{
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("Show overlay", () => chatOverlay.Show());
+ AddStep("Join channel 1", () => channelManager.JoinChannel(testChannel1));
+ AddStep("Select channel 1", () => clickDrawable(getChannelListItem(testChannel1)));
AddStep("Send message in channel 1", () =>
{
- channel1.AddNewMessages(message = new Message
+ testChannel1.AddNewMessages(message = new Message
{
- ChannelId = channel1.Id,
+ ChannelId = testChannel1.Id,
Content = "Message to highlight!",
Timestamp = DateTimeOffset.Now,
- Sender = new APIUser
- {
- Id = 2,
- Username = "Someone",
- }
+ Sender = testUser,
});
});
-
- AddStep("Highlight message", () => chatOverlay.HighlightMessage(message, channel1));
+ AddStep("Highlight message", () => chatOverlay.HighlightMessage(message, testChannel1));
+ waitForChannel1Visible();
}
[Test]
@@ -457,28 +288,22 @@ namespace osu.Game.Tests.Visual.Online
{
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("Show overlay", () => chatOverlay.Show());
+ AddStep("Join channel 1", () => channelManager.JoinChannel(testChannel1));
+ AddStep("Join channel 2", () => channelManager.JoinChannel(testChannel2));
+ AddStep("Select channel 1", () => clickDrawable(getChannelListItem(testChannel1)));
AddStep("Send message in channel 2", () =>
{
- channel2.AddNewMessages(message = new Message
+ testChannel2.AddNewMessages(message = new Message
{
- ChannelId = channel2.Id,
+ ChannelId = testChannel2.Id,
Content = "Message to highlight!",
Timestamp = DateTimeOffset.Now,
- Sender = new APIUser
- {
- Id = 2,
- Username = "Someone",
- }
+ Sender = testUser,
});
});
-
- AddStep("Highlight message", () => chatOverlay.HighlightMessage(message, channel2));
- AddAssert("Switched to channel 2", () => channelManager.CurrentChannel.Value == channel2);
+ AddStep("Highlight message", () => chatOverlay.HighlightMessage(message, testChannel2));
+ waitForChannel2Visible();
}
[Test]
@@ -486,30 +311,23 @@ namespace osu.Game.Tests.Visual.Online
{
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("Show overlay", () => chatOverlay.Show());
+ AddStep("Join channel 1", () => channelManager.JoinChannel(testChannel1));
+ AddStep("Join channel 2", () => channelManager.JoinChannel(testChannel2));
+ AddStep("Select channel 1", () => clickDrawable(getChannelListItem(testChannel1)));
AddStep("Send message in channel 2", () =>
{
- channel2.AddNewMessages(message = new Message
+ testChannel2.AddNewMessages(message = new Message
{
- ChannelId = channel2.Id,
+ ChannelId = testChannel2.Id,
Content = "Message to highlight!",
Timestamp = DateTimeOffset.Now,
- Sender = new APIUser
- {
- Id = 2,
- Username = "Someone",
- }
+ Sender = testUser,
});
});
- AddStep("Leave channel 2", () => channelManager.LeaveChannel(channel2));
-
- AddStep("Highlight message", () => chatOverlay.HighlightMessage(message, channel2));
- AddAssert("Switched to channel 2", () => channelManager.CurrentChannel.Value == channel2);
+ AddStep("Leave channel 2", () => channelManager.LeaveChannel(testChannel2));
+ AddStep("Highlight message", () => chatOverlay.HighlightMessage(message, testChannel2));
+ waitForChannel2Visible();
}
[Test]
@@ -517,24 +335,19 @@ namespace osu.Game.Tests.Visual.Online
{
Message message = null;
- AddStep("Join channel 1", () => channelManager.JoinChannel(channel1));
-
+ AddStep("Join channel 1", () => channelManager.JoinChannel(testChannel1));
AddStep("Send message in channel 1", () =>
{
- channel1.AddNewMessages(message = new Message
+ testChannel1.AddNewMessages(message = new Message
{
- ChannelId = channel1.Id,
+ ChannelId = testChannel1.Id,
Content = "Message to highlight!",
Timestamp = DateTimeOffset.Now,
- Sender = new APIUser
- {
- Id = 2,
- Username = "Someone",
- }
+ Sender = testUser,
});
});
-
- AddStep("Highlight message", () => chatOverlay.HighlightMessage(message, channel1));
+ AddStep("Highlight message", () => chatOverlay.HighlightMessage(message, testChannel1));
+ waitForChannel1Visible();
}
[Test]
@@ -542,40 +355,167 @@ namespace osu.Game.Tests.Visual.Online
{
Message message = null;
- AddStep("Join channel 1", () => channelManager.JoinChannel(channel1));
-
+ AddStep("Join channel 1", () => channelManager.JoinChannel(testChannel1));
AddStep("Send message in channel 1", () =>
{
- channel1.AddNewMessages(message = new Message
+ testChannel1.AddNewMessages(message = new Message
{
- ChannelId = channel1.Id,
+ ChannelId = testChannel1.Id,
Content = "Message to highlight!",
Timestamp = DateTimeOffset.Now,
- Sender = new APIUser
- {
- Id = 2,
- Username = "Someone",
- }
+ Sender = testUser,
});
});
-
AddStep("Set null channel", () => channelManager.CurrentChannel.Value = null);
- AddStep("Highlight message", () => chatOverlay.HighlightMessage(message, channel1));
+ AddStep("Highlight message", () => chatOverlay.HighlightMessage(message, testChannel1));
+ waitForChannel1Visible();
}
- private void pressChannelHotkey(int number)
+ [Test]
+ public void TestTextBoxRetainsFocus()
{
- var channelKey = Key.Number0 + number;
- InputManager.PressKey(Key.AltLeft);
- InputManager.Key(channelKey);
- InputManager.ReleaseKey(Key.AltLeft);
+ AddStep("Show overlay", () => chatOverlay.Show());
+ AddAssert("TextBox is focused", () => InputManager.FocusedDrawable == chatOverlayTextBox);
+ AddStep("Join channel 1", () => channelManager.JoinChannel(testChannel1));
+ AddStep("Select channel 1", () => clickDrawable(getChannelListItem(testChannel1)));
+ waitForChannel1Visible();
+ AddAssert("TextBox is focused", () => InputManager.FocusedDrawable == chatOverlayTextBox);
+ AddStep("Click drawable channel", () => clickDrawable(currentDrawableChannel));
+ AddAssert("TextBox is focused", () => InputManager.FocusedDrawable == chatOverlayTextBox);
+ AddStep("Click selector", () => clickDrawable(channelSelectorButton));
+ AddAssert("TextBox is focused", () => InputManager.FocusedDrawable == chatOverlayTextBox);
+ AddStep("Click listing", () => clickDrawable(chatOverlay.ChildrenOfType().Single()));
+ AddAssert("TextBox is focused", () => InputManager.FocusedDrawable == chatOverlayTextBox);
+ AddStep("Click channel list", () => clickDrawable(chatOverlay.ChildrenOfType().Single()));
+ AddAssert("TextBox is focused", () => InputManager.FocusedDrawable == chatOverlayTextBox);
+ AddStep("Click top bar", () => clickDrawable(chatOverlay.ChildrenOfType().Single()));
+ AddAssert("TextBox is focused", () => InputManager.FocusedDrawable == chatOverlayTextBox);
+ AddStep("Hide overlay", () => chatOverlay.Hide());
+ AddAssert("TextBox is not focused", () => InputManager.FocusedDrawable == null);
}
- private void pressCloseDocumentKeys() => InputManager.Keys(PlatformAction.DocumentClose);
+ [Test]
+ public void TestSlowLoadingChannel()
+ {
+ AddStep("Show overlay (slow-loading)", () =>
+ {
+ chatOverlay.Show();
+ chatOverlay.SlowLoading = true;
+ });
+ AddStep("Join channel 1", () => channelManager.JoinChannel(testChannel1));
+ AddStep("Select channel 1", () => clickDrawable(getChannelListItem(testChannel1)));
+ AddUntilStep("Channel 1 loading", () => !channelIsVisible && chatOverlay.GetSlowLoadingChannel(testChannel1).LoadState == LoadState.Loading);
- private void pressNewTabKeys() => InputManager.Keys(PlatformAction.TabNew);
+ AddStep("Join channel 2", () => channelManager.JoinChannel(testChannel2));
+ AddStep("Select channel 2", () => clickDrawable(getChannelListItem(testChannel2)));
+ AddUntilStep("Channel 2 loading", () => !channelIsVisible && chatOverlay.GetSlowLoadingChannel(testChannel2).LoadState == LoadState.Loading);
- private void pressRestoreTabKeys() => InputManager.Keys(PlatformAction.TabRestore);
+ AddStep("Finish channel 1 load", () => chatOverlay.GetSlowLoadingChannel(testChannel1).LoadEvent.Set());
+ 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());
+ AddUntilStep("Channel 2 loaded", () => chatOverlay.GetSlowLoadingChannel(testChannel2).IsLoaded);
+ waitForChannel2Visible();
+
+ AddStep("Select channel 1", () => clickDrawable(getChannelListItem(testChannel1)));
+ AddUntilStep("Channel 1 loaded", () => chatOverlay.GetSlowLoadingChannel(testChannel1).IsLoaded);
+ waitForChannel1Visible();
+ }
+
+ [Test]
+ public void TestKeyboardCloseAndRestoreChannel()
+ {
+ AddStep("Show overlay with channel 1", () =>
+ {
+ channelManager.JoinChannel(testChannel1);
+ chatOverlay.Show();
+ });
+ waitForChannel1Visible();
+ AddStep("Press document close keys", () => InputManager.Keys(PlatformAction.DocumentClose));
+ AddAssert("Listing is visible", () => listingIsVisible);
+
+ AddStep("Press tab restore keys", () => InputManager.Keys(PlatformAction.TabRestore));
+ waitForChannel1Visible();
+ }
+
+ [Test]
+ public void TestKeyboardNewChannel()
+ {
+ AddStep("Show overlay with channel 1", () =>
+ {
+ channelManager.JoinChannel(testChannel1);
+ chatOverlay.Show();
+ });
+ waitForChannel1Visible();
+ AddStep("Press tab new keys", () => InputManager.Keys(PlatformAction.TabNew));
+ AddAssert("Listing is visible", () => listingIsVisible);
+ }
+
+ [Test]
+ public void TestKeyboardNextChannel()
+ {
+ Channel announceChannel = createAnnounceChannel();
+ Channel pmChannel1 = createPrivateChannel();
+ Channel pmChannel2 = createPrivateChannel();
+
+ AddStep("Show overlay with channels", () =>
+ {
+ channelManager.JoinChannel(testChannel1);
+ channelManager.JoinChannel(testChannel2);
+ channelManager.JoinChannel(pmChannel1);
+ channelManager.JoinChannel(pmChannel2);
+ channelManager.JoinChannel(announceChannel);
+ chatOverlay.Show();
+ });
+
+ waitForChannel1Visible();
+ AddStep("Press document next keys", () => InputManager.Keys(PlatformAction.DocumentNext));
+ waitForChannel2Visible();
+
+ AddStep("Press document next keys", () => InputManager.Keys(PlatformAction.DocumentNext));
+ AddUntilStep("PM Channel 1 displayed", () => channelIsVisible && currentDrawableChannel?.Channel == pmChannel1);
+
+ AddStep("Press document next keys", () => InputManager.Keys(PlatformAction.DocumentNext));
+ AddUntilStep("PM Channel 2 displayed", () => channelIsVisible && currentDrawableChannel?.Channel == pmChannel2);
+
+ AddStep("Press document next keys", () => InputManager.Keys(PlatformAction.DocumentNext));
+ AddUntilStep("Announce channel displayed", () => channelIsVisible && currentDrawableChannel?.Channel == announceChannel);
+
+ AddStep("Press document next keys", () => InputManager.Keys(PlatformAction.DocumentNext));
+ 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;
+
+ private bool loadingIsVisible =>
+ chatOverlay.ChildrenOfType().Single().State.Value == Visibility.Visible;
+
+ private bool channelIsVisible =>
+ !listingIsVisible && !loadingIsVisible;
+
+ [CanBeNull]
+ private DrawableChannel currentDrawableChannel =>
+ chatOverlay.ChildrenOfType().SingleOrDefault();
+
+ private ChannelListItem getChannelListItem(Channel channel) =>
+ chatOverlay.ChildrenOfType().Single(item => item.Channel == channel);
+
+ private ChatTextBox chatOverlayTextBox =>
+ chatOverlay.ChildrenOfType().Single();
+
+ private ChatOverlayTopBar chatOverlayTopBar =>
+ chatOverlay.ChildrenOfType().Single();
+
+ private ChannelListItem channelSelectorButton =>
+ chatOverlay.ChildrenOfType().Single(item => item.Channel is ChannelListing.ChannelListingChannel);
private void clickDrawable(Drawable d)
{
@@ -583,81 +523,75 @@ namespace osu.Game.Tests.Visual.Online
InputManager.Click(MouseButton.Left);
}
- private class ChannelManagerContainer : Container
+ private List createChannelMessages(Channel channel)
{
- public TestChatOverlay ChatOverlay { get; private set; }
-
- [Cached]
- public ChannelManager ChannelManager { get; } = new ChannelManager();
-
- private readonly List channels;
-
- public ChannelManagerContainer(List channels)
+ var message = new Message
+ {
+ ChannelId = channel.Id,
+ Content = $"Hello, this is a message in {channel.Name}",
+ Sender = testUser,
+ Timestamp = new DateTimeOffset(DateTime.Now),
+ };
+ return new List { message };
+ }
+
+ private Channel createPublicChannel(int id) => new Channel
+ {
+ Id = id,
+ Name = $"#channel-{id}",
+ Topic = $"We talk about the number {id} here",
+ Type = ChannelType.Public,
+ };
+
+ private Channel createPrivateChannel()
+ {
+ int id = RNG.Next(0, 10000);
+ return new Channel(new APIUser
+ {
+ Id = id,
+ Username = $"test user {id}",
+ });
+ }
+
+ private Channel createAnnounceChannel()
+ {
+ int id = RNG.Next(0, 10000);
+ return new Channel
+ {
+ Name = $"Announce {id}",
+ Type = ChannelType.Announce,
+ Id = id,
+ };
+ }
+
+ private class TestChatOverlay : ChatOverlay
+ {
+ public bool SlowLoading { get; set; }
+
+ public SlowLoadingDrawableChannel GetSlowLoadingChannel(Channel channel) => DrawableChannels.OfType().Single(c => c.Channel == channel);
+
+ protected override ChatOverlayDrawableChannel CreateDrawableChannel(Channel newChannel)
+ {
+ return SlowLoading
+ ? new SlowLoadingDrawableChannel(newChannel)
+ : new ChatOverlayDrawableChannel(newChannel);
+ }
+ }
+
+ private class SlowLoadingDrawableChannel : ChatOverlayDrawableChannel
+ {
+ public readonly ManualResetEventSlim LoadEvent = new ManualResetEventSlim();
+
+ public SlowLoadingDrawableChannel([NotNull] Channel channel)
+ : base(channel)
{
- this.channels = channels;
}
[BackgroundDependencyLoader]
private void load()
{
- ((BindableList)ChannelManager.AvailableChannels).AddRange(channels);
-
- InternalChildren = new Drawable[]
- {
- ChannelManager,
- ChatOverlay = new TestChatOverlay { RelativeSizeAxes = Axes.Both, },
- };
+ LoadEvent.Wait(10000);
}
}
-
- 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.Tests/Visual/Online/TestSceneChatOverlayV2.cs b/osu.Game.Tests/Visual/Online/TestSceneChatOverlayV2.cs
deleted file mode 100644
index 0580d20171..0000000000
--- a/osu.Game.Tests/Visual/Online/TestSceneChatOverlayV2.cs
+++ /dev/null
@@ -1,572 +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 System.Collections.Generic;
-using System.Net;
-using System.Threading;
-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.Input;
-using osu.Framework.Logging;
-using osu.Framework.Testing;
-using osu.Framework.Utils;
-using osu.Game.Configuration;
-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.Listing;
-using osu.Game.Overlays.Chat.ChannelList;
-using osuTK;
-using osuTK.Input;
-
-namespace osu.Game.Tests.Visual.Online
-{
- [TestFixture]
- public class TestSceneChatOverlayV2 : OsuManualInputManagerTestScene
- {
- private TestChatOverlayV2 chatOverlay;
- private ChannelManager channelManager;
-
- private APIUser testUser;
- private Channel testPMChannel;
- private Channel[] testChannels;
-
- private Channel testChannel1 => testChannels[0];
- private Channel testChannel2 => testChannels[1];
-
- [Resolved]
- private OsuConfigManager config { get; set; } = null!;
-
- [SetUp]
- public void SetUp() => Schedule(() =>
- {
- testUser = new APIUser { Username = "test user", Id = 5071479 };
- testPMChannel = new Channel(testUser);
- testChannels = Enumerable.Range(1, 10).Select(createPublicChannel).ToArray();
-
- Child = new DependencyProvidingContainer
- {
- RelativeSizeAxes = Axes.Both,
- CachedDependencies = new (Type, object)[]
- {
- (typeof(ChannelManager), channelManager = new ChannelManager()),
- },
- Children = new Drawable[]
- {
- channelManager,
- chatOverlay = new TestChatOverlayV2(),
- },
- };
- });
-
- [SetUpSteps]
- public void SetUpSteps()
- {
- AddStep("Setup request handler", () =>
- {
- ((DummyAPIAccess)API).HandleRequest = req =>
- {
- switch (req)
- {
- case GetUpdatesRequest getUpdates:
- getUpdates.TriggerFailure(new WebException());
- return true;
-
- case JoinChannelRequest joinChannel:
- joinChannel.TriggerSuccess();
- return true;
-
- case LeaveChannelRequest leaveChannel:
- leaveChannel.TriggerSuccess();
- return true;
-
- case GetMessagesRequest getMessages:
- getMessages.TriggerSuccess(createChannelMessages(getMessages.Channel));
- return true;
-
- case GetUserRequest getUser:
- if (getUser.Lookup == testUser.Username)
- getUser.TriggerSuccess(testUser);
- else
- getUser.TriggerFailure(new WebException());
- return true;
-
- case PostMessageRequest postMessage:
- postMessage.TriggerSuccess(new Message(RNG.Next(0, 10000000))
- {
- Content = postMessage.Message.Content,
- ChannelId = postMessage.Message.ChannelId,
- Sender = postMessage.Message.Sender,
- Timestamp = new DateTimeOffset(DateTime.Now),
- });
- return true;
-
- default:
- Logger.Log($"Unhandled Request Type: {req.GetType()}");
- return false;
- }
- };
- });
-
- AddStep("Add test channels", () =>
- {
- (channelManager.AvailableChannels as BindableList)?.AddRange(testChannels);
- });
- }
-
- [Test]
- public void TestBasic()
- {
- AddStep("Show overlay with channel", () =>
- {
- chatOverlay.Show();
- Channel joinedChannel = channelManager.JoinChannel(testChannel1);
- channelManager.CurrentChannel.Value = joinedChannel;
- });
- AddAssert("Overlay is visible", () => chatOverlay.State.Value == Visibility.Visible);
- AddUntilStep("Channel is visible", () => channelIsVisible && currentDrawableChannel.Channel == testChannel1);
- }
-
- [Test]
- public void TestShowHide()
- {
- AddStep("Show overlay", () => chatOverlay.Show());
- AddAssert("Overlay is visible", () => chatOverlay.State.Value == Visibility.Visible);
- AddStep("Hide overlay", () => chatOverlay.Hide());
- AddAssert("Overlay is hidden", () => chatOverlay.State.Value == Visibility.Hidden);
- }
-
- [Test]
- public void TestChatHeight()
- {
- BindableFloat configChatHeight = new BindableFloat();
- config.BindWith(OsuSetting.ChatDisplayHeight, configChatHeight);
- float newHeight = 0;
-
- AddStep("Reset config chat height", () => configChatHeight.SetDefault());
- AddStep("Show overlay", () => chatOverlay.Show());
- AddAssert("Overlay uses config height", () => chatOverlay.Height == configChatHeight.Default);
- AddStep("Click top bar", () =>
- {
- InputManager.MoveMouseTo(chatOverlayTopBar);
- InputManager.PressButton(MouseButton.Left);
- });
- AddStep("Drag overlay to new height", () => InputManager.MoveMouseTo(chatOverlayTopBar, new Vector2(0, -300)));
- AddStep("Stop dragging", () => InputManager.ReleaseButton(MouseButton.Left));
- AddStep("Store new height", () => newHeight = chatOverlay.Height);
- AddAssert("Config height changed", () => !configChatHeight.IsDefault && configChatHeight.Value == newHeight);
- AddStep("Hide overlay", () => chatOverlay.Hide());
- AddStep("Show overlay", () => chatOverlay.Show());
- AddAssert("Overlay uses new height", () => chatOverlay.Height == newHeight);
- }
-
- [Test]
- public void TestChannelSelection()
- {
- AddStep("Show overlay", () => chatOverlay.Show());
- 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);
- }
-
- [Test]
- public void TestSearchInListing()
- {
- AddStep("Show overlay", () => chatOverlay.Show());
- AddAssert("Listing is visible", () => listingIsVisible);
- AddStep("Search for 'number 2'", () => chatOverlayTextBox.Text = "number 2");
- AddUntilStep("Only channel 2 visibile", () =>
- {
- IEnumerable listingItems = chatOverlay.ChildrenOfType()
- .Where(item => item.IsPresent);
- return listingItems.Count() == 1 && listingItems.Single().Channel == testChannel2;
- });
- }
-
- [Test]
- public void TestChannelCloseButton()
- {
- AddStep("Show overlay", () => chatOverlay.Show());
- AddStep("Join PM and public channels", () =>
- {
- channelManager.JoinChannel(testChannel1);
- channelManager.JoinChannel(testPMChannel);
- });
- AddStep("Select PM channel", () => clickDrawable(getChannelListItem(testPMChannel)));
- AddStep("Click close button", () =>
- {
- ChannelListItemCloseButton closeButton = getChannelListItem(testPMChannel).ChildrenOfType().Single();
- clickDrawable(closeButton);
- });
- AddAssert("PM channel closed", () => !channelManager.JoinedChannels.Contains(testPMChannel));
- AddStep("Select normal channel", () => clickDrawable(getChannelListItem(testChannel1)));
- AddStep("Click close button", () =>
- {
- ChannelListItemCloseButton closeButton = getChannelListItem(testChannel1).ChildrenOfType().Single();
- clickDrawable(closeButton);
- });
- AddAssert("Normal channel closed", () => !channelManager.JoinedChannels.Contains(testChannel1));
- }
-
- [Test]
- public void TestChatCommand()
- {
- AddStep("Show overlay", () => chatOverlay.Show());
- AddStep("Join channel 1", () => channelManager.JoinChannel(testChannel1));
- AddStep("Select channel 1", () => clickDrawable(getChannelListItem(testChannel1)));
- AddStep("Open chat with user", () => channelManager.PostCommand($"chat {testUser.Username}"));
- AddAssert("PM channel is selected", () =>
- channelManager.CurrentChannel.Value.Type == ChannelType.PM && channelManager.CurrentChannel.Value.Users.Single() == testUser);
- AddStep("Open chat with non-existent user", () => channelManager.PostCommand("chat user_doesnt_exist"));
- 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(getChannelListItem(testChannel1)));
- AddStep("Unregister request handling", () => ((DummyAPIAccess)API).HandleRequest = null);
- AddStep("Open chat with user", () => channelManager.PostCommand($"chat {testUser.Username}"));
- AddAssert("PM channel is selected", () =>
- channelManager.CurrentChannel.Value.Type == ChannelType.PM && channelManager.CurrentChannel.Value.Users.Single() == testUser);
- }
-
- [Test]
- public void TestMultiplayerChannelIsNotShown()
- {
- Channel multiplayerChannel = null;
-
- AddStep("Show overlay", () => chatOverlay.Show());
- AddStep("Join multiplayer channel", () => channelManager.JoinChannel(multiplayerChannel = new Channel(new APIUser())
- {
- Name = "#mp_1",
- Type = ChannelType.Multiplayer,
- }));
- AddAssert("Channel is joined", () => channelManager.JoinedChannels.Contains(multiplayerChannel));
- AddUntilStep("Channel not present in listing", () => !chatOverlay.ChildrenOfType()
- .Where(item => item.IsPresent)
- .Select(item => item.Channel)
- .Contains(multiplayerChannel));
- }
-
- [Test]
- public void TestHighlightOnCurrentChannel()
- {
- Message message = null;
-
- AddStep("Show overlay", () => chatOverlay.Show());
- AddStep("Join channel 1", () => channelManager.JoinChannel(testChannel1));
- AddStep("Select channel 1", () => clickDrawable(getChannelListItem(testChannel1)));
- AddStep("Send message in channel 1", () =>
- {
- testChannel1.AddNewMessages(message = new Message
- {
- ChannelId = testChannel1.Id,
- Content = "Message to highlight!",
- Timestamp = DateTimeOffset.Now,
- Sender = testUser,
- });
- });
- AddStep("Highlight message", () => chatOverlay.HighlightMessage(message, testChannel1));
- AddUntilStep("Channel 1 is visible", () => channelIsVisible && currentDrawableChannel.Channel == testChannel1);
- }
-
- [Test]
- public void TestHighlightOnAnotherChannel()
- {
- Message message = null;
-
- AddStep("Show overlay", () => chatOverlay.Show());
- AddStep("Join channel 1", () => channelManager.JoinChannel(testChannel1));
- AddStep("Join channel 2", () => channelManager.JoinChannel(testChannel2));
- AddStep("Select channel 1", () => clickDrawable(getChannelListItem(testChannel1)));
- AddStep("Send message in channel 2", () =>
- {
- testChannel2.AddNewMessages(message = new Message
- {
- ChannelId = testChannel2.Id,
- Content = "Message to highlight!",
- Timestamp = DateTimeOffset.Now,
- Sender = testUser,
- });
- });
- AddStep("Highlight message", () => chatOverlay.HighlightMessage(message, testChannel2));
- AddUntilStep("Channel 2 is visible", () => channelIsVisible && currentDrawableChannel.Channel == testChannel2);
- }
-
- [Test]
- public void TestHighlightOnLeftChannel()
- {
- Message message = null;
-
- AddStep("Show overlay", () => chatOverlay.Show());
- AddStep("Join channel 1", () => channelManager.JoinChannel(testChannel1));
- AddStep("Join channel 2", () => channelManager.JoinChannel(testChannel2));
- AddStep("Select channel 1", () => clickDrawable(getChannelListItem(testChannel1)));
- AddStep("Send message in channel 2", () =>
- {
- testChannel2.AddNewMessages(message = new Message
- {
- ChannelId = testChannel2.Id,
- Content = "Message to highlight!",
- Timestamp = DateTimeOffset.Now,
- Sender = testUser,
- });
- });
- AddStep("Leave channel 2", () => channelManager.LeaveChannel(testChannel2));
- AddStep("Highlight message", () => chatOverlay.HighlightMessage(message, testChannel2));
- AddUntilStep("Channel 2 is visible", () => channelIsVisible && currentDrawableChannel.Channel == testChannel2);
- }
-
- [Test]
- public void TestHighlightWhileChatNeverOpen()
- {
- Message message = null;
-
- AddStep("Join channel 1", () => channelManager.JoinChannel(testChannel1));
- AddStep("Send message in channel 1", () =>
- {
- testChannel1.AddNewMessages(message = new Message
- {
- ChannelId = testChannel1.Id,
- Content = "Message to highlight!",
- Timestamp = DateTimeOffset.Now,
- Sender = testUser,
- });
- });
- AddStep("Highlight message", () => chatOverlay.HighlightMessage(message, testChannel1));
- AddUntilStep("Channel 1 is visible", () => channelIsVisible && currentDrawableChannel.Channel == testChannel1);
- }
-
- [Test]
- public void TestHighlightWithNullChannel()
- {
- Message message = null;
-
- AddStep("Join channel 1", () => channelManager.JoinChannel(testChannel1));
- AddStep("Send message in channel 1", () =>
- {
- testChannel1.AddNewMessages(message = new Message
- {
- ChannelId = testChannel1.Id,
- Content = "Message to highlight!",
- Timestamp = DateTimeOffset.Now,
- Sender = testUser,
- });
- });
- AddStep("Set null channel", () => channelManager.CurrentChannel.Value = null);
- AddStep("Highlight message", () => chatOverlay.HighlightMessage(message, testChannel1));
- AddUntilStep("Channel 1 is visible", () => channelIsVisible && currentDrawableChannel.Channel == testChannel1);
- }
-
- [Test]
- public void TestTextBoxRetainsFocus()
- {
- AddStep("Show overlay", () => chatOverlay.Show());
- AddAssert("TextBox is focused", () => InputManager.FocusedDrawable == chatOverlayTextBox);
- AddStep("Join channel 1", () => channelManager.JoinChannel(testChannel1));
- AddStep("Select channel 1", () => clickDrawable(getChannelListItem(testChannel1)));
- AddAssert("TextBox is focused", () => InputManager.FocusedDrawable == chatOverlayTextBox);
- AddStep("Click drawable channel", () => clickDrawable(currentDrawableChannel));
- AddAssert("TextBox is focused", () => InputManager.FocusedDrawable == chatOverlayTextBox);
- AddStep("Click selector", () => clickDrawable(channelSelectorButton));
- AddAssert("TextBox is focused", () => InputManager.FocusedDrawable == chatOverlayTextBox);
- AddStep("Click listing", () => clickDrawable(chatOverlay.ChildrenOfType().Single()));
- AddAssert("TextBox is focused", () => InputManager.FocusedDrawable == chatOverlayTextBox);
- AddStep("Click channel list", () => clickDrawable(chatOverlay.ChildrenOfType().Single()));
- AddAssert("TextBox is focused", () => InputManager.FocusedDrawable == chatOverlayTextBox);
- AddStep("Click top bar", () => clickDrawable(chatOverlay.ChildrenOfType().Single()));
- AddAssert("TextBox is focused", () => InputManager.FocusedDrawable == chatOverlayTextBox);
- AddStep("Hide overlay", () => chatOverlay.Hide());
- AddAssert("TextBox is not focused", () => InputManager.FocusedDrawable == null);
- }
-
- [Test]
- public void TestSlowLoadingChannel()
- {
- AddStep("Show overlay (slow-loading)", () =>
- {
- chatOverlay.Show();
- chatOverlay.SlowLoading = true;
- });
- AddStep("Join channel 1", () => channelManager.JoinChannel(testChannel1));
- AddStep("Select channel 1", () => clickDrawable(getChannelListItem(testChannel1)));
- AddAssert("Channel 1 loading", () => !channelIsVisible && chatOverlay.GetSlowLoadingChannel(testChannel1).LoadState == LoadState.Loading);
-
- AddStep("Join channel 2", () => channelManager.JoinChannel(testChannel2));
- AddStep("Select channel 2", () => clickDrawable(getChannelListItem(testChannel2)));
- AddAssert("Channel 2 loading", () => !channelIsVisible && chatOverlay.GetSlowLoadingChannel(testChannel2).LoadState == LoadState.Loading);
-
- AddStep("Finish channel 1 load", () => chatOverlay.GetSlowLoadingChannel(testChannel1).LoadEvent.Set());
- AddAssert("Channel 1 ready", () => chatOverlay.GetSlowLoadingChannel(testChannel1).LoadState == LoadState.Ready);
- AddAssert("Channel 1 not displayed", () => !channelIsVisible);
-
- AddStep("Finish channel 2 load", () => chatOverlay.GetSlowLoadingChannel(testChannel2).LoadEvent.Set());
- AddAssert("Channel 2 loaded", () => chatOverlay.GetSlowLoadingChannel(testChannel2).IsLoaded);
- AddAssert("Channel 2 displayed", () => channelIsVisible && currentDrawableChannel.Channel == testChannel2);
-
- AddStep("Select channel 1", () => clickDrawable(getChannelListItem(testChannel1)));
- AddAssert("Channel 1 loaded", () => chatOverlay.GetSlowLoadingChannel(testChannel1).IsLoaded);
- AddAssert("Channel 1 displayed", () => channelIsVisible && currentDrawableChannel.Channel == testChannel1);
- }
-
- [Test]
- public void TestKeyboardCloseAndRestoreChannel()
- {
- AddStep("Show overlay with channel 1", () =>
- {
- channelManager.JoinChannel(testChannel1);
- chatOverlay.Show();
- });
- AddAssert("Channel 1 displayed", () => channelIsVisible && currentDrawableChannel.Channel == testChannel1);
-
- AddStep("Press document close keys", () => InputManager.Keys(PlatformAction.DocumentClose));
- AddAssert("Listing is visible", () => listingIsVisible);
-
- AddStep("Press tab restore keys", () => InputManager.Keys(PlatformAction.TabRestore));
- AddAssert("Channel 1 displayed", () => channelIsVisible && currentDrawableChannel.Channel == testChannel1);
- }
-
- [Test]
- public void TestKeyboardNewChannel()
- {
- AddStep("Show overlay with channel 1", () =>
- {
- channelManager.JoinChannel(testChannel1);
- chatOverlay.Show();
- });
- AddAssert("Channel 1 displayed", () => channelIsVisible && currentDrawableChannel.Channel == testChannel1);
-
- AddStep("Press tab new keys", () => InputManager.Keys(PlatformAction.TabNew));
- AddAssert("Listing is visible", () => listingIsVisible);
- }
-
- [Test]
- public void TestKeyboardNextChannel()
- {
- Channel pmChannel1 = createPrivateChannel();
- Channel pmChannel2 = createPrivateChannel();
-
- AddStep("Show overlay with channels", () =>
- {
- channelManager.JoinChannel(testChannel1);
- channelManager.JoinChannel(testChannel2);
- channelManager.JoinChannel(pmChannel1);
- channelManager.JoinChannel(pmChannel2);
- chatOverlay.Show();
- });
-
- AddAssert("Channel 1 displayed", () => channelIsVisible && currentDrawableChannel.Channel == testChannel1);
-
- AddStep("Press document next keys", () => InputManager.Keys(PlatformAction.DocumentNext));
- AddAssert("Channel 2 displayed", () => channelIsVisible && currentDrawableChannel.Channel == testChannel2);
-
- AddStep("Press document next keys", () => InputManager.Keys(PlatformAction.DocumentNext));
- AddAssert("PM Channel 1 displayed", () => channelIsVisible && currentDrawableChannel.Channel == pmChannel1);
-
- AddStep("Press document next keys", () => InputManager.Keys(PlatformAction.DocumentNext));
- AddAssert("PM Channel 2 displayed", () => channelIsVisible && currentDrawableChannel.Channel == pmChannel2);
-
- AddStep("Press document next keys", () => InputManager.Keys(PlatformAction.DocumentNext));
- AddAssert("Channel 1 displayed", () => channelIsVisible && currentDrawableChannel.Channel == testChannel1);
- }
-
- private bool listingIsVisible =>
- chatOverlay.ChildrenOfType().Single().State.Value == Visibility.Visible;
-
- private bool loadingIsVisible =>
- chatOverlay.ChildrenOfType().Single().State.Value == Visibility.Visible;
-
- private bool channelIsVisible =>
- !listingIsVisible && !loadingIsVisible;
-
- private DrawableChannel currentDrawableChannel =>
- chatOverlay.ChildrenOfType().Single();
-
- private ChannelListItem getChannelListItem(Channel channel) =>
- chatOverlay.ChildrenOfType().Single(item => item.Channel == channel);
-
- private ChatTextBox chatOverlayTextBox =>
- chatOverlay.ChildrenOfType().Single();
-
- private ChatOverlayTopBar chatOverlayTopBar =>
- chatOverlay.ChildrenOfType().Single();
-
- private ChannelListItem channelSelectorButton =>
- chatOverlay.ChildrenOfType().Single(item => item.Channel is ChannelListing.ChannelListingChannel);
-
- private void clickDrawable(Drawable d)
- {
- InputManager.MoveMouseTo(d);
- InputManager.Click(MouseButton.Left);
- }
-
- private List createChannelMessages(Channel channel)
- {
- var message = new Message
- {
- ChannelId = channel.Id,
- Content = $"Hello, this is a message in {channel.Name}",
- Sender = testUser,
- Timestamp = new DateTimeOffset(DateTime.Now),
- };
- return new List { message };
- }
-
- private Channel createPublicChannel(int id) => new Channel
- {
- Id = id,
- Name = $"#channel-{id}",
- Topic = $"We talk about the number {id} here",
- Type = ChannelType.Public,
- };
-
- private Channel createPrivateChannel()
- {
- int id = RNG.Next(0, 10000);
- return new Channel(new APIUser
- {
- Id = id,
- Username = $"test user {id}",
- });
- }
-
- private class TestChatOverlayV2 : ChatOverlayV2
- {
- public bool SlowLoading { get; set; }
-
- public SlowLoadingDrawableChannel GetSlowLoadingChannel(Channel channel) => DrawableChannels.OfType().Single(c => c.Channel == channel);
-
- protected override ChatOverlayDrawableChannel CreateDrawableChannel(Channel newChannel)
- {
- return SlowLoading
- ? new SlowLoadingDrawableChannel(newChannel)
- : new ChatOverlayDrawableChannel(newChannel);
- }
- }
-
- private class SlowLoadingDrawableChannel : ChatOverlayDrawableChannel
- {
- public readonly ManualResetEventSlim LoadEvent = new ManualResetEventSlim();
-
- public SlowLoadingDrawableChannel([NotNull] Channel channel)
- : base(channel)
- {
- }
-
- [BackgroundDependencyLoader]
- private void load()
- {
- LoadEvent.Wait(10000);
- }
- }
- }
-}
diff --git a/osu.Game.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.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.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
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/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/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/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/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/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.
}
}
diff --git a/osu.Game/Overlays/Chat/ChannelList/ChannelList.cs b/osu.Game/Overlays/Chat/ChannelList/ChannelList.cs
index ce1ed2b4d7..47a2d234d1 100644
--- a/osu.Game/Overlays/Chat/ChannelList/ChannelList.cs
+++ b/osu.Game/Overlays/Chat/ChannelList/ChannelList.cs
@@ -26,15 +26,20 @@ namespace osu.Game.Overlays.Chat.ChannelList
public Action? OnRequestSelect;
public Action? OnRequestLeave;
- public IEnumerable Channels => publicChannelFlow.Channels.Concat(privateChannelFlow.Channels);
+ public IEnumerable Channels => groupFlow.Children
+ .OfType()
+ .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 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]
@@ -49,25 +54,20 @@ namespace osu.Game.Overlays.Chat.ChannelList
},
scroll = new OsuScrollContainer
{
- Padding = new MarginPadding { Vertical = 7 },
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[]
{
- new ChannelListLabel(ChatStrings.ChannelsListTitlePUBLIC.ToUpper()),
- publicChannelFlow = new ChannelListItemFlow(),
- selector = new ChannelListItem(ChannelListingChannel)
- {
- Margin = new MarginPadding { Bottom = 10 },
- },
- new ChannelListLabel(ChatStrings.ChannelsListTitlePM.ToUpper()),
- privateChannelFlow = new ChannelListItemFlow(),
+ announceChannelGroup = new ChannelGroup(ChatStrings.ChannelsListTitleANNOUNCE.ToUpper()),
+ publicChannelGroup = new ChannelGroup(ChatStrings.ChannelsListTitlePUBLIC.ToUpper()),
+ selector = new ChannelListItem(ChannelListingChannel),
+ privateChannelGroup = new ChannelGroup(ChatStrings.ChannelsListTitlePM.ToUpper()),
},
},
},
@@ -85,9 +85,11 @@ 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);
+
+ updateVisibility();
}
public void RemoveChannel(Channel channel)
@@ -96,10 +98,12 @@ 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);
+
+ updateVisibility();
}
public ChannelListItem GetItem(Channel channel)
@@ -112,40 +116,58 @@ 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 announceChannelGroup.ItemFlow;
default:
- return publicChannelFlow;
+ return publicChannelGroup.ItemFlow;
}
}
- private class ChannelListLabel : OsuSpriteText
+ private void updateVisibility()
{
- public ChannelListLabel(LocalisableString label)
- {
- Text = label;
- Margin = new MarginPadding { Left = 18, Bottom = 5 };
- Font = OsuFont.Torus.With(size: 12, weight: FontWeight.SemiBold);
- }
+ if (announceChannelGroup.ItemFlow.Children.Count == 0)
+ announceChannelGroup.Hide();
+ else
+ announceChannelGroup.Show();
}
- private class ChannelListItemFlow : FillFlowContainer
+ private class ChannelGroup : FillFlowContainer
{
- public IEnumerable Channels => Children.Select(c => c.Channel);
+ public readonly FillFlowContainer ItemFlow;
- 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),
+ },
+ ItemFlow = new FillFlowContainer
+ {
+ Direction = FillDirection.Vertical,
+ RelativeSizeAxes = Axes.X,
+ AutoSizeAxes = Axes.Y,
+ },
+ };
}
}
}
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);
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
index 034670cf37..02769b5d68 100644
--- a/osu.Game/Overlays/ChatOverlay.cs
+++ b/osu.Game/Overlays/ChatOverlay.cs
@@ -1,35 +1,29 @@
-// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+#nullable enable
+
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.Input.Events;
using osu.Framework.Localisation;
+using osu.Game.Configuration;
+using osu.Game.Graphics.Containers;
+using osu.Game.Graphics.UserInterface;
using osu.Game.Localisation;
-using osu.Game.Online;
+using osu.Game.Online.Chat;
+using osu.Game.Overlays.Chat;
+using osu.Game.Overlays.Chat.ChannelList;
+using osu.Game.Overlays.Chat.Listing;
namespace osu.Game.Overlays
{
@@ -39,271 +33,147 @@ namespace osu.Game.Overlays
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;
+ private ChatOverlayTopBar topBar = null!;
+ private ChannelList channelList = null!;
+ private LoadingLayer loading = null!;
+ private ChannelListing channelListing = null!;
+ private ChatTextBar textBar = null!;
+ private Container currentChannelContainer = null!;
- [Resolved]
- private ChannelManager channelManager { get; set; }
+ private readonly Dictionary loadedChannels = new Dictionary();
- private Container currentChannelContainer;
+ protected IEnumerable DrawableChannels => loadedChannels.Values;
- private readonly List loadedChannels = new List();
-
- private LoadingSpinner loading;
-
- private FocusedTextBox textBox;
-
- private const int transition_length = 500;
+ private readonly BindableFloat chatHeight = new BindableFloat();
+ private bool isDraggingTopBar;
+ private float dragStartChatHeight;
public const float DEFAULT_HEIGHT = 0.4f;
- public const float TAB_AREA_HEIGHT = 50;
+ private const int transition_length = 500;
+ private const float top_bar_height = 40;
+ private const float side_bar_width = 190;
+ private const float chat_bar_height = 60;
- protected ChannelTabControl ChannelTabControl;
+ [Resolved]
+ private OsuConfigManager config { get; set; } = null!;
- protected virtual ChannelTabControl CreateChannelTabControl() => new ChannelTabControl();
+ [Resolved]
+ private ChannelManager channelManager { get; set; } = null!;
- private Container chatContainer;
- private TabsArea tabsArea;
- private Box chatBackground;
- private Box tabBackground;
+ [Cached]
+ private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Pink);
- public Bindable ChatHeight { get; set; }
-
- private Container channelSelectionContainer;
- protected ChannelSelectionOverlay ChannelSelectionOverlay;
+ [Cached]
+ private readonly Bindable currentChannel = new Bindable();
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()
{
+ Height = DEFAULT_HEIGHT;
+
+ Masking = true;
+
+ const float corner_radius = 7f;
+
+ CornerRadius = corner_radius;
+
+ // Hack to hide the bottom edge corner radius off-screen.
+ Margin = new MarginPadding { Bottom = -corner_radius };
+ Padding = new MarginPadding { Bottom = corner_radius };
+
RelativeSizeAxes = Axes.Both;
- RelativePositionAxes = Axes.Both;
- Anchor = Anchor.BottomLeft;
- Origin = Anchor.BottomLeft;
+ Anchor = Anchor.BottomCentre;
+ Origin = Anchor.BottomCentre;
}
[BackgroundDependencyLoader]
- private void load(OsuConfigManager config, OsuColour colours, TextureStore textures)
+ private void load()
{
- const float padding = 5;
+ // Required for the pop in/out animation
+ RelativePositionAxes = Axes.Both;
Children = new Drawable[]
{
- channelSelectionContainer = new Container
+ topBar = new ChatOverlayTopBar
+ {
+ RelativeSizeAxes = Axes.X,
+ Height = top_bar_height,
+ },
+ channelList = new ChannelList
+ {
+ RelativeSizeAxes = Axes.Y,
+ Width = side_bar_width,
+ Padding = new MarginPadding { Top = top_bar_height },
+ },
+ new Container
{
RelativeSizeAxes = Axes.Both,
- Height = 1f - DEFAULT_HEIGHT,
- Masking = true,
- Children = new[]
+ Anchor = Anchor.TopRight,
+ Origin = Anchor.TopRight,
+ Padding = new MarginPadding
{
- ChannelSelectionOverlay = new ChannelSelectionOverlay
+ Top = top_bar_height,
+ Left = side_bar_width,
+ Bottom = chat_bar_height,
+ },
+ Children = new Drawable[]
+ {
+ new Box
+ {
+ RelativeSizeAxes = Axes.Both,
+ Colour = colourProvider.Background4,
+ },
+ currentChannelContainer = new Container
+ {
+ RelativeSizeAxes = Axes.Both,
+ },
+ loading = new LoadingLayer(true),
+ channelListing = new ChannelListing
{
RelativeSizeAxes = Axes.Both,
},
},
},
- chatContainer = new Container
+ textBar = new ChatTextBar
{
- 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;
- }),
- }
- },
- },
+ RelativeSizeAxes = Axes.X,
+ Anchor = Anchor.BottomRight,
+ Origin = Anchor.BottomRight,
+ Padding = new MarginPadding { Left = side_bar_width },
},
};
-
- 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)
+ protected override void LoadComplete()
{
- if (e.NewValue == null)
+ base.LoadComplete();
+
+ config.BindWith(OsuSetting.ChatDisplayHeight, chatHeight);
+
+ chatHeight.BindValueChanged(height => { Height = height.NewValue; }, true);
+
+ currentChannel.BindTo(channelManager.CurrentChannel);
+ joinedChannels.BindTo(channelManager.JoinedChannels);
+ availableChannels.BindTo(channelManager.AvailableChannels);
+
+ Schedule(() =>
{
- textBox.Current.Disabled = true;
- currentChannelContainer.Clear(false);
- ChannelSelectionOverlay.Show();
- return;
- }
+ currentChannel.BindValueChanged(currentChannelChanged, true);
+ joinedChannels.BindCollectionChanged(joinedChannelsChanged, true);
+ availableChannels.BindCollectionChanged(availableChannelsChanged, true);
+ });
- if (e.NewValue is ChannelSelectorTabItem.ChannelSelectorTabChannel)
- return;
+ channelList.OnRequestSelect += channel => channelManager.CurrentChannel.Value = channel;
+ channelList.OnRequestLeave += channel => channelManager.LeaveChannel(channel);
- textBox.Current.Disabled = e.NewValue.ReadOnly;
+ channelListing.OnRequestJoin += channel => channelManager.JoinChannel(channel);
+ channelListing.OnRequestLeave += channel => channelManager.LeaveChannel(channel);
- 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);
+ textBar.OnSearchTermsChanged += searchTerms => channelListing.SearchTerm = searchTerms;
+ textBar.OnChatMessageCommitted += handleChatMessage;
}
///
@@ -320,7 +190,7 @@ namespace osu.Game.Overlays
if (!channel.Joined.Value)
channel = channelManager.JoinChannel(channel);
- currentChannel.Value = channel;
+ channelManager.CurrentChannel.Value = channel;
}
channel.HighlightedMessage.Value = message;
@@ -328,159 +198,172 @@ namespace osu.Game.Overlays
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();
+ currentChannel.Value = channelList.ChannelListingChannel;
+ return true;
+
+ case PlatformAction.DocumentClose:
+ channelManager.LeaveChannel(currentChannel.Value);
return true;
case PlatformAction.TabRestore:
channelManager.JoinLastClosedChannel();
return true;
- case PlatformAction.DocumentClose:
- channelManager.LeaveChannel(currentChannel.Value);
+ case PlatformAction.DocumentPrevious:
+ cycleChannel(-1);
return true;
- }
- return false;
+ case PlatformAction.DocumentNext:
+ cycleChannel(1);
+ return true;
+
+ default:
+ return false;
+ }
}
public void OnReleased(KeyBindingReleaseEvent e)
{
}
- public override bool AcceptsFocus => true;
-
- protected override void OnFocus(FocusEvent e)
+ protected override bool OnDragStart(DragStartEvent e)
{
- // this is necessary as textbox is masked away and therefore can't get focus :(
- textBox.TakeFocus();
- base.OnFocus(e);
+ isDraggingTopBar = topBar.IsHovered;
+
+ if (!isDraggingTopBar)
+ return base.OnDragStart(e);
+
+ dragStartChatHeight = chatHeight.Value;
+ return true;
+ }
+
+ protected override void OnDrag(DragEvent e)
+ {
+ if (!isDraggingTopBar)
+ return;
+
+ float targetChatHeight = dragStartChatHeight - (e.MousePosition.Y - e.MouseDownPosition.Y) / Parent.DrawSize.Y;
+ chatHeight.Value = targetChatHeight;
+ }
+
+ protected override void OnDragEnd(DragEndEvent e)
+ {
+ isDraggingTopBar = false;
+ base.OnDragEnd(e);
}
protected override void PopIn()
{
+ base.PopIn();
+
this.MoveToY(0, transition_length, Easing.OutQuint);
this.FadeIn(transition_length, Easing.OutQuint);
-
- textBox.HoldFocus = true;
-
- base.PopIn();
}
protected override void PopOut()
{
+ base.PopOut();
+
this.MoveToY(Height, transition_length, Easing.InSine);
this.FadeOut(transition_length, Easing.InSine);
- ChannelSelectionOverlay.Hide();
-
- textBox.HoldFocus = false;
- base.PopOut();
+ textBar.TextBoxKillFocus();
}
+ protected override void OnFocus(FocusEvent e)
+ {
+ textBar.TextBoxTakeFocus();
+ base.OnFocus(e);
+ }
+
+ private void currentChannelChanged(ValueChangedEvent channel)
+ {
+ Channel? newChannel = channel.NewValue;
+
+ // null channel denotes that we should be showing the listing.
+ if (newChannel == null)
+ {
+ currentChannel.Value = channelList.ChannelListingChannel;
+ return;
+ }
+
+ if (newChannel is ChannelListing.ChannelListingChannel)
+ {
+ currentChannelContainer.Clear(false);
+ channelListing.Show();
+ textBar.ShowSearch.Value = true;
+ }
+ else
+ {
+ channelListing.Hide();
+ textBar.ShowSearch.Value = false;
+
+ if (loadedChannels.ContainsKey(newChannel))
+ {
+ currentChannelContainer.Clear(false);
+ currentChannelContainer.Add(loadedChannels[newChannel]);
+ }
+ else
+ {
+ loading.Show();
+
+ // Ensure the drawable channel is stored before async load to prevent double loading
+ ChatOverlayDrawableChannel drawableChannel = CreateDrawableChannel(newChannel);
+ loadedChannels.Add(newChannel, drawableChannel);
+
+ LoadComponentAsync(drawableChannel, loadedDrawable =>
+ {
+ // Ensure the current channel hasn't changed by the time the load completes
+ if (currentChannel.Value != loadedDrawable.Channel)
+ return;
+
+ // Ensure the cached reference hasn't been removed from leaving the channel
+ if (!loadedChannels.ContainsKey(loadedDrawable.Channel))
+ return;
+
+ currentChannelContainer.Clear(false);
+ currentChannelContainer.Add(loadedDrawable);
+ loading.Hide();
+ });
+ }
+ }
+
+ // Mark channel as read when channel switched
+ if (newChannel.Messages.Any())
+ channelManager.MarkChannelAsRead(newChannel);
+ }
+
+ protected virtual ChatOverlayDrawableChannel CreateDrawableChannel(Channel newChannel) => new ChatOverlayDrawableChannel(newChannel);
+
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);
- }
+ IEnumerable newChannels = args.NewItems.OfType().Where(isChatChannel);
+
+ foreach (var channel in newChannels)
+ channelList.AddChannel(channel);
break;
case NotifyCollectionChangedAction.Remove:
- foreach (Channel channel in args.OldItems.Cast())
+ IEnumerable leftChannels = args.OldItems.OfType().Where(isChatChannel);
+
+ foreach (var channel in leftChannels)
{
- if (!ChannelTabControl.Items.Contains(channel))
- continue;
+ channelList.RemoveChannel(channel);
- ChannelTabControl.RemoveChannel(channel);
-
- var loaded = loadedChannels.Find(c => c.Channel == channel);
-
- if (loaded != null)
+ if (loadedChannels.ContainsKey(channel))
{
- // 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);
+ ChatOverlayDrawableChannel loaded = loadedChannels[channel];
+ loadedChannels.Remove(channel);
+ // DrawableChannel removed from cache must be manually disposed
loaded.Dispose();
}
}
@@ -490,35 +373,47 @@ namespace osu.Game.Overlays
}
private void availableChannelsChanged(object sender, NotifyCollectionChangedEventArgs args)
- {
- ChannelSelectionOverlay.UpdateAvailableChannels(availableChannels);
- }
+ => channelListing.UpdateAvailableChannels(channelManager.AvailableChannels);
- private void postMessage(TextBox textBox, bool newText)
+ private void handleChatMessage(string message)
{
- string text = textBox.Text.Trim();
-
- if (string.IsNullOrWhiteSpace(text))
+ if (string.IsNullOrWhiteSpace(message))
return;
- if (text[0] == '/')
- channelManager.PostCommand(text.Substring(1));
+ if (message[0] == '/')
+ channelManager.PostCommand(message.Substring(1));
else
- channelManager.PostMessage(text);
-
- textBox.Text = string.Empty;
+ channelManager.PostMessage(message);
}
- private class TabsArea : Container
+ private void cycleChannel(int direction)
{
- // IsHovered is used
- public override bool HandlePositionalInput => true;
+ List overlayChannels = channelList.Channels.ToList();
- public TabsArea()
+ if (overlayChannels.Count < 2)
+ return;
+
+ int currentIndex = overlayChannels.IndexOf(currentChannel.Value);
+
+ currentChannel.Value = overlayChannels[(currentIndex + direction + overlayChannels.Count) % overlayChannels.Count];
+
+ channelList.ScrollChannelIntoView(currentChannel.Value);
+ }
+
+ ///
+ /// Whether a channel should be displayed in this overlay, based on its type.
+ ///
+ private static bool isChatChannel(Channel channel)
+ {
+ switch (channel.Type)
{
- Name = @"tabs area";
- RelativeSizeAxes = Axes.X;
- Height = TAB_AREA_HEIGHT;
+ case ChannelType.Multiplayer:
+ case ChannelType.Spectator:
+ case ChannelType.Temporary:
+ return false;
+
+ default:
+ return true;
}
}
}
diff --git a/osu.Game/Overlays/ChatOverlayV2.cs b/osu.Game/Overlays/ChatOverlayV2.cs
deleted file mode 100644
index f2ec007b19..0000000000
--- a/osu.Game/Overlays/ChatOverlayV2.cs
+++ /dev/null
@@ -1,420 +0,0 @@
-// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
-// See the LICENCE file in the repository root for full licence text.
-
-#nullable enable
-
-using System.Collections.Generic;
-using System.Collections.Specialized;
-using System.Diagnostics;
-using System.Linq;
-using osu.Framework.Allocation;
-using osu.Framework.Bindables;
-using osu.Framework.Graphics;
-using osu.Framework.Graphics.Containers;
-using osu.Framework.Graphics.Shapes;
-using osu.Framework.Input;
-using osu.Framework.Input.Bindings;
-using osu.Framework.Input.Events;
-using osu.Framework.Localisation;
-using osu.Game.Configuration;
-using osu.Game.Graphics.Containers;
-using osu.Game.Graphics.UserInterface;
-using osu.Game.Localisation;
-using osu.Game.Online.Chat;
-using osu.Game.Overlays.Chat;
-using osu.Game.Overlays.Chat.ChannelList;
-using osu.Game.Overlays.Chat.Listing;
-
-namespace osu.Game.Overlays
-{
- public class ChatOverlayV2 : OsuFocusedOverlayContainer, INamedOverlayComponent, IKeyBindingHandler
- {
- public string IconTexture => "Icons/Hexacons/messaging";
- public LocalisableString Title => ChatStrings.HeaderTitle;
- public LocalisableString Description => ChatStrings.HeaderDescription;
-
- private ChatOverlayTopBar topBar = null!;
- private ChannelList channelList = null!;
- private LoadingLayer loading = null!;
- private ChannelListing channelListing = null!;
- private ChatTextBar textBar = null!;
- private Container currentChannelContainer = null!;
-
- private readonly Dictionary loadedChannels = new Dictionary();
-
- protected IEnumerable DrawableChannels => loadedChannels.Values;
-
- private readonly BindableFloat chatHeight = new BindableFloat();
- private bool isDraggingTopBar;
- private float dragStartChatHeight;
-
- public const float DEFAULT_HEIGHT = 0.4f;
-
- private const int transition_length = 500;
- private const float top_bar_height = 40;
- private const float side_bar_width = 190;
- private const float chat_bar_height = 60;
-
- [Resolved]
- private OsuConfigManager config { get; set; } = null!;
-
- [Resolved]
- private ChannelManager channelManager { get; set; } = null!;
-
- [Cached]
- private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Pink);
-
- [Cached]
- private readonly Bindable currentChannel = new Bindable();
-
- private readonly IBindableList availableChannels = new BindableList();
- private readonly IBindableList joinedChannels = new BindableList();
-
- public ChatOverlayV2()
- {
- Height = DEFAULT_HEIGHT;
-
- Masking = true;
-
- const float corner_radius = 7f;
-
- CornerRadius = corner_radius;
-
- // Hack to hide the bottom edge corner radius off-screen.
- Margin = new MarginPadding { Bottom = -corner_radius };
- Padding = new MarginPadding { Bottom = corner_radius };
-
- RelativeSizeAxes = Axes.Both;
- Anchor = Anchor.BottomCentre;
- Origin = Anchor.BottomCentre;
- }
-
- [BackgroundDependencyLoader]
- private void load()
- {
- // Required for the pop in/out animation
- RelativePositionAxes = Axes.Both;
-
- Children = new Drawable[]
- {
- topBar = new ChatOverlayTopBar
- {
- RelativeSizeAxes = Axes.X,
- Height = top_bar_height,
- },
- channelList = new ChannelList
- {
- RelativeSizeAxes = Axes.Y,
- Width = side_bar_width,
- Padding = new MarginPadding { Top = top_bar_height },
- },
- new Container
- {
- RelativeSizeAxes = Axes.Both,
- Anchor = Anchor.TopRight,
- Origin = Anchor.TopRight,
- Padding = new MarginPadding
- {
- Top = top_bar_height,
- Left = side_bar_width,
- Bottom = chat_bar_height,
- },
- Children = new Drawable[]
- {
- new Box
- {
- RelativeSizeAxes = Axes.Both,
- Colour = colourProvider.Background4,
- },
- currentChannelContainer = new Container
- {
- RelativeSizeAxes = Axes.Both,
- },
- loading = new LoadingLayer(true),
- channelListing = new ChannelListing
- {
- RelativeSizeAxes = Axes.Both,
- },
- },
- },
- textBar = new ChatTextBar
- {
- RelativeSizeAxes = Axes.X,
- Anchor = Anchor.BottomRight,
- Origin = Anchor.BottomRight,
- Padding = new MarginPadding { Left = side_bar_width },
- },
- };
- }
-
- protected override void LoadComplete()
- {
- base.LoadComplete();
-
- config.BindWith(OsuSetting.ChatDisplayHeight, chatHeight);
-
- chatHeight.BindValueChanged(height => { Height = height.NewValue; }, true);
-
- currentChannel.BindTo(channelManager.CurrentChannel);
- joinedChannels.BindTo(channelManager.JoinedChannels);
- availableChannels.BindTo(channelManager.AvailableChannels);
-
- Schedule(() =>
- {
- currentChannel.BindValueChanged(currentChannelChanged, true);
- joinedChannels.BindCollectionChanged(joinedChannelsChanged, true);
- availableChannels.BindCollectionChanged(availableChannelsChanged, true);
- });
-
- channelList.OnRequestSelect += channel => channelManager.CurrentChannel.Value = channel;
- channelList.OnRequestLeave += channel => channelManager.LeaveChannel(channel);
-
- channelListing.OnRequestJoin += channel => channelManager.JoinChannel(channel);
- channelListing.OnRequestLeave += channel => channelManager.LeaveChannel(channel);
-
- textBar.OnSearchTermsChanged += searchTerms => channelListing.SearchTerm = searchTerms;
- textBar.OnChatMessageCommitted += handleChatMessage;
- }
-
- ///
- /// 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);
-
- channelManager.CurrentChannel.Value = channel;
- }
-
- channel.HighlightedMessage.Value = message;
-
- Show();
- }
-
- public bool OnPressed(KeyBindingPressEvent e)
- {
- switch (e.Action)
- {
- case PlatformAction.TabNew:
- currentChannel.Value = channelList.ChannelListingChannel;
- return true;
-
- case PlatformAction.DocumentClose:
- channelManager.LeaveChannel(currentChannel.Value);
- return true;
-
- case PlatformAction.TabRestore:
- channelManager.JoinLastClosedChannel();
- return true;
-
- case PlatformAction.DocumentPrevious:
- cycleChannel(-1);
- return true;
-
- case PlatformAction.DocumentNext:
- cycleChannel(1);
- return true;
-
- default:
- return false;
- }
- }
-
- public void OnReleased(KeyBindingReleaseEvent e)
- {
- }
-
- protected override bool OnDragStart(DragStartEvent e)
- {
- isDraggingTopBar = topBar.IsHovered;
-
- if (!isDraggingTopBar)
- return base.OnDragStart(e);
-
- dragStartChatHeight = chatHeight.Value;
- return true;
- }
-
- protected override void OnDrag(DragEvent e)
- {
- if (!isDraggingTopBar)
- return;
-
- float targetChatHeight = dragStartChatHeight - (e.MousePosition.Y - e.MouseDownPosition.Y) / Parent.DrawSize.Y;
- chatHeight.Value = targetChatHeight;
- }
-
- protected override void OnDragEnd(DragEndEvent e)
- {
- isDraggingTopBar = false;
- base.OnDragEnd(e);
- }
-
- protected override void PopIn()
- {
- base.PopIn();
-
- this.MoveToY(0, transition_length, Easing.OutQuint);
- this.FadeIn(transition_length, Easing.OutQuint);
- }
-
- protected override void PopOut()
- {
- base.PopOut();
-
- this.MoveToY(Height, transition_length, Easing.InSine);
- this.FadeOut(transition_length, Easing.InSine);
-
- textBar.TextBoxKillFocus();
- }
-
- protected override void OnFocus(FocusEvent e)
- {
- textBar.TextBoxTakeFocus();
- base.OnFocus(e);
- }
-
- private void currentChannelChanged(ValueChangedEvent channel)
- {
- Channel? newChannel = channel.NewValue;
-
- // null channel denotes that we should be showing the listing.
- if (newChannel == null)
- {
- currentChannel.Value = channelList.ChannelListingChannel;
- return;
- }
-
- if (newChannel is ChannelListing.ChannelListingChannel)
- {
- currentChannelContainer.Clear(false);
- channelListing.Show();
- textBar.ShowSearch.Value = true;
- }
- else
- {
- channelListing.Hide();
- textBar.ShowSearch.Value = false;
-
- if (loadedChannels.ContainsKey(newChannel))
- {
- currentChannelContainer.Clear(false);
- currentChannelContainer.Add(loadedChannels[newChannel]);
- }
- else
- {
- loading.Show();
-
- // Ensure the drawable channel is stored before async load to prevent double loading
- ChatOverlayDrawableChannel drawableChannel = CreateDrawableChannel(newChannel);
- loadedChannels.Add(newChannel, drawableChannel);
-
- LoadComponentAsync(drawableChannel, loadedDrawable =>
- {
- // Ensure the current channel hasn't changed by the time the load completes
- if (currentChannel.Value != loadedDrawable.Channel)
- return;
-
- // Ensure the cached reference hasn't been removed from leaving the channel
- if (!loadedChannels.ContainsKey(loadedDrawable.Channel))
- return;
-
- currentChannelContainer.Clear(false);
- currentChannelContainer.Add(loadedDrawable);
- loading.Hide();
- });
- }
- }
-
- // Mark channel as read when channel switched
- if (newChannel.Messages.Any())
- channelManager.MarkChannelAsRead(newChannel);
- }
-
- protected virtual ChatOverlayDrawableChannel CreateDrawableChannel(Channel newChannel) => new ChatOverlayDrawableChannel(newChannel);
-
- private void joinedChannelsChanged(object sender, NotifyCollectionChangedEventArgs args)
- {
- switch (args.Action)
- {
- case NotifyCollectionChangedAction.Add:
- IEnumerable newChannels = args.NewItems.OfType().Where(isChatChannel);
-
- foreach (var channel in newChannels)
- channelList.AddChannel(channel);
-
- break;
-
- case NotifyCollectionChangedAction.Remove:
- IEnumerable leftChannels = args.OldItems.OfType().Where(isChatChannel);
-
- foreach (var channel in leftChannels)
- {
- channelList.RemoveChannel(channel);
-
- if (loadedChannels.ContainsKey(channel))
- {
- ChatOverlayDrawableChannel loaded = loadedChannels[channel];
- loadedChannels.Remove(channel);
- // DrawableChannel removed from cache must be manually disposed
- loaded.Dispose();
- }
- }
-
- break;
- }
- }
-
- private void availableChannelsChanged(object sender, NotifyCollectionChangedEventArgs args)
- => channelListing.UpdateAvailableChannels(channelManager.AvailableChannels);
-
- private void handleChatMessage(string message)
- {
- if (string.IsNullOrWhiteSpace(message))
- return;
-
- if (message[0] == '/')
- channelManager.PostCommand(message.Substring(1));
- else
- channelManager.PostMessage(message);
- }
-
- private void cycleChannel(int direction)
- {
- List overlayChannels = channelList.Channels.ToList();
-
- if (overlayChannels.Count < 2)
- return;
-
- int currentIndex = overlayChannels.IndexOf(currentChannel.Value);
-
- currentChannel.Value = overlayChannels[(currentIndex + direction + overlayChannels.Count) % overlayChannels.Count];
-
- channelList.ScrollChannelIntoView(currentChannel.Value);
- }
-
- ///
- /// Whether a channel should be displayed in this overlay, based on its type.
- ///
- private static bool isChatChannel(Channel channel)
- {
- switch (channel.Type)
- {
- case ChannelType.Multiplayer:
- case ChannelType.Spectator:
- case ChannelType.Temporary:
- return false;
-
- default:
- return true;
- }
- }
- }
-}
diff --git a/osu.Game/Overlays/Dashboard/CurrentlyPlayingDisplay.cs b/osu.Game/Overlays/Dashboard/CurrentlyPlayingDisplay.cs
index a9312e9a3a..23f67a06cb 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;
@@ -9,11 +10,16 @@ using osu.Framework.Bindables;
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;
+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;
@@ -24,26 +30,62 @@ namespace osu.Game.Overlays.Dashboard
{
internal class CurrentlyPlayingDisplay : CompositeDrawable
{
+ private const float search_textbox_height = 40;
+ private const float padding = 10;
+
private readonly IBindableList playingUsers = new BindableList();
- private FillFlowContainer userFlow;
+ private SearchContainer userFlow;
+ private BasicSearchTextBox searchTextBox;
[Resolved]
private SpectatorClient spectatorClient { get; set; }
[BackgroundDependencyLoader]
- private void load()
+ private void load(OverlayColourProvider colourProvider)
{
RelativeSizeAxes = Axes.X;
AutoSizeAxes = Axes.Y;
- InternalChild = userFlow = new FillFlowContainer
+ InternalChildren = new Drawable[]
{
- RelativeSizeAxes = Axes.X,
- AutoSizeAxes = Axes.Y,
- Padding = new MarginPadding(10),
- Spacing = new Vector2(10),
+ new Box
+ {
+ RelativeSizeAxes = Axes.X,
+ Height = padding * 2 + search_textbox_height,
+ Colour = colourProvider.Background4,
+ },
+ new Container
+ {
+ RelativeSizeAxes = Axes.X,
+ Padding = new MarginPadding(padding),
+ Child = searchTextBox = new BasicSearchTextBox
+ {
+ RelativeSizeAxes = Axes.X,
+ Anchor = Anchor.TopCentre,
+ Origin = Anchor.TopCentre,
+ Height = search_textbox_height,
+ ReleaseFocusOnCommit = false,
+ HoldFocus = true,
+ PlaceholderText = HomeStrings.SearchPlaceholder,
+ },
+ },
+ userFlow = new SearchContainer
+ {
+ RelativeSizeAxes = Axes.X,
+ AutoSizeAxes = Axes.Y,
+ Spacing = new Vector2(10),
+ Padding = new MarginPadding
+ {
+ Top = padding * 3 + search_textbox_height,
+ Bottom = padding,
+ Right = padding,
+ Left = padding,
+ },
+ },
};
+
+ searchTextBox.Current.ValueChanged += text => userFlow.SearchTerm = text.NewValue;
}
[Resolved]
@@ -57,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)
@@ -102,17 +151,34 @@ namespace osu.Game.Overlays.Dashboard
panel.Origin = Anchor.TopCentre;
});
- private class PlayingUserPanel : CompositeDrawable
+ public class PlayingUserPanel : CompositeDrawable, IFilterable
{
public readonly APIUser User;
+ public IEnumerable FilterTerms { get; }
+
[Resolved(canBeNull: true)]
private IPerformFromScreenRunner performer { get; set; }
+ public bool FilteringActive { set; get; }
+
+ public bool MatchingFilter
+ {
+ set
+ {
+ if (value)
+ Show();
+ else
+ Hide();
+ }
+ }
+
public PlayingUserPanel(APIUser user)
{
User = user;
+ FilterTerms = new LocalisableString[] { User.Username };
+
AutoSizeAxes = Axes.Both;
}
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)
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/Settings/Sections/SkinSection.cs b/osu.Game/Overlays/Settings/Sections/SkinSection.cs
index a34776ddf0..a87e65b735 100644
--- a/osu.Game/Overlays/Settings/Sections/SkinSection.cs
+++ b/osu.Game/Overlays/Settings/Sections/SkinSection.cs
@@ -127,9 +127,12 @@ 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()
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;
}
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)
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();
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/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)
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);
}
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,
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
diff --git a/osu.Game/Screens/Edit/Verify/VerifyScreen.cs b/osu.Game/Screens/Edit/Verify/VerifyScreen.cs
index 9dc5a53907..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, 200),
+ new Dimension(GridSizeMode.Absolute, 250),
},
Content = new[]
{
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);
}
}
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);
+ }
+ }
+}
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()
diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj
index 950d4ceae1..eb47d0468f 100644
--- a/osu.Game/osu.Game.csproj
+++ b/osu.Game/osu.Game.csproj
@@ -29,13 +29,14 @@
+
all
runtime; build; native; contentfiles; analyzers; buildtransitive
-
+
diff --git a/osu.iOS.props b/osu.iOS.props
index bcff0d3c44..ccecad6f82 100644
--- a/osu.iOS.props
+++ b/osu.iOS.props
@@ -61,7 +61,7 @@
-
+
@@ -84,7 +84,7 @@
-
+