diff --git a/.github/workflows/sentry-release.yml b/.github/workflows/sentry-release.yml
index 8ca9f38234..442b97c473 100644
--- a/.github/workflows/sentry-release.yml
+++ b/.github/workflows/sentry-release.yml
@@ -23,4 +23,4 @@ jobs:
SENTRY_URL: https://sentry.ppy.sh/
with:
environment: production
- version: ${{ github.ref }}
+ version: osu@${{ github.ref_name }}
diff --git a/osu.Android.props b/osu.Android.props
index d5a77c6349..116c7dbfcd 100644
--- a/osu.Android.props
+++ b/osu.Android.props
@@ -51,8 +51,8 @@
-
-
+
+
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/TestSceneEditorClock.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorClock.cs
index 4b9be77471..393d3886e7 100644
--- a/osu.Game.Tests/Visual/Editing/TestSceneEditorClock.cs
+++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorClock.cs
@@ -2,10 +2,13 @@
// See the LICENCE file in the repository root for full licence text.
using NUnit.Framework;
+using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Rulesets.Osu;
+using osu.Game.Screens.Edit;
using osu.Game.Screens.Edit.Components;
+using osu.Game.Tests.Beatmaps;
using osuTK;
namespace osu.Game.Tests.Visual.Editing
@@ -13,6 +16,9 @@ namespace osu.Game.Tests.Visual.Editing
[TestFixture]
public class TestSceneEditorClock : EditorClockTestScene
{
+ [Cached]
+ private EditorBeatmap editorBeatmap = new EditorBeatmap(new TestBeatmap(new OsuRuleset().RulesetInfo));
+
public TestSceneEditorClock()
{
Add(new FillFlowContainer
diff --git a/osu.Game.Tests/Visual/Editing/TestScenePlaybackControl.cs b/osu.Game.Tests/Visual/Editing/TestScenePlaybackControl.cs
index 6aa884a197..bf0a7876a9 100644
--- a/osu.Game.Tests/Visual/Editing/TestScenePlaybackControl.cs
+++ b/osu.Game.Tests/Visual/Editing/TestScenePlaybackControl.cs
@@ -12,7 +12,7 @@ using osuTK;
namespace osu.Game.Tests.Visual.Editing
{
[TestFixture]
- public class TestScenePlaybackControl : OsuTestScene
+ public class TestScenePlaybackControl : EditorClockTestScene
{
[BackgroundDependencyLoader]
private void load()
diff --git a/osu.Game.Tests/Visual/Editing/TestSceneTapTimingControl.cs b/osu.Game.Tests/Visual/Editing/TestSceneTapTimingControl.cs
index 46b45979ea..8dd368f2a9 100644
--- a/osu.Game.Tests/Visual/Editing/TestSceneTapTimingControl.cs
+++ b/osu.Game.Tests/Visual/Editing/TestSceneTapTimingControl.cs
@@ -77,6 +77,12 @@ namespace osu.Game.Tests.Visual.Editing
timingInfo.Text = $"offset: {selectedGroup.Value.Time:N2} bpm: {selectedGroup.Value.ControlPoints.OfType().First().BPM:N2}";
}
+ [Test]
+ public void TestNoop()
+ {
+ AddStep("do nothing", () => { });
+ }
+
[Test]
public void TestTapThenReset()
{
diff --git a/osu.Game.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/TestSceneTimingScreen.cs b/osu.Game.Tests/Visual/Editing/TestSceneTimingScreen.cs
index 17b8189fc7..a358166477 100644
--- a/osu.Game.Tests/Visual/Editing/TestSceneTimingScreen.cs
+++ b/osu.Game.Tests/Visual/Editing/TestSceneTimingScreen.cs
@@ -1,14 +1,18 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+using System.Linq;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Graphics.Containers;
+using osu.Framework.Testing;
using osu.Game.Overlays;
using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Osu;
using osu.Game.Screens.Edit;
using osu.Game.Screens.Edit.Timing;
+using osu.Game.Screens.Edit.Timing.RowAttributes;
+using osuTK.Input;
namespace osu.Game.Tests.Visual.Editing
{
@@ -22,6 +26,8 @@ namespace osu.Game.Tests.Visual.Editing
[Cached]
private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Blue);
+ private TimingScreen timingScreen;
+
protected override bool ScrollUsingMouseWheel => false;
public TestSceneTimingScreen()
@@ -36,12 +42,54 @@ namespace osu.Game.Tests.Visual.Editing
Beatmap.Value = CreateWorkingBeatmap(editorBeatmap.PlayableBeatmap);
Beatmap.Disabled = true;
- Child = new TimingScreen
+ Child = timingScreen = new TimingScreen
{
State = { Value = Visibility.Visible },
};
}
+ [SetUpSteps]
+ public void SetUpSteps()
+ {
+ AddStep("Stop clock", () => Clock.Stop());
+
+ AddUntilStep("wait for rows to load", () => Child.ChildrenOfType().Any());
+ }
+
+ [Test]
+ public void TestTrackingCurrentTimeWhileRunning()
+ {
+ AddStep("Select first effect point", () =>
+ {
+ InputManager.MoveMouseTo(Child.ChildrenOfType().First());
+ InputManager.Click(MouseButton.Left);
+ });
+
+ AddUntilStep("Selection changed", () => timingScreen.SelectedGroup.Value.Time == 54670);
+ AddUntilStep("Ensure seeked to correct time", () => Clock.CurrentTimeAccurate == 54670);
+
+ AddStep("Seek to just before next point", () => Clock.Seek(69000));
+ AddStep("Start clock", () => Clock.Start());
+
+ AddUntilStep("Selection changed", () => timingScreen.SelectedGroup.Value.Time == 69670);
+ }
+
+ [Test]
+ public void TestTrackingCurrentTimeWhilePaused()
+ {
+ AddStep("Select first effect point", () =>
+ {
+ InputManager.MoveMouseTo(Child.ChildrenOfType().First());
+ InputManager.Click(MouseButton.Left);
+ });
+
+ AddUntilStep("Selection changed", () => timingScreen.SelectedGroup.Value.Time == 54670);
+ AddUntilStep("Ensure seeked to correct time", () => Clock.CurrentTimeAccurate == 54670);
+
+ AddStep("Seek to later", () => Clock.Seek(80000));
+ AddUntilStep("Selection changed", () => timingScreen.SelectedGroup.Value.Time == 69670);
+ }
+
protected override void Dispose(bool isDisposing)
{
Beatmap.Disabled = false;
diff --git a/osu.Game.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/Multiplayer/TestSceneDrawableRoom.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoom.cs
index 7d010592ae..3172a68b81 100644
--- a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoom.cs
+++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoom.cs
@@ -124,13 +124,19 @@ namespace osu.Game.Tests.Visual.Multiplayer
Status = { Value = new RoomStatusOpen() },
Category = { Value = RoomCategory.Spotlight },
}),
+ createLoungeRoom(new Room
+ {
+ Name = { Value = "Featured artist room" },
+ Status = { Value = new RoomStatusOpen() },
+ Category = { Value = RoomCategory.FeaturedArtist },
+ }),
}
};
});
- AddUntilStep("wait for panel load", () => rooms.Count == 5);
+ AddUntilStep("wait for panel load", () => rooms.Count == 6);
AddUntilStep("correct status text", () => rooms.ChildrenOfType().Count(s => s.Text.ToString().StartsWith("Currently playing", StringComparison.Ordinal)) == 2);
- AddUntilStep("correct status text", () => rooms.ChildrenOfType().Count(s => s.Text.ToString().StartsWith("Ready to play", StringComparison.Ordinal)) == 3);
+ AddUntilStep("correct status text", () => rooms.ChildrenOfType().Count(s => s.Text.ToString().StartsWith("Ready to play", StringComparison.Ordinal)) == 4);
}
[Test]
diff --git a/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlay.cs
index 859727e632..9d206af40e 100644
--- a/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlay.cs
+++ b/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlay.cs
@@ -10,7 +10,10 @@ using osu.Game.Rulesets;
using System;
using System.Collections.Generic;
using System.Linq;
+using osu.Framework.Testing;
+using osu.Game.Beatmaps.Drawables;
using osu.Game.Online.API.Requests.Responses;
+using osu.Game.Overlays.BeatmapSet.Scores;
using APIUser = osu.Game.Online.API.Requests.Responses.APIUser;
namespace osu.Game.Tests.Visual.Online
@@ -101,6 +104,14 @@ namespace osu.Game.Tests.Visual.Online
AddStep("show many difficulties", () => overlay.ShowBeatmapSet(createManyDifficultiesBeatmapSet()));
downloadAssert(true);
+
+ AddAssert("status is loved", () => overlay.ChildrenOfType().Single().Status == BeatmapOnlineStatus.Loved);
+ AddAssert("scores container is visible", () => overlay.ChildrenOfType().Single().Alpha == 1);
+
+ AddStep("go to second beatmap", () => overlay.ChildrenOfType().ElementAt(1).TriggerClick());
+
+ AddAssert("status is graveyard", () => overlay.ChildrenOfType().Single().Status == BeatmapOnlineStatus.Graveyard);
+ AddAssert("scores container is hidden", () => overlay.ChildrenOfType().Single().Alpha == 0);
}
[Test]
@@ -232,6 +243,7 @@ namespace osu.Game.Tests.Visual.Online
Fails = Enumerable.Range(1, 100).Select(j => j % 12 - 6).ToArray(),
Retries = Enumerable.Range(-2, 100).Select(j => j % 12 - 6).ToArray(),
},
+ Status = i % 2 == 0 ? BeatmapOnlineStatus.Graveyard : BeatmapOnlineStatus.Loved,
});
}
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/TestSceneChatLink.cs b/osu.Game.Tests/Visual/Online/TestSceneChatLink.cs
index 6818147da4..a28de3be1e 100644
--- a/osu.Game.Tests/Visual/Online/TestSceneChatLink.cs
+++ b/osu.Game.Tests/Visual/Online/TestSceneChatLink.cs
@@ -12,7 +12,6 @@ using osu.Framework.Graphics.Containers;
using osu.Game.Graphics;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Online.Chat;
-using osu.Game.Overlays;
using osu.Game.Overlays.Chat;
using osuTK.Graphics;
@@ -22,12 +21,10 @@ namespace osu.Game.Tests.Visual.Online
public class TestSceneChatLink : OsuTestScene
{
private readonly TestChatLineContainer textContainer;
- private readonly DialogOverlay dialogOverlay;
private Color4 linkColour;
public TestSceneChatLink()
{
- Add(dialogOverlay = new DialogOverlay { Depth = float.MinValue });
Add(textContainer = new TestChatLineContainer
{
Padding = new MarginPadding { Left = 20, Right = 20 },
@@ -47,9 +44,6 @@ namespace osu.Game.Tests.Visual.Online
availableChannels.Add(new Channel { Name = "#english" });
availableChannels.Add(new Channel { Name = "#japanese" });
Dependencies.Cache(chatManager);
-
- Dependencies.Cache(new ChatOverlay());
- Dependencies.CacheAs(dialogOverlay);
}
[SetUp]
diff --git a/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs
index 4d1dee1650..2cf1114f30 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,169 @@ 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();
+ });
+
+ AddStep("Select channel 1", () => clickDrawable(getChannelListItem(testChannel1)));
+
+ 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 +525,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 e27db00003..0000000000
--- a/osu.Game.Tests/Visual/Online/TestSceneChatOverlayV2.cs
+++ /dev/null
@@ -1,500 +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.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 { RelativeSizeAxes = Axes.Both },
- },
- };
- });
-
- [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);
- }
-
- 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 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/Playlists/TestScenePlaylistsResultsScreen.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs
index f5fe00458a..c532e8bc05 100644
--- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs
+++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs
@@ -173,6 +173,8 @@ namespace osu.Game.Tests.Visual.Playlists
{
AddUntilStep("wait for scores loaded", () =>
requestComplete
+ // request handler may need to fire more than once to get scores.
+ && totalCount > 0
&& resultsScreen.ScorePanelList.GetScorePanels().Count() == totalCount
&& resultsScreen.ScorePanelList.AllPanelsVisible);
AddWaitStep("wait for display", 5);
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.Tests/Visual/Settings/TestSceneSettingsItem.cs b/osu.Game.Tests/Visual/Settings/TestSceneSettingsItem.cs
index 83265e13ad..3e679a7905 100644
--- a/osu.Game.Tests/Visual/Settings/TestSceneSettingsItem.cs
+++ b/osu.Game.Tests/Visual/Settings/TestSceneSettingsItem.cs
@@ -6,6 +6,7 @@ using NUnit.Framework;
using osu.Framework.Bindables;
using osu.Framework.Testing;
using osu.Framework.Utils;
+using osu.Game.Graphics.Containers;
using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterface;
using osu.Game.Overlays.Settings;
@@ -83,7 +84,7 @@ namespace osu.Game.Tests.Visual.Settings
AddStep("clear label", () => textBox.LabelText = default);
AddAssert("default value button centre aligned to control size", () => Precision.AlmostEquals(restoreDefaultValueButton.Parent.DrawHeight, control.DrawHeight, 1));
- AddStep("set warning text", () => textBox.WarningText = "This is some very important warning text! Hopefully it doesn't break the alignment of the default value indicator...");
+ AddStep("set warning text", () => textBox.SetNoticeText("This is some very important warning text! Hopefully it doesn't break the alignment of the default value indicator...", true));
AddAssert("default value button centre aligned to control size", () => Precision.AlmostEquals(restoreDefaultValueButton.Parent.DrawHeight, control.DrawHeight, 1));
}
@@ -129,16 +130,18 @@ namespace osu.Game.Tests.Visual.Settings
SettingsNumberBox numberBox = null;
AddStep("create settings item", () => Child = numberBox = new SettingsNumberBox());
- AddAssert("warning text not created", () => !numberBox.ChildrenOfType().Any());
+ AddAssert("warning text not created", () => !numberBox.ChildrenOfType().Any());
- AddStep("set warning text", () => numberBox.WarningText = "this is a warning!");
- AddAssert("warning text created", () => numberBox.ChildrenOfType().Single().Alpha == 1);
+ AddStep("set warning text", () => numberBox.SetNoticeText("this is a warning!", true));
+ AddAssert("warning text created", () => numberBox.ChildrenOfType().Single().Alpha == 1);
- AddStep("unset warning text", () => numberBox.WarningText = default);
- AddAssert("warning text hidden", () => numberBox.ChildrenOfType().Single().Alpha == 0);
+ AddStep("unset warning text", () => numberBox.ClearNoticeText());
+ AddAssert("warning text hidden", () => !numberBox.ChildrenOfType().Any());
- AddStep("set warning text again", () => numberBox.WarningText = "another warning!");
- AddAssert("warning text shown again", () => numberBox.ChildrenOfType().Single().Alpha == 1);
+ AddStep("set warning text again", () => numberBox.SetNoticeText("another warning!", true));
+ AddAssert("warning text shown again", () => numberBox.ChildrenOfType().Single().Alpha == 1);
+
+ AddStep("set non warning text", () => numberBox.SetNoticeText("you did good!"));
}
}
}
diff --git a/osu.Game/Beatmaps/Drawables/BundledBeatmapDownloader.cs b/osu.Game/Beatmaps/Drawables/BundledBeatmapDownloader.cs
index 84903d381a..5ebdee0b09 100644
--- a/osu.Game/Beatmaps/Drawables/BundledBeatmapDownloader.cs
+++ b/osu.Game/Beatmaps/Drawables/BundledBeatmapDownloader.cs
@@ -85,6 +85,8 @@ namespace osu.Game.Beatmaps.Drawables
downloadTrackers.Add(beatmapDownloadTracker);
AddInternal(beatmapDownloadTracker);
+ // Note that this is downloading the beatmaps even if they are already downloaded.
+ // We could rely more on `BeatmapDownloadTracker`'s exposed state to avoid this.
beatmapDownloader.Download(beatmapSet);
}
}
diff --git a/osu.Game/Beatmaps/Formats/LegacyDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyDecoder.cs
index 0276abc3ff..ff13e61360 100644
--- a/osu.Game/Beatmaps/Formats/LegacyDecoder.cs
+++ b/osu.Game/Beatmaps/Formats/LegacyDecoder.cs
@@ -59,7 +59,7 @@ namespace osu.Game.Beatmaps.Formats
}
catch (Exception e)
{
- Logger.Log($"Failed to process line \"{line}\" into \"{output}\": {e.Message}", LoggingTarget.Runtime, LogLevel.Important);
+ Logger.Log($"Failed to process line \"{line}\" into \"{output}\": {e.Message}");
}
}
}
diff --git a/osu.Game/Database/OnlineLookupCache.cs b/osu.Game/Database/OnlineLookupCache.cs
index 2f98aef58a..506103a2c0 100644
--- a/osu.Game/Database/OnlineLookupCache.cs
+++ b/osu.Game/Database/OnlineLookupCache.cs
@@ -91,7 +91,7 @@ namespace osu.Game.Database
}
}
- private void performLookup()
+ private async Task performLookup()
{
// contains at most 50 unique IDs from tasks, which is used to perform the lookup.
var nextTaskBatch = new Dictionary>>();
@@ -127,7 +127,7 @@ namespace osu.Game.Database
// rather than queueing, we maintain our own single-threaded request stream.
// todo: we probably want retry logic here.
- api.Perform(request);
+ await api.PerformAsync(request).ConfigureAwait(false);
finishPendingTask();
diff --git a/osu.Game/Graphics/OsuColour.cs b/osu.Game/Graphics/OsuColour.cs
index afedf36cad..7fd94b57f3 100644
--- a/osu.Game/Graphics/OsuColour.cs
+++ b/osu.Game/Graphics/OsuColour.cs
@@ -4,6 +4,7 @@
using System;
using osu.Framework.Extensions.Color4Extensions;
using osu.Game.Beatmaps;
+using osu.Game.Online.Rooms;
using osu.Game.Overlays;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Scoring;
@@ -188,6 +189,24 @@ namespace osu.Game.Graphics
}
}
+ ///
+ /// Retrieves the main accent colour for a .
+ ///
+ public Color4? ForRoomCategory(RoomCategory roomCategory)
+ {
+ switch (roomCategory)
+ {
+ case RoomCategory.Spotlight:
+ return SpotlightColour;
+
+ case RoomCategory.FeaturedArtist:
+ return FeaturedArtistColour;
+
+ default:
+ return null;
+ }
+ }
+
///
/// Returns a foreground text colour that is supposed to contrast well with
/// the supplied .
@@ -360,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/Localisation/LayoutSettingsStrings.cs b/osu.Game/Localisation/LayoutSettingsStrings.cs
new file mode 100644
index 0000000000..5ac28f19b3
--- /dev/null
+++ b/osu.Game/Localisation/LayoutSettingsStrings.cs
@@ -0,0 +1,29 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Framework.Localisation;
+
+namespace osu.Game.Localisation
+{
+ public static class LayoutSettingsStrings
+ {
+ private const string prefix = @"osu.Game.Resources.Localisation.LayoutSettings";
+
+ ///
+ /// "Checking for fullscreen capabilities..."
+ ///
+ public static LocalisableString CheckingForFullscreenCapabilities => new TranslatableString(getKey(@"checking_for_fullscreen_capabilities"), @"Checking for fullscreen capabilities...");
+
+ ///
+ /// "osu! is running exclusive fullscreen, guaranteeing low latency!"
+ ///
+ public static LocalisableString OsuIsRunningExclusiveFullscreen => new TranslatableString(getKey(@"osu_is_running_exclusive_fullscreen"), @"osu! is running exclusive fullscreen, guaranteeing low latency!");
+
+ ///
+ /// "Unable to run exclusive fullscreen. You'll still experience some input latency."
+ ///
+ public static LocalisableString UnableToRunExclusiveFullscreen => new TranslatableString(getKey(@"unable_to_run_exclusive_fullscreen"), @"Unable to run exclusive fullscreen. You'll still experience some input latency.");
+
+ private static string getKey(string key) => $@"{prefix}:{key}";
+ }
+}
\ No newline at end of file
diff --git a/osu.Game/Online/API/DummyAPIAccess.cs b/osu.Game/Online/API/DummyAPIAccess.cs
index f292e95bd1..d3707d977c 100644
--- a/osu.Game/Online/API/DummyAPIAccess.cs
+++ b/osu.Game/Online/API/DummyAPIAccess.cs
@@ -63,12 +63,13 @@ namespace osu.Game.Online.API
public virtual void Queue(APIRequest request)
{
- if (HandleRequest?.Invoke(request) != true)
+ Schedule(() =>
{
- // this will fail due to not receiving an APIAccess, and trigger a failure on the request.
- // this is intended - any request in testing that needs non-failures should use HandleRequest.
- request.Perform(this);
- }
+ if (HandleRequest?.Invoke(request) != true)
+ {
+ request.Fail(new InvalidOperationException($@"{nameof(DummyAPIAccess)} cannot process this request."));
+ }
+ });
}
public void Perform(APIRequest request) => HandleRequest?.Invoke(request);
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/HubClientConnector.cs b/osu.Game/Online/HubClientConnector.cs
index ca9bf00b23..261724e315 100644
--- a/osu.Game/Online/HubClientConnector.cs
+++ b/osu.Game/Online/HubClientConnector.cs
@@ -20,6 +20,8 @@ namespace osu.Game.Online
{
public class HubClientConnector : IHubClientConnector
{
+ public const string SERVER_SHUTDOWN_MESSAGE = "Server is shutting down.";
+
///
/// Invoked whenever a new hub connection is built, to configure it before it's started.
///
@@ -64,20 +66,28 @@ namespace osu.Game.Online
this.preferMessagePack = preferMessagePack;
apiState.BindTo(api.State);
- apiState.BindValueChanged(state =>
- {
- switch (state.NewValue)
- {
- case APIState.Failing:
- case APIState.Offline:
- Task.Run(() => disconnect(true));
- break;
+ apiState.BindValueChanged(state => connectIfPossible(), true);
+ }
- case APIState.Online:
- Task.Run(connect);
- break;
- }
- }, true);
+ public void Reconnect()
+ {
+ Logger.Log($"{clientName} reconnecting...", LoggingTarget.Network);
+ Task.Run(connectIfPossible);
+ }
+
+ private void connectIfPossible()
+ {
+ switch (apiState.Value)
+ {
+ case APIState.Failing:
+ case APIState.Offline:
+ Task.Run(() => disconnect(true));
+ break;
+
+ case APIState.Online:
+ Task.Run(connect);
+ break;
+ }
}
private async Task connect()
diff --git a/osu.Game/Online/IHubClientConnector.cs b/osu.Game/Online/IHubClientConnector.cs
index d2ceb1f030..b168e4669f 100644
--- a/osu.Game/Online/IHubClientConnector.cs
+++ b/osu.Game/Online/IHubClientConnector.cs
@@ -30,5 +30,10 @@ namespace osu.Game.Online
/// Invoked whenever a new hub connection is built, to configure it before it's started.
///
public Action? ConfigureConnection { get; set; }
+
+ ///
+ /// Reconnect if already connected.
+ ///
+ void Reconnect();
}
}
diff --git a/osu.Game/Online/Multiplayer/MultiplayerClientExtensions.cs b/osu.Game/Online/Multiplayer/MultiplayerClientExtensions.cs
index 4729765084..cda313bd0a 100644
--- a/osu.Game/Online/Multiplayer/MultiplayerClientExtensions.cs
+++ b/osu.Game/Online/Multiplayer/MultiplayerClientExtensions.cs
@@ -25,12 +25,7 @@ namespace osu.Game.Online.Multiplayer
Debug.Assert(exception != null);
- string message = exception is HubException
- // HubExceptions arrive with additional message context added, but we want to display the human readable message:
- // "An unexpected error occurred invoking 'AddPlaylistItem' on the server.InvalidStateException: Can't enqueue more than 3 items at once."
- // We generally use the message field for a user-parseable error (eventually to be replaced), so drop the first part for now.
- ? exception.Message.Substring(exception.Message.IndexOf(':') + 1).Trim()
- : exception.Message;
+ string message = exception.GetHubExceptionMessage() ?? exception.Message;
Logger.Log(message, level: LogLevel.Important);
onError?.Invoke(exception);
@@ -40,5 +35,16 @@ namespace osu.Game.Online.Multiplayer
onSuccess?.Invoke();
}
});
+
+ public static string? GetHubExceptionMessage(this Exception exception)
+ {
+ if (exception is HubException hubException)
+ // HubExceptions arrive with additional message context added, but we want to display the human readable message:
+ // "An unexpected error occurred invoking 'AddPlaylistItem' on the server.InvalidStateException: Can't enqueue more than 3 items at once."
+ // We generally use the message field for a user-parseable error (eventually to be replaced), so drop the first part for now.
+ return hubException.Message.Substring(exception.Message.IndexOf(':') + 1).Trim();
+
+ return null;
+ }
}
}
diff --git a/osu.Game/Online/Multiplayer/OnlineMultiplayerClient.cs b/osu.Game/Online/Multiplayer/OnlineMultiplayerClient.cs
index 4dc23d8b85..a3423d4189 100644
--- a/osu.Game/Online/Multiplayer/OnlineMultiplayerClient.cs
+++ b/osu.Game/Online/Multiplayer/OnlineMultiplayerClient.cs
@@ -3,10 +3,12 @@
#nullable enable
+using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
+using Microsoft.AspNetCore.SignalR;
using Microsoft.AspNetCore.SignalR.Client;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
@@ -71,14 +73,23 @@ namespace osu.Game.Online.Multiplayer
}
}
- protected override Task JoinRoom(long roomId, string? password = null)
+ protected override async Task JoinRoom(long roomId, string? password = null)
{
if (!IsConnected.Value)
- return Task.FromCanceled(new CancellationToken(true));
+ throw new OperationCanceledException();
Debug.Assert(connection != null);
- return connection.InvokeAsync(nameof(IMultiplayerServer.JoinRoomWithPassword), roomId, password ?? string.Empty);
+ try
+ {
+ return await connection.InvokeAsync(nameof(IMultiplayerServer.JoinRoomWithPassword), roomId, password ?? string.Empty);
+ }
+ catch (HubException exception)
+ {
+ if (exception.GetHubExceptionMessage() == HubClientConnector.SERVER_SHUTDOWN_MESSAGE)
+ connector?.Reconnect();
+ throw;
+ }
}
protected override Task LeaveRoomInternal()
diff --git a/osu.Game/Online/Rooms/RoomCategory.cs b/osu.Game/Online/Rooms/RoomCategory.cs
index a1dcfa5fd9..bca4d78359 100644
--- a/osu.Game/Online/Rooms/RoomCategory.cs
+++ b/osu.Game/Online/Rooms/RoomCategory.cs
@@ -1,6 +1,8 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+using System.ComponentModel;
+
namespace osu.Game.Online.Rooms
{
public enum RoomCategory
@@ -8,5 +10,8 @@ namespace osu.Game.Online.Rooms
// used for osu-web deserialization so names shouldn't be changed.
Normal,
Spotlight,
+
+ [Description("Featured Artist")]
+ FeaturedArtist,
}
}
diff --git a/osu.Game/Online/Spectator/OnlineSpectatorClient.cs b/osu.Game/Online/Spectator/OnlineSpectatorClient.cs
index 4d6ca0b311..0f77f723db 100644
--- a/osu.Game/Online/Spectator/OnlineSpectatorClient.cs
+++ b/osu.Game/Online/Spectator/OnlineSpectatorClient.cs
@@ -5,10 +5,12 @@
using System.Diagnostics;
using System.Threading.Tasks;
+using Microsoft.AspNetCore.SignalR;
using Microsoft.AspNetCore.SignalR.Client;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Game.Online.API;
+using osu.Game.Online.Multiplayer;
namespace osu.Game.Online.Spectator
{
@@ -47,14 +49,23 @@ namespace osu.Game.Online.Spectator
}
}
- protected override Task BeginPlayingInternal(SpectatorState state)
+ protected override async Task BeginPlayingInternal(SpectatorState state)
{
if (!IsConnected.Value)
- return Task.CompletedTask;
+ return;
Debug.Assert(connection != null);
- return connection.SendAsync(nameof(ISpectatorServer.BeginPlaySession), state);
+ try
+ {
+ await connection.SendAsync(nameof(ISpectatorServer.BeginPlaySession), state);
+ }
+ catch (HubException exception)
+ {
+ if (exception.GetHubExceptionMessage() == HubClientConnector.SERVER_SHUTDOWN_MESSAGE)
+ connector?.Reconnect();
+ throw;
+ }
}
protected override Task SendFramesInternal(FrameDataBundle bundle)
diff --git a/osu.Game/Overlays/BeatmapSet/BeatmapSetHeaderContent.cs b/osu.Game/Overlays/BeatmapSet/BeatmapSetHeaderContent.cs
index 56efb725cd..e0fc7482f6 100644
--- a/osu.Game/Overlays/BeatmapSet/BeatmapSetHeaderContent.cs
+++ b/osu.Game/Overlays/BeatmapSet/BeatmapSetHeaderContent.cs
@@ -10,6 +10,7 @@ using osu.Framework.Graphics.Colour;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Localisation;
+using osu.Game.Beatmaps;
using osu.Game.Beatmaps.Drawables;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
@@ -229,6 +230,8 @@ namespace osu.Game.Overlays.BeatmapSet
{
Details.BeatmapInfo = b.NewValue;
externalLink.Link = $@"{api.WebsiteRootUrl}/beatmapsets/{BeatmapSet.Value?.OnlineID}#{b.NewValue?.Ruleset.ShortName}/{b.NewValue?.OnlineID}";
+
+ onlineStatusPill.Status = b.NewValue?.Status ?? BeatmapOnlineStatus.None;
};
}
@@ -272,7 +275,6 @@ namespace osu.Game.Overlays.BeatmapSet
featuredArtist.Alpha = setInfo.NewValue.TrackId != null ? 1 : 0;
onlineStatusPill.FadeIn(500, Easing.OutQuint);
- onlineStatusPill.Status = setInfo.NewValue.Status;
downloadButtonsContainer.FadeIn(transition_duration);
favouriteButton.FadeIn(transition_duration);
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/Scores/ScoresContainer.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs
index 591e4cf73e..5f24a6549d 100644
--- a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs
+++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs
@@ -253,7 +253,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores
noScoresPlaceholder.Hide();
- if (Beatmap.Value == null || Beatmap.Value.OnlineID <= 0 || (Beatmap.Value.BeatmapSet as IBeatmapSetOnlineInfo)?.Status <= BeatmapOnlineStatus.Pending)
+ if (Beatmap.Value == null || Beatmap.Value.OnlineID <= 0 || (Beatmap.Value.Status <= BeatmapOnlineStatus.Pending))
{
Scores = null;
Hide();
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 d1ceae604c..47a2d234d1 100644
--- a/osu.Game/Overlays/Chat/ChannelList/ChannelList.cs
+++ b/osu.Game/Overlays/Chat/ChannelList/ChannelList.cs
@@ -4,16 +4,20 @@
#nullable enable
using System;
+using System.Linq;
using System.Collections.Generic;
using osu.Framework.Allocation;
+using osu.Framework.Extensions.LocalisationExtensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
+using osu.Framework.Localisation;
using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
using osu.Game.Graphics.Sprites;
using osu.Game.Online.Chat;
using osu.Game.Overlays.Chat.Listing;
+using osu.Game.Resources.Localisation.Web;
namespace osu.Game.Overlays.Chat.ChannelList
{
@@ -22,12 +26,20 @@ namespace osu.Game.Overlays.Chat.ChannelList
public Action? OnRequestSelect;
public Action? OnRequestLeave;
+ 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 ChannelListItemFlow publicChannelFlow = null!;
- private ChannelListItemFlow privateChannelFlow = null!;
+ private OsuScrollContainer scroll = null!;
+ private FillFlowContainer groupFlow = null!;
+ private ChannelGroup announceChannelGroup = null!;
+ private ChannelGroup publicChannelGroup = null!;
+ private ChannelGroup privateChannelGroup = null!;
private ChannelListItem selector = null!;
[BackgroundDependencyLoader]
@@ -40,25 +52,22 @@ namespace osu.Game.Overlays.Chat.ChannelList
RelativeSizeAxes = Axes.Both,
Colour = colourProvider.Background6,
},
- new OsuScrollContainer
+ scroll = new OsuScrollContainer
{
- Padding = new MarginPadding { Vertical = 7 },
RelativeSizeAxes = Axes.Both,
ScrollbarAnchor = Anchor.TopRight,
ScrollDistance = 35f,
- Child = new FillFlowContainer
+ Child = groupFlow = new FillFlowContainer
{
Direction = FillDirection.Vertical,
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Children = new Drawable[]
{
- publicChannelFlow = new ChannelListItemFlow("CHANNELS"),
- selector = new ChannelListItem(ChannelListingChannel)
- {
- Margin = new MarginPadding { Bottom = 10 },
- },
- privateChannelFlow = new ChannelListItemFlow("DIRECT MESSAGES"),
+ announceChannelGroup = new ChannelGroup(ChatStrings.ChannelsListTitleANNOUNCE.ToUpper()),
+ publicChannelGroup = new ChannelGroup(ChatStrings.ChannelsListTitlePUBLIC.ToUpper()),
+ selector = new ChannelListItem(ChannelListingChannel),
+ privateChannelGroup = new ChannelGroup(ChatStrings.ChannelsListTitlePM.ToUpper()),
},
},
},
@@ -76,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)
@@ -87,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)
@@ -101,35 +114,60 @@ namespace osu.Game.Overlays.Chat.ChannelList
return channelMap[channel];
}
- private ChannelListItemFlow getFlowForChannel(Channel channel)
+ public void ScrollChannelIntoView(Channel channel) => scroll.ScrollIntoView(GetItem(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:
- throw new ArgumentOutOfRangeException();
+ return publicChannelGroup.ItemFlow;
}
}
- private class ChannelListItemFlow : FillFlowContainer
+ private void updateVisibility()
{
- public ChannelListItemFlow(string label)
+ if (announceChannelGroup.ItemFlow.Children.Count == 0)
+ announceChannelGroup.Hide();
+ else
+ announceChannelGroup.Show();
+ }
+
+ private class ChannelGroup : FillFlowContainer
+ {
+ public readonly FillFlowContainer ItemFlow;
+
+ public ChannelGroup(LocalisableString label)
{
Direction = FillDirection.Vertical;
RelativeSizeAxes = Axes.X;
AutoSizeAxes = Axes.Y;
+ Padding = new MarginPadding { Top = 8 };
- Add(new OsuSpriteText
+ Children = new Drawable[]
{
- Text = label,
- Margin = new MarginPadding { Left = 18, Bottom = 5 },
- Font = OsuFont.Torus.With(size: 12, weight: FontWeight.SemiBold),
- });
+ 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/ChatOverlayTopBar.cs b/osu.Game/Overlays/Chat/ChatOverlayTopBar.cs
index 3a8cd1fb91..79f22b51f7 100644
--- a/osu.Game/Overlays/Chat/ChatOverlayTopBar.cs
+++ b/osu.Game/Overlays/Chat/ChatOverlayTopBar.cs
@@ -13,6 +13,7 @@ using osu.Framework.Graphics.Textures;
using osu.Framework.Input.Events;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
+using osu.Game.Resources.Localisation.Web;
using osuTK;
using osuTK.Graphics;
@@ -58,7 +59,7 @@ namespace osu.Game.Overlays.Chat
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
- Text = "osu!chat",
+ Text = ChatStrings.TitleCompact,
Font = OsuFont.Torus.With(size: 16, weight: FontWeight.SemiBold),
Margin = new MarginPadding { Bottom = 2f },
},
diff --git a/osu.Game/Overlays/Chat/ChatTextBar.cs b/osu.Game/Overlays/Chat/ChatTextBar.cs
index 404d686d91..5100959eeb 100644
--- a/osu.Game/Overlays/Chat/ChatTextBar.cs
+++ b/osu.Game/Overlays/Chat/ChatTextBar.cs
@@ -14,6 +14,7 @@ using osu.Framework.Graphics.UserInterface;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using osu.Game.Online.Chat;
+using osu.Game.Resources.Localisation.Web;
using osuTK;
namespace osu.Game.Overlays.Chat
@@ -140,16 +141,16 @@ namespace osu.Game.Overlays.Chat
switch (newChannel?.Type)
{
- case ChannelType.Public:
- chattingText.Text = $"chatting in {newChannel.Name}";
+ case null:
+ chattingText.Text = string.Empty;
break;
case ChannelType.PM:
- chattingText.Text = $"chatting with {newChannel.Name}";
+ chattingText.Text = ChatStrings.TalkingWith(newChannel.Name);
break;
default:
- chattingText.Text = string.Empty;
+ chattingText.Text = ChatStrings.TalkingIn(newChannel.Name);
break;
}
}, true);
diff --git a/osu.Game/Overlays/Chat/ChatTextBox.cs b/osu.Game/Overlays/Chat/ChatTextBox.cs
index e0f949caba..1ee0e8445f 100644
--- a/osu.Game/Overlays/Chat/ChatTextBox.cs
+++ b/osu.Game/Overlays/Chat/ChatTextBox.cs
@@ -5,6 +5,7 @@
using osu.Framework.Bindables;
using osu.Game.Graphics.UserInterface;
+using osu.Game.Resources.Localisation.Web;
namespace osu.Game.Overlays.Chat
{
@@ -22,7 +23,7 @@ namespace osu.Game.Overlays.Chat
{
bool showSearch = change.NewValue;
- PlaceholderText = showSearch ? "type here to search" : "type here";
+ PlaceholderText = showSearch ? HomeStrings.SearchPlaceholder : ChatStrings.InputPlaceholder;
Text = string.Empty;
}, true);
}
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 ef479ea21b..0000000000
--- a/osu.Game/Overlays/ChatOverlayV2.cs
+++ /dev/null
@@ -1,350 +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;
-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.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
- {
- 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;
-
- private const int transition_length = 500;
- private const float default_chat_height = 0.4f;
- private const float top_bar_height = 40;
- private const float side_bar_width = 190;
- private const float chat_bar_height = 60;
-
- [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_chat_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 };
-
- 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);
- currentChannel.BindValueChanged(currentChannelChanged, true);
-
- joinedChannels.BindTo(channelManager.JoinedChannels);
- joinedChannels.BindCollectionChanged(joinedChannelsChanged, true);
-
- availableChannels.BindTo(channelManager.AvailableChannels);
- availableChannels.BindCollectionChanged(availableChannelsChanged, true);
-
- channelList.OnRequestSelect += channel => channelManager.CurrentChannel.Value = channel;
- channelList.OnRequestLeave += channel => channelManager.LeaveChannel(channel);
-
- 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();
- }
-
- 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();
- });
- }
- }
- }
-
- protected virtual ChatOverlayDrawableChannel CreateDrawableChannel(Channel newChannel) => new ChatOverlayDrawableChannel(newChannel);
-
- private void joinedChannelsChanged(object sender, NotifyCollectionChangedEventArgs args)
- {
- switch (args.Action)
- {
- case NotifyCollectionChangedAction.Add:
- IEnumerable newChannels = filterChannels(args.NewItems);
-
- foreach (var channel in newChannels)
- channelList.AddChannel(channel);
-
- break;
-
- case NotifyCollectionChangedAction.Remove:
- IEnumerable leftChannels = filterChannels(args.OldItems);
-
- 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 IEnumerable filterChannels(IList channels)
- => channels.Cast().Where(c => c.Type == ChannelType.Public || c.Type == ChannelType.PM);
-
- private void handleChatMessage(string message)
- {
- if (string.IsNullOrWhiteSpace(message))
- return;
-
- if (message[0] == '/')
- channelManager.PostCommand(message.Substring(1));
- else
- channelManager.PostMessage(message);
- }
- }
-}
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/FirstRunSetup/ScreenBeatmaps.cs b/osu.Game/Overlays/FirstRunSetup/ScreenBeatmaps.cs
index 17e04c0c99..ddcee7c040 100644
--- a/osu.Game/Overlays/FirstRunSetup/ScreenBeatmaps.cs
+++ b/osu.Game/Overlays/FirstRunSetup/ScreenBeatmaps.cs
@@ -154,12 +154,15 @@ namespace osu.Game.Overlays.FirstRunSetup
var downloadTracker = tutorialDownloader.DownloadTrackers.First();
+ downloadTracker.State.BindValueChanged(state =>
+ {
+ if (state.NewValue == DownloadState.LocallyAvailable)
+ downloadTutorialButton.Complete();
+ }, true);
+
downloadTracker.Progress.BindValueChanged(progress =>
{
downloadTutorialButton.SetProgress(progress.NewValue, false);
-
- if (progress.NewValue == 1)
- downloadTutorialButton.Complete();
}, true);
}
diff --git a/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs b/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs
index 602ace6dea..d79ba593f7 100644
--- a/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs
+++ b/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs
@@ -13,6 +13,7 @@ using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Localisation;
using osu.Framework.Platform;
+using osu.Framework.Platform.Windows;
using osu.Game.Configuration;
using osu.Game.Graphics.Containers;
using osu.Game.Graphics.UserInterface;
@@ -34,10 +35,14 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics
private Bindable sizeFullscreen;
private readonly BindableList resolutions = new BindableList(new[] { new Size(9999, 9999) });
+ private readonly IBindable fullscreenCapability = new Bindable(FullscreenCapability.Capable);
[Resolved]
private OsuGameBase game { get; set; }
+ [Resolved]
+ private GameHost host { get; set; }
+
private SettingsDropdown resolutionDropdown;
private SettingsDropdown displayDropdown;
private SettingsDropdown windowModeDropdown;
@@ -65,6 +70,9 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics
windowModes.BindTo(host.Window.SupportedWindowModes);
}
+ if (host.Window is WindowsWindow windowsWindow)
+ fullscreenCapability.BindTo(windowsWindow.FullscreenCapability);
+
Children = new Drawable[]
{
windowModeDropdown = new SettingsDropdown
@@ -139,6 +147,8 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics
}
},
};
+
+ fullscreenCapability.BindValueChanged(_ => Schedule(updateScreenModeWarning), true);
}
protected override void LoadComplete()
@@ -150,8 +160,7 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics
windowModeDropdown.Current.BindValueChanged(mode =>
{
updateDisplayModeDropdowns();
-
- windowModeDropdown.WarningText = mode.NewValue != WindowMode.Fullscreen ? GraphicsSettingsStrings.NotFullscreenNote : default;
+ updateScreenModeWarning();
}, true);
windowModes.BindCollectionChanged((sender, args) =>
@@ -213,6 +222,38 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics
}
}
+ private void updateScreenModeWarning()
+ {
+ if (windowModeDropdown.Current.Value != WindowMode.Fullscreen)
+ {
+ windowModeDropdown.SetNoticeText(GraphicsSettingsStrings.NotFullscreenNote, true);
+ return;
+ }
+
+ if (host.Window is WindowsWindow)
+ {
+ switch (fullscreenCapability.Value)
+ {
+ case FullscreenCapability.Unknown:
+ windowModeDropdown.SetNoticeText(LayoutSettingsStrings.CheckingForFullscreenCapabilities, true);
+ break;
+
+ case FullscreenCapability.Capable:
+ windowModeDropdown.SetNoticeText(LayoutSettingsStrings.OsuIsRunningExclusiveFullscreen);
+ break;
+
+ case FullscreenCapability.Incapable:
+ windowModeDropdown.SetNoticeText(LayoutSettingsStrings.UnableToRunExclusiveFullscreen, true);
+ break;
+ }
+ }
+ else
+ {
+ // We can only detect exclusive fullscreen status on windows currently.
+ windowModeDropdown.ClearNoticeText();
+ }
+ }
+
private void bindPreviewEvent(Bindable bindable)
{
bindable.ValueChanged += _ =>
diff --git a/osu.Game/Overlays/Settings/Sections/Graphics/RendererSettings.cs b/osu.Game/Overlays/Settings/Sections/Graphics/RendererSettings.cs
index 653f30a018..8c3e45cd62 100644
--- a/osu.Game/Overlays/Settings/Sections/Graphics/RendererSettings.cs
+++ b/osu.Game/Overlays/Settings/Sections/Graphics/RendererSettings.cs
@@ -48,7 +48,16 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics
frameLimiterDropdown.Current.BindValueChanged(limit =>
{
- frameLimiterDropdown.WarningText = limit.NewValue == FrameSync.Unlimited ? GraphicsSettingsStrings.UnlimitedFramesNote : default;
+ switch (limit.NewValue)
+ {
+ case FrameSync.Unlimited:
+ frameLimiterDropdown.SetNoticeText(GraphicsSettingsStrings.UnlimitedFramesNote, true);
+ break;
+
+ default:
+ frameLimiterDropdown.ClearNoticeText();
+ break;
+ }
}, true);
}
}
diff --git a/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs b/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs
index 4235dc0a05..1511d53b6b 100644
--- a/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs
+++ b/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs
@@ -117,9 +117,9 @@ namespace osu.Game.Overlays.Settings.Sections.Input
if (RuntimeInfo.OS != RuntimeInfo.Platform.Windows)
{
if (highPrecision.NewValue)
- highPrecisionMouse.WarningText = MouseSettingsStrings.HighPrecisionPlatformWarning;
+ highPrecisionMouse.SetNoticeText(MouseSettingsStrings.HighPrecisionPlatformWarning, true);
else
- highPrecisionMouse.WarningText = null;
+ highPrecisionMouse.ClearNoticeText();
}
}, true);
}
diff --git a/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs b/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs
index 802d442ced..5d31c38ae7 100644
--- a/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs
+++ b/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs
@@ -11,6 +11,7 @@ using osu.Framework.Localisation;
using osu.Framework.Platform;
using osu.Framework.Threading;
using osu.Game.Graphics;
+using osu.Game.Graphics.Containers;
using osu.Game.Graphics.Sprites;
using osuTK;
using osu.Game.Localisation;
@@ -95,11 +96,13 @@ namespace osu.Game.Overlays.Settings.Sections.Input
Origin = Anchor.TopCentre,
Text = TabletSettingsStrings.NoTabletDetected,
},
- new SettingsNoticeText(colours)
+ new LinkFlowContainer(cp => cp.Colour = colours.Yellow)
{
TextAnchor = Anchor.TopCentre,
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
+ RelativeSizeAxes = Axes.X,
+ AutoSizeAxes = Axes.Y,
}.With(t =>
{
if (RuntimeInfo.OS == RuntimeInfo.Platform.Windows || RuntimeInfo.OS == RuntimeInfo.Platform.Linux)
diff --git a/osu.Game/Overlays/Settings/Sections/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/Settings/Sections/UserInterface/MainMenuSettings.cs b/osu.Game/Overlays/Settings/Sections/UserInterface/MainMenuSettings.cs
index 284e9cb2de..fceffa09c5 100644
--- a/osu.Game/Overlays/Settings/Sections/UserInterface/MainMenuSettings.cs
+++ b/osu.Game/Overlays/Settings/Sections/UserInterface/MainMenuSettings.cs
@@ -61,7 +61,10 @@ namespace osu.Game.Overlays.Settings.Sections.UserInterface
user.BindValueChanged(u =>
{
- backgroundSourceDropdown.WarningText = u.NewValue?.IsSupporter != true ? UserInterfaceStrings.NotSupporterNote : default;
+ if (u.NewValue?.IsSupporter != true)
+ backgroundSourceDropdown.SetNoticeText(UserInterfaceStrings.NotSupporterNote, true);
+ else
+ backgroundSourceDropdown.ClearNoticeText();
}, true);
}
}
diff --git a/osu.Game/Overlays/Settings/SettingsItem.cs b/osu.Game/Overlays/Settings/SettingsItem.cs
index ee9daa1c0d..ea076b77ac 100644
--- a/osu.Game/Overlays/Settings/SettingsItem.cs
+++ b/osu.Game/Overlays/Settings/SettingsItem.cs
@@ -41,7 +41,7 @@ namespace osu.Game.Overlays.Settings
private SpriteText labelText;
- private OsuTextFlowContainer warningText;
+ private OsuTextFlowContainer noticeText;
public bool ShowsDefaultIndicator = true;
private readonly Container defaultValueIndicatorContainer;
@@ -70,27 +70,32 @@ namespace osu.Game.Overlays.Settings
}
///
- /// Text to be displayed at the bottom of this .
- /// Generally used to recommend the user change their setting as the current one is considered sub-optimal.
+ /// Clear any warning text.
///
- public LocalisableString? WarningText
+ public void ClearNoticeText()
{
- set
+ noticeText?.Expire();
+ noticeText = null;
+ }
+
+ ///
+ /// Set the text to be displayed at the bottom of this .
+ /// Generally used to provide feedback to a user about a sub-optimal setting.
+ ///
+ /// The text to display.
+ /// Whether the text is in a warning state. Will decide how this is visually represented.
+ public void SetNoticeText(LocalisableString text, bool isWarning = false)
+ {
+ ClearNoticeText();
+
+ // construct lazily for cases where the label is not needed (may be provided by the Control).
+ FlowContent.Add(noticeText = new LinkFlowContainer(cp => cp.Colour = isWarning ? colours.Yellow : colours.Green)
{
- bool hasValue = value != default;
-
- if (warningText == null)
- {
- if (!hasValue)
- return;
-
- // construct lazily for cases where the label is not needed (may be provided by the Control).
- FlowContent.Add(warningText = new SettingsNoticeText(colours) { Margin = new MarginPadding { Bottom = 5 } });
- }
-
- warningText.Alpha = hasValue ? 1 : 0;
- warningText.Text = value ?? default;
- }
+ RelativeSizeAxes = Axes.X,
+ AutoSizeAxes = Axes.Y,
+ Margin = new MarginPadding { Bottom = 5 },
+ Text = text,
+ });
}
public virtual Bindable Current
diff --git a/osu.Game/Overlays/Settings/SettingsNoticeText.cs b/osu.Game/Overlays/Settings/SettingsNoticeText.cs
deleted file mode 100644
index 76ecf7edd4..0000000000
--- a/osu.Game/Overlays/Settings/SettingsNoticeText.cs
+++ /dev/null
@@ -1,19 +0,0 @@
-// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
-// See the LICENCE file in the repository root for full licence text.
-
-using osu.Framework.Graphics;
-using osu.Game.Graphics;
-using osu.Game.Graphics.Containers;
-
-namespace osu.Game.Overlays.Settings
-{
- public class SettingsNoticeText : LinkFlowContainer
- {
- public SettingsNoticeText(OsuColour colours)
- : base(s => s.Colour = colours.Yellow)
- {
- RelativeSizeAxes = Axes.X;
- AutoSizeAxes = Axes.Y;
- }
- }
-}
diff --git a/osu.Game/Overlays/Volume/VolumeMeter.cs b/osu.Game/Overlays/Volume/VolumeMeter.cs
index 46b8b35da2..929c362bd8 100644
--- a/osu.Game/Overlays/Volume/VolumeMeter.cs
+++ b/osu.Game/Overlays/Volume/VolumeMeter.cs
@@ -329,7 +329,7 @@ namespace osu.Game.Overlays.Volume
if (isPrecise)
{
- scrollAccumulation += delta * adjust_step * 0.1;
+ scrollAccumulation += delta * adjust_step;
while (Precision.AlmostBigger(Math.Abs(scrollAccumulation), precision))
{
diff --git a/osu.Game/Rulesets/Edit/DistancedHitObjectComposer.cs b/osu.Game/Rulesets/Edit/DistancedHitObjectComposer.cs
index 7019dad803..aaee15eae8 100644
--- a/osu.Game/Rulesets/Edit/DistancedHitObjectComposer.cs
+++ b/osu.Game/Rulesets/Edit/DistancedHitObjectComposer.cs
@@ -26,6 +26,8 @@ namespace osu.Game.Rulesets.Edit
public abstract class DistancedHitObjectComposer : HitObjectComposer, IDistanceSnapProvider, IScrollBindingHandler
where TObject : HitObject
{
+ private const float adjust_step = 0.1f;
+
public Bindable DistanceSpacingMultiplier { get; } = new BindableDouble(1.0)
{
MinValue = 0.1,
@@ -61,7 +63,7 @@ namespace osu.Game.Rulesets.Edit
Child = distanceSpacingSlider = new ExpandableSlider>
{
Current = { BindTarget = DistanceSpacingMultiplier },
- KeyboardStep = 0.1f,
+ KeyboardStep = adjust_step,
}
}
});
@@ -93,7 +95,7 @@ namespace osu.Game.Rulesets.Edit
{
case GlobalAction.EditorIncreaseDistanceSpacing:
case GlobalAction.EditorDecreaseDistanceSpacing:
- return adjustDistanceSpacing(e.Action, 0.1f);
+ return adjustDistanceSpacing(e.Action, adjust_step);
}
return false;
@@ -109,7 +111,7 @@ namespace osu.Game.Rulesets.Edit
{
case GlobalAction.EditorIncreaseDistanceSpacing:
case GlobalAction.EditorDecreaseDistanceSpacing:
- return adjustDistanceSpacing(e.Action, e.ScrollAmount * (e.IsPrecise ? 0.01f : 0.1f));
+ return adjustDistanceSpacing(e.Action, e.ScrollAmount * adjust_step);
}
return false;
diff --git a/osu.Game/Screens/Edit/BottomBar.cs b/osu.Game/Screens/Edit/BottomBar.cs
new file mode 100644
index 0000000000..62caaced89
--- /dev/null
+++ b/osu.Game/Screens/Edit/BottomBar.cs
@@ -0,0 +1,81 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Framework.Allocation;
+using osu.Framework.Extensions.Color4Extensions;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Effects;
+using osu.Framework.Graphics.Shapes;
+using osu.Game.Overlays;
+using osu.Game.Screens.Edit.Components;
+using osu.Game.Screens.Edit.Components.Timelines.Summary;
+using osuTK;
+using osuTK.Graphics;
+
+namespace osu.Game.Screens.Edit
+{
+ internal class BottomBar : CompositeDrawable
+ {
+ public TestGameplayButton TestGameplayButton { get; private set; }
+
+ [BackgroundDependencyLoader]
+ private void load(OverlayColourProvider colourProvider, Editor editor)
+ {
+ Anchor = Anchor.BottomLeft;
+ Origin = Anchor.BottomLeft;
+
+ RelativeSizeAxes = Axes.X;
+
+ Height = 60;
+
+ Masking = true;
+ EdgeEffect = new EdgeEffectParameters
+ {
+ Colour = Color4.Black.Opacity(0.2f),
+ Type = EdgeEffectType.Shadow,
+ Radius = 10f,
+ };
+
+ InternalChildren = new Drawable[]
+ {
+ new Box
+ {
+ RelativeSizeAxes = Axes.Both,
+ Colour = colourProvider.Background4,
+ },
+ new Container
+ {
+ RelativeSizeAxes = Axes.Both,
+ Child = new GridContainer
+ {
+ RelativeSizeAxes = Axes.Both,
+ ColumnDimensions = new[]
+ {
+ new Dimension(GridSizeMode.Absolute, 170),
+ new Dimension(),
+ new Dimension(GridSizeMode.Absolute, 220),
+ new Dimension(GridSizeMode.Absolute, 120),
+ },
+ Content = new[]
+ {
+ new Drawable[]
+ {
+ new TimeInfoContainer { RelativeSizeAxes = Axes.Both },
+ new SummaryTimeline { RelativeSizeAxes = Axes.Both },
+ new PlaybackControl { RelativeSizeAxes = Axes.Both },
+ TestGameplayButton = new TestGameplayButton
+ {
+ RelativeSizeAxes = Axes.Both,
+ Padding = new MarginPadding { Left = 10 },
+ Size = new Vector2(1),
+ Action = editor.TestGameplay,
+ }
+ },
+ }
+ },
+ }
+ };
+ }
+ }
+}
diff --git a/osu.Game/Screens/Edit/Components/BottomBarContainer.cs b/osu.Game/Screens/Edit/Components/BottomBarContainer.cs
index 08091fc3f7..3c63da3a4a 100644
--- a/osu.Game/Screens/Edit/Components/BottomBarContainer.cs
+++ b/osu.Game/Screens/Edit/Components/BottomBarContainer.cs
@@ -8,20 +8,19 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Game.Beatmaps;
-using osu.Game.Graphics;
+using osuTK.Graphics;
namespace osu.Game.Screens.Edit.Components
{
public class BottomBarContainer : Container
{
- private const float corner_radius = 5;
private const float contents_padding = 15;
protected readonly IBindable Beatmap = new Bindable();
protected readonly IBindable