Merge remote-tracking branch 'origin/master' into rearrangeable-playlist

This commit is contained in:
smoogipoo
2020-02-04 16:55:55 +09:00
181 changed files with 3643 additions and 1690 deletions

View File

@ -135,7 +135,7 @@ csharp_preferred_modifier_order = public,private,protected,internal,new,abstract
csharp_style_expression_bodied_accessors = true:warning csharp_style_expression_bodied_accessors = true:warning
csharp_style_expression_bodied_constructors = false:none csharp_style_expression_bodied_constructors = false:none
csharp_style_expression_bodied_indexers = true:warning csharp_style_expression_bodied_indexers = true:warning
csharp_style_expression_bodied_methods = true:silent csharp_style_expression_bodied_methods = false:silent
csharp_style_expression_bodied_operators = true:warning csharp_style_expression_bodied_operators = true:warning
csharp_style_expression_bodied_properties = true:warning csharp_style_expression_bodied_properties = true:warning
csharp_style_expression_bodied_local_functions = true:silent csharp_style_expression_bodied_local_functions = true:silent

View File

@ -54,6 +54,6 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="ppy.osu.Game.Resources" Version="2019.1230.0" /> <PackageReference Include="ppy.osu.Game.Resources" Version="2019.1230.0" />
<PackageReference Include="ppy.osu.Framework.Android" Version="2020.125.0" /> <PackageReference Include="ppy.osu.Framework.Android" Version="2020.131.0" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@ -20,7 +20,9 @@ namespace osu.Game.Rulesets.Catch.UI
internal readonly CatcherArea CatcherArea; internal readonly CatcherArea CatcherArea;
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => base.ReceivePositionalInputAt(screenSpacePos) || CatcherArea.ReceivePositionalInputAt(screenSpacePos); public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) =>
// only check the X position; handle all vertical space.
base.ReceivePositionalInputAt(new Vector2(screenSpacePos.X, ScreenSpaceDrawQuad.Centre.Y));
public CatchPlayfield(BeatmapDifficulty difficulty, Func<CatchHitObject, DrawableHitObject<CatchHitObject>> createDrawableRepresentation) public CatchPlayfield(BeatmapDifficulty difficulty, Func<CatchHitObject, DrawableHitObject<CatchHitObject>> createDrawableRepresentation)
{ {

View File

@ -7,12 +7,10 @@ using System.Linq;
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Audio; using osu.Framework.Audio;
using osu.Framework.Graphics;
using osu.Framework.IO.Stores; using osu.Framework.IO.Stores;
using osu.Framework.Testing; using osu.Framework.Testing;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Screens;
using osu.Game.Screens.Play; using osu.Game.Screens.Play;
using osu.Game.Skinning; using osu.Game.Skinning;
using osu.Game.Tests.Visual; using osu.Game.Tests.Visual;
@ -21,7 +19,7 @@ using osuTK.Graphics;
namespace osu.Game.Rulesets.Osu.Tests namespace osu.Game.Rulesets.Osu.Tests
{ {
public class TestSceneLegacyBeatmapSkin : OsuTestScene public class TestSceneLegacyBeatmapSkin : ScreenTestScene
{ {
[Resolved] [Resolved]
private AudioManager audio { get; set; } private AudioManager audio { get; set; }
@ -65,7 +63,8 @@ namespace osu.Game.Rulesets.Osu.Tests
ExposedPlayer player; ExposedPlayer player;
Beatmap.Value = new CustomSkinWorkingBeatmap(audio, beatmapHasColours); Beatmap.Value = new CustomSkinWorkingBeatmap(audio, beatmapHasColours);
Child = new OsuScreenStack(player = new ExposedPlayer(userHasCustomColours)) { RelativeSizeAxes = Axes.Both };
LoadScreen(player = new ExposedPlayer(userHasCustomColours));
return player; return player;
} }

View File

@ -44,6 +44,7 @@ namespace osu.Game.Rulesets.Osu.Tests
private const double time_during_slide_2 = 3000; private const double time_during_slide_2 = 3000;
private const double time_during_slide_3 = 3500; private const double time_during_slide_3 = 3500;
private const double time_during_slide_4 = 3800; private const double time_during_slide_4 = 3800;
private const double time_slider_end = 4000;
private List<JudgementResult> judgementResults; private List<JudgementResult> judgementResults;
private bool allJudgedFired; private bool allJudgedFired;
@ -284,6 +285,48 @@ namespace osu.Game.Rulesets.Osu.Tests
AddAssert("Tracking acquired", assertMidSliderJudgements); AddAssert("Tracking acquired", assertMidSliderJudgements);
} }
/// <summary>
/// Scenario:
/// - Press a key on the slider head
/// - While holding the key, move cursor close to the edge of tracking area
/// - Keep the cursor on the edge of tracking area until the slider ends
/// Expected Result:
/// A passing test case will have the slider track the cursor throughout the whole test.
/// </summary>
[Test]
public void TestTrackingAreaEdge()
{
performTest(new List<ReplayFrame>
{
new OsuReplayFrame { Position = new Vector2(0, 0), Actions = { OsuAction.LeftButton }, Time = time_slider_start },
new OsuReplayFrame { Position = new Vector2(0, OsuHitObject.OBJECT_RADIUS * 1.19f), Actions = { OsuAction.LeftButton }, Time = time_slider_start + 250 },
new OsuReplayFrame { Position = new Vector2(slider_path_length, OsuHitObject.OBJECT_RADIUS * 1.199f), Actions = { OsuAction.LeftButton }, Time = time_slider_end },
});
AddAssert("Tracking kept", assertGreatJudge);
}
/// <summary>
/// Scenario:
/// - Press a key on the slider head
/// - While holding the key, move cursor just outside the tracking area
/// - Keep the cursor just outside the tracking area until the slider ends
/// Expected Result:
/// A passing test case will have the slider drop the tracking on frame 2.
/// </summary>
[Test]
public void TestTrackingAreaOutsideEdge()
{
performTest(new List<ReplayFrame>
{
new OsuReplayFrame { Position = new Vector2(0, 0), Actions = { OsuAction.LeftButton }, Time = time_slider_start },
new OsuReplayFrame { Position = new Vector2(0, OsuHitObject.OBJECT_RADIUS * 1.21f), Actions = { OsuAction.LeftButton }, Time = time_slider_start + 250 },
new OsuReplayFrame { Position = new Vector2(slider_path_length, OsuHitObject.OBJECT_RADIUS * 1.201f), Actions = { OsuAction.LeftButton }, Time = time_slider_end },
});
AddAssert("Tracking dropped", assertMidSliderJudgementFail);
}
private bool assertGreatJudge() => judgementResults.Last().Type == HitResult.Great; private bool assertGreatJudge() => judgementResults.Last().Type == HitResult.Great;
private bool assertHeadMissTailTracked() => judgementResults[^2].Type == HitResult.Great && judgementResults.First().Type == HitResult.Miss; private bool assertHeadMissTailTracked() => judgementResults[^2].Type == HitResult.Great && judgementResults.First().Type == HitResult.Miss;
@ -294,6 +337,8 @@ namespace osu.Game.Rulesets.Osu.Tests
private ScoreAccessibleReplayPlayer currentPlayer; private ScoreAccessibleReplayPlayer currentPlayer;
private const float slider_path_length = 25;
private void performTest(List<ReplayFrame> frames) private void performTest(List<ReplayFrame> frames)
{ {
AddStep("load player", () => AddStep("load player", () =>
@ -309,8 +354,8 @@ namespace osu.Game.Rulesets.Osu.Tests
Path = new SliderPath(PathType.PerfectCurve, new[] Path = new SliderPath(PathType.PerfectCurve, new[]
{ {
Vector2.Zero, Vector2.Zero,
new Vector2(25, 0), new Vector2(slider_path_length, 0),
}, 25), }, slider_path_length),
} }
}, },
BeatmapInfo = BeatmapInfo =

View File

@ -4,7 +4,7 @@
<PackageReference Include="Appveyor.TestLogger" Version="2.0.0" /> <PackageReference Include="Appveyor.TestLogger" Version="2.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.4.0" /> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.4.0" />
<PackageReference Include="NUnit" Version="3.12.0" /> <PackageReference Include="NUnit" Version="3.12.0" />
<PackageReference Include="NUnit3TestAdapter" Version="3.16.0" /> <PackageReference Include="NUnit3TestAdapter" Version="3.15.1" />
<PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" /> <PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" />
</ItemGroup> </ItemGroup>
<PropertyGroup Label="Project"> <PropertyGroup Label="Project">

View File

@ -23,7 +23,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections
/// <summary> /// <summary>
/// The start time of <see cref="Start"/>. /// The start time of <see cref="Start"/>.
/// </summary> /// </summary>
public readonly Bindable<double> StartTime = new Bindable<double>(); public readonly Bindable<double> StartTime = new BindableDouble();
/// <summary> /// <summary>
/// The <see cref="DrawableOsuHitObject"/> which <see cref="FollowPoint"/>s will exit from. /// The <see cref="DrawableOsuHitObject"/> which <see cref="FollowPoint"/>s will exit from.

View File

@ -22,7 +22,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
private readonly IBindable<Vector2> positionBindable = new Bindable<Vector2>(); private readonly IBindable<Vector2> positionBindable = new Bindable<Vector2>();
private readonly IBindable<int> stackHeightBindable = new Bindable<int>(); private readonly IBindable<int> stackHeightBindable = new Bindable<int>();
private readonly IBindable<float> scaleBindable = new Bindable<float>(); private readonly IBindable<float> scaleBindable = new BindableFloat();
public OsuAction? HitAction => HitArea.HitAction; public OsuAction? HitAction => HitArea.HitAction;

View File

@ -37,7 +37,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
InternalChild = scaleContainer = new ReverseArrowPiece(); InternalChild = scaleContainer = new ReverseArrowPiece();
} }
private readonly IBindable<float> scaleBindable = new Bindable<float>(); private readonly IBindable<float> scaleBindable = new BindableFloat();
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load() private void load()

View File

@ -37,7 +37,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
private readonly IBindable<Vector2> positionBindable = new Bindable<Vector2>(); private readonly IBindable<Vector2> positionBindable = new Bindable<Vector2>();
private readonly IBindable<int> stackHeightBindable = new Bindable<int>(); private readonly IBindable<int> stackHeightBindable = new Bindable<int>();
private readonly IBindable<float> scaleBindable = new Bindable<float>(); private readonly IBindable<float> scaleBindable = new BindableFloat();
public DrawableSlider(Slider s) public DrawableSlider(Slider s)
: base(s) : base(s)

View File

@ -52,7 +52,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
}; };
} }
private readonly IBindable<float> scaleBindable = new Bindable<float>(); private readonly IBindable<float> scaleBindable = new BindableFloat();
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load() private void load()

View File

@ -23,7 +23,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
public Func<OsuAction?> GetInitialHitAction; public Func<OsuAction?> GetInitialHitAction;
private readonly Slider slider; private readonly Slider slider;
public readonly Drawable FollowCircle; private readonly Drawable followCircle;
private readonly DrawableSlider drawableSlider; private readonly DrawableSlider drawableSlider;
public SliderBall(Slider slider, DrawableSlider drawableSlider = null) public SliderBall(Slider slider, DrawableSlider drawableSlider = null)
@ -38,7 +38,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
Children = new[] Children = new[]
{ {
FollowCircle = new FollowCircleContainer followCircle = new FollowCircleContainer
{ {
Origin = Anchor.Centre, Origin = Anchor.Centre,
Anchor = Anchor.Centre, Anchor = Anchor.Centre,
@ -95,8 +95,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
tracking = value; tracking = value;
FollowCircle.ScaleTo(tracking ? 2f : 1, 300, Easing.OutQuint); followCircle.ScaleTo(tracking ? 2.4f : 1f, 300, Easing.OutQuint);
FollowCircle.FadeTo(tracking ? 1f : 0, 300, Easing.OutQuint); followCircle.FadeTo(tracking ? 1f : 0, 300, Easing.OutQuint);
} }
} }
@ -149,7 +149,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
// in valid time range // in valid time range
Time.Current >= slider.StartTime && Time.Current < slider.EndTime && Time.Current >= slider.StartTime && Time.Current < slider.EndTime &&
// in valid position range // in valid position range
lastScreenSpaceMousePosition.HasValue && FollowCircle.ReceivePositionalInputAt(lastScreenSpaceMousePosition.Value) && lastScreenSpaceMousePosition.HasValue && followCircle.ReceivePositionalInputAt(lastScreenSpaceMousePosition.Value) &&
// valid action // valid action
(actions?.Any(isValidTrackingAction) ?? false); (actions?.Any(isValidTrackingAction) ?? false);
} }

View File

@ -57,7 +57,7 @@ namespace osu.Game.Rulesets.Osu.Objects
public double Radius => OBJECT_RADIUS * Scale; public double Radius => OBJECT_RADIUS * Scale;
public readonly Bindable<float> ScaleBindable = new Bindable<float>(1); public readonly Bindable<float> ScaleBindable = new BindableFloat(1);
public float Scale public float Scale
{ {

View File

@ -57,7 +57,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
autoCursorScale = config.GetBindable<bool>(OsuSetting.AutoCursorSize); autoCursorScale = config.GetBindable<bool>(OsuSetting.AutoCursorSize);
autoCursorScale.ValueChanged += _ => calculateScale(); autoCursorScale.ValueChanged += _ => calculateScale();
CursorScale = new Bindable<float>(); CursorScale = new BindableFloat();
CursorScale.ValueChanged += e => ActiveCursor.Scale = cursorTrail.Scale = new Vector2(e.NewValue); CursorScale.ValueChanged += e => ActiveCursor.Scale = cursorTrail.Scale = new Vector2(e.NewValue);
calculateScale(); calculateScale();

View File

@ -50,7 +50,7 @@ namespace osu.Game.Rulesets.Osu.UI
{ {
Add(localCursorContainer = new OsuCursorContainer()); Add(localCursorContainer = new OsuCursorContainer());
localCursorScale = new Bindable<float>(); localCursorScale = new BindableFloat();
localCursorScale.BindTo(localCursorContainer.CursorScale); localCursorScale.BindTo(localCursorContainer.CursorScale);
localCursorScale.BindValueChanged(scale => cursorScaleContainer.Scale = new Vector2(scale.NewValue), true); localCursorScale.BindValueChanged(scale => cursorScaleContainer.Scale = new Vector2(scale.NewValue), true);
} }

View File

@ -118,17 +118,19 @@ namespace osu.Game.Tests.Editor
[Test] [Test]
public void TestGetSnappedDurationFromDistance() public void TestGetSnappedDurationFromDistance()
{ {
assertSnappedDuration(50, 0); assertSnappedDuration(0, 0);
assertSnappedDuration(50, 1000);
assertSnappedDuration(100, 1000); assertSnappedDuration(100, 1000);
assertSnappedDuration(150, 1000); assertSnappedDuration(150, 2000);
assertSnappedDuration(200, 2000); assertSnappedDuration(200, 2000);
assertSnappedDuration(250, 2000); assertSnappedDuration(250, 3000);
AddStep("set slider multiplier = 2", () => composer.EditorBeatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier = 2); AddStep("set slider multiplier = 2", () => composer.EditorBeatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier = 2);
assertSnappedDuration(0, 0);
assertSnappedDuration(50, 0); assertSnappedDuration(50, 0);
assertSnappedDuration(100, 0); assertSnappedDuration(100, 1000);
assertSnappedDuration(150, 0); assertSnappedDuration(150, 1000);
assertSnappedDuration(200, 1000); assertSnappedDuration(200, 1000);
assertSnappedDuration(250, 1000); assertSnappedDuration(250, 1000);
@ -139,8 +141,8 @@ namespace osu.Game.Tests.Editor
}); });
assertSnappedDuration(50, 0); assertSnappedDuration(50, 0);
assertSnappedDuration(100, 0); assertSnappedDuration(100, 500);
assertSnappedDuration(150, 0); assertSnappedDuration(150, 500);
assertSnappedDuration(200, 500); assertSnappedDuration(200, 500);
assertSnappedDuration(250, 500); assertSnappedDuration(250, 500);
assertSnappedDuration(400, 1000); assertSnappedDuration(400, 1000);
@ -149,17 +151,17 @@ namespace osu.Game.Tests.Editor
[Test] [Test]
public void GetSnappedDistanceFromDistance() public void GetSnappedDistanceFromDistance()
{ {
assertSnappedDistance(50, 0); assertSnappedDistance(50, 100);
assertSnappedDistance(100, 100); assertSnappedDistance(100, 100);
assertSnappedDistance(150, 100); assertSnappedDistance(150, 200);
assertSnappedDistance(200, 200); assertSnappedDistance(200, 200);
assertSnappedDistance(250, 200); assertSnappedDistance(250, 300);
AddStep("set slider multiplier = 2", () => composer.EditorBeatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier = 2); AddStep("set slider multiplier = 2", () => composer.EditorBeatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier = 2);
assertSnappedDistance(50, 0); assertSnappedDistance(50, 0);
assertSnappedDistance(100, 0); assertSnappedDistance(100, 200);
assertSnappedDistance(150, 0); assertSnappedDistance(150, 200);
assertSnappedDistance(200, 200); assertSnappedDistance(200, 200);
assertSnappedDistance(250, 200); assertSnappedDistance(250, 200);
@ -170,8 +172,8 @@ namespace osu.Game.Tests.Editor
}); });
assertSnappedDistance(50, 0); assertSnappedDistance(50, 0);
assertSnappedDistance(100, 0); assertSnappedDistance(100, 200);
assertSnappedDistance(150, 0); assertSnappedDistance(150, 200);
assertSnappedDistance(200, 200); assertSnappedDistance(200, 200);
assertSnappedDistance(250, 200); assertSnappedDistance(250, 200);
assertSnappedDistance(400, 400); assertSnappedDistance(400, 400);

View File

@ -68,10 +68,10 @@ namespace osu.Game.Tests.Visual.Background
[SetUp] [SetUp]
public virtual void SetUp() => Schedule(() => public virtual void SetUp() => Schedule(() =>
{ {
Child = new OsuScreenStack(songSelect = new DummySongSelect()) var stack = new OsuScreenStack { RelativeSizeAxes = Axes.Both };
{ Child = stack;
RelativeSizeAxes = Axes.Both
}; stack.Push(songSelect = new DummySongSelect());
}); });
/// <summary> /// <summary>
@ -277,7 +277,7 @@ namespace osu.Game.Tests.Visual.Background
private void setupUserSettings() private void setupUserSettings()
{ {
AddUntilStep("Song select has selection", () => songSelect.Carousel.SelectedBeatmap != null); AddUntilStep("Song select has selection", () => songSelect.Carousel?.SelectedBeatmap != null);
AddStep("Set default user settings", () => AddStep("Set default user settings", () =>
{ {
SelectedMods.Value = SelectedMods.Value.Concat(new[] { new OsuModNoFail() }).ToArray(); SelectedMods.Value = SelectedMods.Value.Concat(new[] { new OsuModNoFail() }).ToArray();
@ -302,8 +302,8 @@ namespace osu.Game.Tests.Visual.Background
} }
public readonly Bindable<bool> DimEnabled = new Bindable<bool>(); public readonly Bindable<bool> DimEnabled = new Bindable<bool>();
public readonly Bindable<double> DimLevel = new Bindable<double>(); public readonly Bindable<double> DimLevel = new BindableDouble();
public readonly Bindable<double> BlurLevel = new Bindable<double>(); public readonly Bindable<double> BlurLevel = new BindableDouble();
public new BeatmapCarousel Carousel => base.Carousel; public new BeatmapCarousel Carousel => base.Carousel;

View File

@ -85,64 +85,64 @@ namespace osu.Game.Tests.Visual.Editor
{ {
} }
protected override void CreateContent(Vector2 startPosition) protected override void CreateContent()
{ {
AddInternal(new Circle AddInternal(new Circle
{ {
Origin = Anchor.Centre, Origin = Anchor.Centre,
Size = new Vector2(5), Size = new Vector2(5),
Position = startPosition Position = StartPosition
}); });
int beatIndex = 0; int indexFromPlacement = 0;
for (float s = startPosition.X + DistanceSpacing; s <= DrawWidth && beatIndex < MaxIntervals; s += DistanceSpacing, beatIndex++) for (float s = StartPosition.X + DistanceSpacing; s <= DrawWidth && indexFromPlacement < MaxIntervals; s += DistanceSpacing, indexFromPlacement++)
{ {
AddInternal(new Circle AddInternal(new Circle
{ {
Origin = Anchor.Centre, Origin = Anchor.Centre,
Size = new Vector2(5, 10), Size = new Vector2(5, 10),
Position = new Vector2(s, startPosition.Y), Position = new Vector2(s, StartPosition.Y),
Colour = GetColourForBeatIndex(beatIndex) Colour = GetColourForIndexFromPlacement(indexFromPlacement)
}); });
} }
beatIndex = 0; indexFromPlacement = 0;
for (float s = startPosition.X - DistanceSpacing; s >= 0 && beatIndex < MaxIntervals; s -= DistanceSpacing, beatIndex++) for (float s = StartPosition.X - DistanceSpacing; s >= 0 && indexFromPlacement < MaxIntervals; s -= DistanceSpacing, indexFromPlacement++)
{ {
AddInternal(new Circle AddInternal(new Circle
{ {
Origin = Anchor.Centre, Origin = Anchor.Centre,
Size = new Vector2(5, 10), Size = new Vector2(5, 10),
Position = new Vector2(s, startPosition.Y), Position = new Vector2(s, StartPosition.Y),
Colour = GetColourForBeatIndex(beatIndex) Colour = GetColourForIndexFromPlacement(indexFromPlacement)
}); });
} }
beatIndex = 0; indexFromPlacement = 0;
for (float s = startPosition.Y + DistanceSpacing; s <= DrawHeight && beatIndex < MaxIntervals; s += DistanceSpacing, beatIndex++) for (float s = StartPosition.Y + DistanceSpacing; s <= DrawHeight && indexFromPlacement < MaxIntervals; s += DistanceSpacing, indexFromPlacement++)
{ {
AddInternal(new Circle AddInternal(new Circle
{ {
Origin = Anchor.Centre, Origin = Anchor.Centre,
Size = new Vector2(10, 5), Size = new Vector2(10, 5),
Position = new Vector2(startPosition.X, s), Position = new Vector2(StartPosition.X, s),
Colour = GetColourForBeatIndex(beatIndex) Colour = GetColourForIndexFromPlacement(indexFromPlacement)
}); });
} }
beatIndex = 0; indexFromPlacement = 0;
for (float s = startPosition.Y - DistanceSpacing; s >= 0 && beatIndex < MaxIntervals; s -= DistanceSpacing, beatIndex++) for (float s = StartPosition.Y - DistanceSpacing; s >= 0 && indexFromPlacement < MaxIntervals; s -= DistanceSpacing, indexFromPlacement++)
{ {
AddInternal(new Circle AddInternal(new Circle
{ {
Origin = Anchor.Centre, Origin = Anchor.Centre,
Size = new Vector2(10, 5), Size = new Vector2(10, 5),
Position = new Vector2(startPosition.X, s), Position = new Vector2(StartPosition.X, s),
Colour = GetColourForBeatIndex(beatIndex) Colour = GetColourForIndexFromPlacement(indexFromPlacement)
}); });
} }
} }

View File

@ -1,146 +1,15 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using System;
using System.Collections.Generic;
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Audio;
using osu.Framework.Bindables;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Timing;
using osu.Game.Beatmaps;
using osu.Game.Graphics.UserInterface;
using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Objects;
using osu.Game.Screens.Edit;
using osu.Game.Screens.Edit.Compose.Components.Timeline; using osu.Game.Screens.Edit.Compose.Components.Timeline;
using osuTK;
using osuTK.Graphics;
namespace osu.Game.Tests.Visual.Editor namespace osu.Game.Tests.Visual.Editor
{ {
[TestFixture] [TestFixture]
public class TestSceneTimelineBlueprintContainer : EditorClockTestScene public class TestSceneTimelineBlueprintContainer : TimelineTestScene
{ {
public override IReadOnlyList<Type> RequiredTypes => new[] public override Drawable CreateTestComponent() => new TimelineBlueprintContainer();
{
typeof(TimelineArea),
typeof(Timeline),
typeof(TimelineButton),
typeof(CentreMarker)
};
[BackgroundDependencyLoader]
private void load(AudioManager audio)
{
Beatmap.Value = new WaveformTestBeatmap(audio);
var editorBeatmap = new EditorBeatmap((Beatmap<HitObject>)Beatmap.Value.Beatmap, BeatDivisor);
Dependencies.Cache(editorBeatmap);
Dependencies.CacheAs<IBeatSnapProvider>(editorBeatmap);
Children = new Drawable[]
{
new FillFlowContainer
{
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Vertical,
Spacing = new Vector2(0, 5),
Children = new Drawable[]
{
new StartStopButton(),
new AudioVisualiser(),
}
},
new TimelineArea
{
Child = new TimelineBlueprintContainer(),
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.X,
Size = new Vector2(0.8f, 100)
}
};
}
private class AudioVisualiser : CompositeDrawable
{
private readonly Drawable marker;
[Resolved]
private IBindable<WorkingBeatmap> beatmap { get; set; }
[Resolved]
private IAdjustableClock adjustableClock { get; set; }
public AudioVisualiser()
{
Size = new Vector2(250, 25);
InternalChildren = new[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Alpha = 0.25f,
},
marker = new Box
{
RelativePositionAxes = Axes.X,
RelativeSizeAxes = Axes.Y,
Width = 2,
}
};
}
protected override void Update()
{
base.Update();
if (beatmap.Value.Track.IsLoaded)
marker.X = (float)(adjustableClock.CurrentTime / beatmap.Value.Track.Length);
}
}
private class StartStopButton : OsuButton
{
private IAdjustableClock adjustableClock;
private bool started;
public StartStopButton()
{
BackgroundColour = Color4.SlateGray;
Size = new Vector2(100, 50);
Text = "Start";
Action = onClick;
}
[BackgroundDependencyLoader]
private void load(IAdjustableClock adjustableClock)
{
this.adjustableClock = adjustableClock;
}
private void onClick()
{
if (started)
{
adjustableClock.Stop();
Text = "Start";
}
else
{
adjustableClock.Start();
Text = "Stop";
}
started = !started;
}
}
} }
} }

View File

@ -0,0 +1,32 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Game.Screens.Edit.Compose.Components;
using osu.Game.Screens.Edit.Compose.Components.Timeline;
using osuTK;
namespace osu.Game.Tests.Visual.Editor
{
[TestFixture]
public class TestSceneTimelineTickDisplay : TimelineTestScene
{
public override Drawable CreateTestComponent() => new TimelineTickDisplay();
[BackgroundDependencyLoader]
private void load()
{
BeatDivisor.Value = 4;
Add(new BeatDivisorControl(BeatDivisor)
{
Anchor = Anchor.TopRight,
Origin = Anchor.TopRight,
Margin = new MarginPadding(30),
Size = new Vector2(90)
});
}
}
}

View File

@ -0,0 +1,148 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Collections.Generic;
using osu.Framework.Allocation;
using osu.Framework.Audio;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Timing;
using osu.Game.Beatmaps;
using osu.Game.Graphics.UserInterface;
using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Objects;
using osu.Game.Screens.Edit;
using osu.Game.Screens.Edit.Compose.Components.Timeline;
using osuTK;
using osuTK.Graphics;
namespace osu.Game.Tests.Visual.Editor
{
public abstract class TimelineTestScene : EditorClockTestScene
{
public override IReadOnlyList<Type> RequiredTypes => new[]
{
typeof(TimelineArea),
typeof(Timeline),
typeof(TimelineButton),
typeof(CentreMarker)
};
protected TimelineArea TimelineArea { get; private set; }
[BackgroundDependencyLoader]
private void load(AudioManager audio)
{
Beatmap.Value = new WaveformTestBeatmap(audio);
var editorBeatmap = new EditorBeatmap((Beatmap<HitObject>)Beatmap.Value.Beatmap, BeatDivisor);
Dependencies.Cache(editorBeatmap);
Dependencies.CacheAs<IBeatSnapProvider>(editorBeatmap);
AddRange(new Drawable[]
{
new FillFlowContainer
{
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Vertical,
Spacing = new Vector2(0, 5),
Children = new Drawable[]
{
new StartStopButton(),
new AudioVisualiser(),
}
},
TimelineArea = new TimelineArea
{
Child = CreateTestComponent(),
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.X,
Size = new Vector2(0.8f, 100),
}
});
}
public abstract Drawable CreateTestComponent();
private class AudioVisualiser : CompositeDrawable
{
private readonly Drawable marker;
[Resolved]
private IBindable<WorkingBeatmap> beatmap { get; set; }
[Resolved]
private IAdjustableClock adjustableClock { get; set; }
public AudioVisualiser()
{
Size = new Vector2(250, 25);
InternalChildren = new[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Alpha = 0.25f,
},
marker = new Box
{
RelativePositionAxes = Axes.X,
RelativeSizeAxes = Axes.Y,
Width = 2,
}
};
}
protected override void Update()
{
base.Update();
if (beatmap.Value.Track.IsLoaded)
marker.X = (float)(adjustableClock.CurrentTime / beatmap.Value.Track.Length);
}
}
private class StartStopButton : OsuButton
{
private IAdjustableClock adjustableClock;
private bool started;
public StartStopButton()
{
BackgroundColour = Color4.SlateGray;
Size = new Vector2(100, 50);
Text = "Start";
Action = onClick;
}
[BackgroundDependencyLoader]
private void load(IAdjustableClock adjustableClock)
{
this.adjustableClock = adjustableClock;
}
private void onClick()
{
if (started)
{
adjustableClock.Stop();
Text = "Start";
}
else
{
adjustableClock.Start();
Text = "Stop";
}
started = !started;
}
}
}
}

View File

@ -2,30 +2,52 @@
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using System.Linq; using System.Linq;
using NUnit.Framework;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Screens; using osu.Framework.Screens;
using osu.Game.Configuration; using osu.Game.Configuration;
using osu.Game.Rulesets; using osu.Game.Rulesets;
using osu.Game.Rulesets.Catch;
using osu.Game.Rulesets.Mania;
using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Taiko;
using osu.Game.Screens.Play; using osu.Game.Screens.Play;
namespace osu.Game.Tests.Visual namespace osu.Game.Tests.Visual.Gameplay
{ {
/// <summary> /// <summary>
/// A base class which runs <see cref="Player"/> test for all available rulesets. /// A base class which runs <see cref="Player"/> test for all available rulesets.
/// Steps to be run for each ruleset should be added via <see cref="AddCheckSteps"/>. /// Steps to be run for each ruleset should be added via <see cref="AddCheckSteps"/>.
/// </summary> /// </summary>
public abstract class AllPlayersTestScene : RateAdjustedBeatmapTestScene public abstract class TestSceneAllRulesetPlayers : RateAdjustedBeatmapTestScene
{ {
protected Player Player; protected Player Player;
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(RulesetStore rulesets) private void load(RulesetStore rulesets)
{ {
foreach (var r in rulesets.AvailableRulesets) OsuConfigManager manager;
Dependencies.Cache(manager = new OsuConfigManager(LocalStorage));
manager.GetBindable<double>(OsuSetting.DimLevel).Value = 1.0;
}
[Test]
public void TestOsu() => runForRuleset(new OsuRuleset().RulesetInfo);
[Test]
public void TestTaiko() => runForRuleset(new TaikoRuleset().RulesetInfo);
[Test]
public void TestCatch() => runForRuleset(new CatchRuleset().RulesetInfo);
[Test]
public void TestMania() => runForRuleset(new ManiaRuleset().RulesetInfo);
private void runForRuleset(RulesetInfo ruleset)
{ {
Player p = null; Player p = null;
AddStep(r.Name, () => p = loadPlayerFor(r)); AddStep($"load {ruleset.Name} player", () => p = loadPlayerFor(ruleset));
AddUntilStep("player loaded", () => AddUntilStep("player loaded", () =>
{ {
if (p?.IsLoaded == true) if (p?.IsLoaded == true)
@ -40,11 +62,6 @@ namespace osu.Game.Tests.Visual
AddCheckSteps(); AddCheckSteps();
} }
OsuConfigManager manager;
Dependencies.Cache(manager = new OsuConfigManager(LocalStorage));
manager.GetBindable<double>(OsuSetting.DimLevel).Value = 1.0;
}
protected abstract void AddCheckSteps(); protected abstract void AddCheckSteps();
private Player loadPlayerFor(RulesetInfo rulesetInfo) private Player loadPlayerFor(RulesetInfo rulesetInfo)

View File

@ -12,7 +12,7 @@ using osu.Game.Storyboards;
namespace osu.Game.Tests.Visual.Gameplay namespace osu.Game.Tests.Visual.Gameplay
{ {
[Description("Player instantiated with an autoplay mod.")] [Description("Player instantiated with an autoplay mod.")]
public class TestSceneAutoplay : AllPlayersTestScene public class TestSceneAutoplay : TestSceneAllRulesetPlayers
{ {
private ClockBackedTestWorkingBeatmap.TrackVirtualManual track; private ClockBackedTestWorkingBeatmap.TrackVirtualManual track;

View File

@ -10,7 +10,7 @@ using osu.Game.Screens.Play;
namespace osu.Game.Tests.Visual.Gameplay namespace osu.Game.Tests.Visual.Gameplay
{ {
public class TestSceneFailAnimation : AllPlayersTestScene public class TestSceneFailAnimation : TestSceneAllRulesetPlayers
{ {
protected override Player CreatePlayer(Ruleset ruleset) protected override Player CreatePlayer(Ruleset ruleset)
{ {
@ -20,7 +20,7 @@ namespace osu.Game.Tests.Visual.Gameplay
public override IReadOnlyList<Type> RequiredTypes => new[] public override IReadOnlyList<Type> RequiredTypes => new[]
{ {
typeof(AllPlayersTestScene), typeof(TestSceneAllRulesetPlayers),
typeof(TestPlayer), typeof(TestPlayer),
typeof(Player), typeof(Player),
}; };

View File

@ -10,7 +10,7 @@ using osu.Game.Screens.Play;
namespace osu.Game.Tests.Visual.Gameplay namespace osu.Game.Tests.Visual.Gameplay
{ {
public class TestSceneFailJudgement : AllPlayersTestScene public class TestSceneFailJudgement : TestSceneAllRulesetPlayers
{ {
protected override Player CreatePlayer(Ruleset ruleset) protected override Player CreatePlayer(Ruleset ruleset)
{ {

View File

@ -2,12 +2,16 @@
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using System; using System;
using System.Linq;
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Testing;
using osu.Game.Configuration; using osu.Game.Configuration;
using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Mods;
using osu.Game.Screens.Play; using osu.Game.Screens.Play;
using osuTK.Input;
namespace osu.Game.Tests.Visual.Gameplay namespace osu.Game.Tests.Visual.Gameplay
{ {
@ -15,7 +19,9 @@ namespace osu.Game.Tests.Visual.Gameplay
{ {
private HUDOverlay hudOverlay; private HUDOverlay hudOverlay;
private Drawable hideTarget => hudOverlay.KeyCounter; // best way of checking hideTargets without exposing. // best way to check without exposing.
private Drawable hideTarget => hudOverlay.KeyCounter;
private FillFlowContainer<KeyCounter> keyCounterFlow => hudOverlay.KeyCounter.ChildrenOfType<FillFlowContainer<KeyCounter>>().First();
[Resolved] [Resolved]
private OsuConfigManager config { get; set; } private OsuConfigManager config { get; set; }
@ -28,6 +34,7 @@ namespace osu.Game.Tests.Visual.Gameplay
AddAssert("showhud is set", () => hudOverlay.ShowHud.Value); AddAssert("showhud is set", () => hudOverlay.ShowHud.Value);
AddAssert("hidetarget is visible", () => hideTarget.IsPresent); AddAssert("hidetarget is visible", () => hideTarget.IsPresent);
AddAssert("key counter flow is visible", () => keyCounterFlow.IsPresent);
AddAssert("pause button is visible", () => hudOverlay.HoldToQuit.IsPresent); AddAssert("pause button is visible", () => hudOverlay.HoldToQuit.IsPresent);
} }
@ -50,6 +57,9 @@ namespace osu.Game.Tests.Visual.Gameplay
AddUntilStep("hidetarget is hidden", () => !hideTarget.IsPresent); AddUntilStep("hidetarget is hidden", () => !hideTarget.IsPresent);
AddAssert("pause button is still visible", () => hudOverlay.HoldToQuit.IsPresent); AddAssert("pause button is still visible", () => hudOverlay.HoldToQuit.IsPresent);
// Key counter flow container should not be affected by this, only the key counter display will be hidden as checked above.
AddAssert("key counter flow not affected", () => keyCounterFlow.IsPresent);
} }
[Test] [Test]
@ -68,12 +78,40 @@ namespace osu.Game.Tests.Visual.Gameplay
AddAssert("config unchanged", () => originalConfigValue == config.Get<bool>(OsuSetting.ShowInterface)); AddAssert("config unchanged", () => originalConfigValue == config.Get<bool>(OsuSetting.ShowInterface));
} }
[Test]
public void TestChangeHUDVisibilityOnHiddenKeyCounter()
{
bool keyCounterVisibleValue = false;
createNew();
AddStep("save keycounter visible value", () => keyCounterVisibleValue = config.Get<bool>(OsuSetting.KeyOverlay));
AddStep("set keycounter visible false", () =>
{
config.Set<bool>(OsuSetting.KeyOverlay, false);
hudOverlay.KeyCounter.AlwaysVisible.Value = false;
});
AddStep("set showhud false", () => hudOverlay.ShowHud.Value = false);
AddUntilStep("hidetarget is hidden", () => !hideTarget.IsPresent);
AddAssert("key counters hidden", () => !keyCounterFlow.IsPresent);
AddStep("set showhud true", () => hudOverlay.ShowHud.Value = true);
AddUntilStep("hidetarget is visible", () => hideTarget.IsPresent);
AddAssert("key counters still hidden", () => !keyCounterFlow.IsPresent);
AddStep("return value", () => config.Set<bool>(OsuSetting.KeyOverlay, keyCounterVisibleValue));
}
private void createNew(Action<HUDOverlay> action = null) private void createNew(Action<HUDOverlay> action = null)
{ {
AddStep("create overlay", () => AddStep("create overlay", () =>
{ {
Child = hudOverlay = new HUDOverlay(null, null, null, Array.Empty<Mod>()); Child = hudOverlay = new HUDOverlay(null, null, null, Array.Empty<Mod>());
// Add any key just to display the key counter visually.
hudOverlay.KeyCounter.Add(new KeyCounterKeyboard(Key.Space));
action?.Invoke(hudOverlay); action?.Invoke(hudOverlay);
}); });
} }

View File

@ -36,6 +36,7 @@ namespace osu.Game.Tests.Visual.Gameplay
public override void SetUpSteps() public override void SetUpSteps()
{ {
base.SetUpSteps(); base.SetUpSteps();
AddStep("resume player", () => Player.GameplayClockContainer.Start()); AddStep("resume player", () => Player.GameplayClockContainer.Start());
confirmClockRunning(true); confirmClockRunning(true);
} }

View File

@ -207,9 +207,11 @@ namespace osu.Game.Tests.Visual.Gameplay
{ {
RelativeSizeAxes = Axes.Both; RelativeSizeAxes = Axes.Both;
OsuScreenStack stack;
InternalChildren = new Drawable[] InternalChildren = new Drawable[]
{ {
new OsuScreenStack(screen) stack = new OsuScreenStack
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
}, },
@ -224,6 +226,8 @@ namespace osu.Game.Tests.Visual.Gameplay
Origin = Anchor.TopLeft, Origin = Anchor.TopLeft,
} }
}; };
stack.Push(screen);
} }
} }

View File

@ -10,7 +10,7 @@ using osu.Game.Storyboards;
namespace osu.Game.Tests.Visual.Gameplay namespace osu.Game.Tests.Visual.Gameplay
{ {
public class TestScenePlayerReferenceLeaking : AllPlayersTestScene public class TestScenePlayerReferenceLeaking : TestSceneAllRulesetPlayers
{ {
private readonly WeakList<WorkingBeatmap> workingWeakReferences = new WeakList<WorkingBeatmap>(); private readonly WeakList<WorkingBeatmap> workingWeakReferences = new WeakList<WorkingBeatmap>();

View File

@ -13,7 +13,7 @@ using osu.Game.Screens.Play;
namespace osu.Game.Tests.Visual.Gameplay namespace osu.Game.Tests.Visual.Gameplay
{ {
[Description("Player instantiated with a replay.")] [Description("Player instantiated with a replay.")]
public class TestSceneReplay : AllPlayersTestScene public class TestSceneReplay : TestSceneAllRulesetPlayers
{ {
protected override Player CreatePlayer(Ruleset ruleset) protected override Player CreatePlayer(Ruleset ruleset)
{ {

View File

@ -75,10 +75,16 @@ namespace osu.Game.Tests.Visual.Gameplay
public void ResultsWithoutPlayer() public void ResultsWithoutPlayer()
{ {
TestSoloResults screen = null; TestSoloResults screen = null;
OsuScreenStack stack;
AddStep("load results", () => Child = new OsuScreenStack(screen = createResultsScreen()) AddStep("load results", () =>
{
Child = stack = new OsuScreenStack
{ {
RelativeSizeAxes = Axes.Both RelativeSizeAxes = Axes.Both
};
stack.Push(screen = createResultsScreen());
}); });
AddUntilStep("wait for loaded", () => screen.IsLoaded); AddUntilStep("wait for loaded", () => screen.IsLoaded);
AddAssert("retry overlay not present", () => screen.RetryOverlay == null); AddAssert("retry overlay not present", () => screen.RetryOverlay == null);
@ -102,11 +108,14 @@ namespace osu.Game.Tests.Visual.Gameplay
public TestResultsContainer(IScreen screen) public TestResultsContainer(IScreen screen)
{ {
RelativeSizeAxes = Axes.Both; RelativeSizeAxes = Axes.Both;
OsuScreenStack stack;
InternalChild = new OsuScreenStack(screen) InternalChild = stack = new OsuScreenStack
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
}; };
stack.Push(screen);
} }
} }

View File

@ -31,7 +31,7 @@ namespace osu.Game.Tests.Visual.Menus
protected IntroTestScene() protected IntroTestScene()
{ {
Drawable introStack = null; OsuScreenStack introStack = null;
Children = new Drawable[] Children = new Drawable[]
{ {
@ -57,10 +57,12 @@ namespace osu.Game.Tests.Visual.Menus
introStack?.Expire(); introStack?.Expire();
Add(introStack = new OsuScreenStack(CreateScreen()) Add(introStack = new OsuScreenStack
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
}); });
introStack.Push(CreateScreen());
}); });
} }

View File

@ -16,7 +16,9 @@ namespace osu.Game.Tests.Visual.Multiplayer
{ {
int index = 0; int index = 0;
OsuScreenStack screenStack = new OsuScreenStack(new TestMultiplayerSubScreen(index)) { RelativeSizeAxes = Axes.Both }; OsuScreenStack screenStack = new OsuScreenStack { RelativeSizeAxes = Axes.Both };
screenStack.Push(new TestMultiplayerSubScreen(index));
Children = new Drawable[] Children = new Drawable[]
{ {

View File

@ -0,0 +1,133 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Collections.Generic;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Configuration;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Platform;
using osu.Framework.Screens;
using osu.Framework.Testing;
using osu.Game.Beatmaps;
using osu.Game.Configuration;
using osu.Game.Graphics.UserInterface;
using osu.Game.Online.API;
using osu.Game.Overlays;
using osu.Game.Rulesets;
using osu.Game.Screens;
using osu.Game.Screens.Menu;
using osuTK.Graphics;
using IntroSequence = osu.Game.Configuration.IntroSequence;
namespace osu.Game.Tests.Visual.Navigation
{
/// <summary>
/// A scene which tests full game flow.
/// </summary>
public abstract class OsuGameTestScene : ManualInputManagerTestScene
{
private GameHost host;
protected TestOsuGame Game;
[BackgroundDependencyLoader]
private void load(GameHost host)
{
this.host = host;
Child = new Box
{
RelativeSizeAxes = Axes.Both,
Colour = Color4.Black,
};
}
[SetUpSteps]
public void SetUpSteps()
{
AddStep("Create new game instance", () =>
{
if (Game != null)
{
Remove(Game);
Game.Dispose();
}
RecycleLocalStorage();
// see MouseSettings
var frameworkConfig = host.Dependencies.Get<FrameworkConfigManager>();
frameworkConfig.GetBindable<double>(FrameworkSetting.CursorSensitivity).Disabled = false;
Game = new TestOsuGame(LocalStorage, API);
Game.SetHost(host);
// todo: this can be removed once we can run audio tracks without a device present
// see https://github.com/ppy/osu/issues/1302
Game.LocalConfig.Set(OsuSetting.IntroSequence, IntroSequence.Circles);
Add(Game);
});
AddUntilStep("Wait for load", () => Game.IsLoaded);
AddUntilStep("Wait for intro", () => Game.ScreenStack.CurrentScreen is IntroScreen);
ConfirmAtMainMenu();
}
protected void PushAndConfirm(Func<Screen> newScreen)
{
Screen screen = null;
AddStep("Push new screen", () => Game.ScreenStack.Push(screen = newScreen()));
AddUntilStep("Wait for new screen", () => Game.ScreenStack.CurrentScreen == screen && screen.IsLoaded);
}
protected void ConfirmAtMainMenu() => AddUntilStep("Wait for main menu", () => Game.ScreenStack.CurrentScreen is MainMenu menu && menu.IsLoaded);
public class TestOsuGame : OsuGame
{
public new ScreenStack ScreenStack => base.ScreenStack;
public new BackButton BackButton => base.BackButton;
public new BeatmapManager BeatmapManager => base.BeatmapManager;
public new SettingsPanel Settings => base.Settings;
public new OsuConfigManager LocalConfig => base.LocalConfig;
public new Bindable<WorkingBeatmap> Beatmap => base.Beatmap;
public new Bindable<RulesetInfo> Ruleset => base.Ruleset;
protected override Loader CreateLoader() => new TestLoader();
public new void PerformFromScreen(Action<IScreen> action, IEnumerable<Type> validScreens = null) => base.PerformFromScreen(action, validScreens);
public TestOsuGame(Storage storage, IAPIProvider api)
{
Storage = storage;
API = api;
}
protected override void LoadComplete()
{
base.LoadComplete();
API.Login("Rhythm Champion", "osu!");
}
}
public class TestLoader : Loader
{
protected override ShaderPrecompiler CreateShaderPrecompiler() => new TestShaderPrecompiler();
private class TestShaderPrecompiler : ShaderPrecompiler
{
protected override bool AllLoaded => true;
}
}
}
}

View File

@ -0,0 +1,72 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using NUnit.Framework;
using osu.Game.Screens.Menu;
using osu.Game.Screens.Play;
using osu.Game.Screens.Select;
namespace osu.Game.Tests.Visual.Navigation
{
public class TestScenePerformFromScreen : OsuGameTestScene
{
[Test]
public void TestPerformAtMenu()
{
AddAssert("could perform immediately", () =>
{
bool actionPerformed = false;
Game.PerformFromScreen(_ => actionPerformed = true);
return actionPerformed;
});
}
[Test]
public void TestPerformAtSongSelect()
{
PushAndConfirm(() => new PlaySongSelect());
AddAssert("could perform immediately", () =>
{
bool actionPerformed = false;
Game.PerformFromScreen(_ => actionPerformed = true, new[] { typeof(PlaySongSelect) });
return actionPerformed;
});
}
[Test]
public void TestPerformAtMenuFromSongSelect()
{
PushAndConfirm(() => new PlaySongSelect());
bool actionPerformed = false;
AddStep("try to perform", () => Game.PerformFromScreen(_ => actionPerformed = true));
AddUntilStep("returned to menu", () => Game.ScreenStack.CurrentScreen is MainMenu);
AddAssert("did perform", () => actionPerformed);
}
[Test]
public void TestPerformAtSongSelectFromPlayerLoader()
{
PushAndConfirm(() => new PlaySongSelect());
PushAndConfirm(() => new PlayerLoader(() => new Player()));
bool actionPerformed = false;
AddStep("try to perform", () => Game.PerformFromScreen(_ => actionPerformed = true, new[] { typeof(PlaySongSelect) }));
AddUntilStep("returned to song select", () => Game.ScreenStack.CurrentScreen is PlaySongSelect);
AddAssert("did perform", () => actionPerformed);
}
[Test]
public void TestPerformAtMenuFromPlayerLoader()
{
PushAndConfirm(() => new PlaySongSelect());
PushAndConfirm(() => new PlayerLoader(() => new Player()));
bool actionPerformed = false;
AddStep("try to perform", () => Game.PerformFromScreen(_ => actionPerformed = true));
AddUntilStep("returned to song select", () => Game.ScreenStack.CurrentScreen is MainMenu);
AddAssert("did perform", () => actionPerformed);
}
}
}

View File

@ -0,0 +1,110 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. 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;
using osu.Framework.Screens;
using osu.Game.Beatmaps;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Mania;
using osu.Game.Rulesets.Osu;
using osu.Game.Screens.Menu;
namespace osu.Game.Tests.Visual.Navigation
{
public class TestScenePresentBeatmap : OsuGameTestScene
{
[Test]
public void TestFromMainMenu()
{
var firstImport = importBeatmap(1);
presentAndConfirm(firstImport);
AddStep("return to menu", () => Game.ScreenStack.CurrentScreen.Exit());
AddUntilStep("wait for menu", () => Game.ScreenStack.CurrentScreen is MainMenu);
var secondimport = importBeatmap(2);
presentAndConfirm(secondimport);
}
[Test]
public void TestFromMainMenuDifferentRuleset()
{
var firstImport = importBeatmap(1);
presentAndConfirm(firstImport);
AddStep("return to menu", () => Game.ScreenStack.CurrentScreen.Exit());
AddUntilStep("wait for menu", () => Game.ScreenStack.CurrentScreen is MainMenu);
var secondimport = importBeatmap(2, new ManiaRuleset().RulesetInfo);
presentAndConfirm(secondimport);
}
[Test]
public void TestFromSongSelect()
{
var firstImport = importBeatmap(1);
presentAndConfirm(firstImport);
var secondimport = importBeatmap(2);
presentAndConfirm(secondimport);
}
[Test]
public void TestFromSongSelectDifferentRuleset()
{
var firstImport = importBeatmap(1);
presentAndConfirm(firstImport);
var secondimport = importBeatmap(2, new ManiaRuleset().RulesetInfo);
presentAndConfirm(secondimport);
}
private Func<BeatmapSetInfo> importBeatmap(int i, RulesetInfo ruleset = null)
{
BeatmapSetInfo imported = null;
AddStep($"import beatmap {i}", () =>
{
var difficulty = new BeatmapDifficulty();
var metadata = new BeatmapMetadata
{
Artist = "SomeArtist",
AuthorString = "SomeAuthor",
Title = $"import {i}"
};
imported = Game.BeatmapManager.Import(new BeatmapSetInfo
{
Hash = Guid.NewGuid().ToString(),
OnlineBeatmapSetID = i,
Metadata = metadata,
Beatmaps = new List<BeatmapInfo>
{
new BeatmapInfo
{
OnlineBeatmapID = i * 1024,
Metadata = metadata,
BaseDifficulty = difficulty,
Ruleset = ruleset ?? new OsuRuleset().RulesetInfo
},
}
}).Result;
});
AddAssert($"import {i} succeeded", () => imported != null);
return () => imported;
}
private void presentAndConfirm(Func<BeatmapSetInfo> getImport)
{
AddStep("present beatmap", () => Game.PresentBeatmap(getImport()));
AddUntilStep("wait for song select", () => Game.ScreenStack.CurrentScreen is Screens.Select.SongSelect);
AddUntilStep("correct beatmap displayed", () => Game.Beatmap.Value.BeatmapSetInfo.ID == getImport().ID);
AddAssert("correct ruleset selected", () => Game.Ruleset.Value.ID == getImport().Beatmaps.First().Ruleset.ID);
}
}
}

View File

@ -1,90 +1,36 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using System;
using System.Linq; using System.Linq;
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Audio.Track; using osu.Framework.Audio.Track;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Platform;
using osu.Framework.Screens;
using osu.Framework.Testing;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Configuration;
using osu.Game.Graphics.UserInterface;
using osu.Game.Online.API;
using osu.Game.Overlays; using osu.Game.Overlays;
using osu.Game.Overlays.Mods; using osu.Game.Overlays.Mods;
using osu.Game.Screens;
using osu.Game.Screens.Menu;
using osu.Game.Screens.Play; using osu.Game.Screens.Play;
using osu.Game.Screens.Select; using osu.Game.Screens.Select;
using osu.Game.Tests.Beatmaps.IO; using osu.Game.Tests.Beatmaps.IO;
using osuTK; using osuTK;
using osuTK.Graphics;
using osuTK.Input; using osuTK.Input;
using IntroSequence = osu.Game.Configuration.IntroSequence;
namespace osu.Game.Tests.Visual.Menus namespace osu.Game.Tests.Visual.Navigation
{ {
public class TestSceneScreenNavigation : ManualInputManagerTestScene public class TestSceneScreenNavigation : OsuGameTestScene
{ {
private const float click_padding = 25; private const float click_padding = 25;
private GameHost host; private Vector2 backButtonPosition => Game.ToScreenSpace(new Vector2(click_padding, Game.LayoutRectangle.Bottom - click_padding));
private TestOsuGame game;
private Vector2 backButtonPosition => game.ToScreenSpace(new Vector2(click_padding, game.LayoutRectangle.Bottom - click_padding)); private Vector2 optionsButtonPosition => Game.ToScreenSpace(new Vector2(click_padding, click_padding));
private Vector2 optionsButtonPosition => game.ToScreenSpace(new Vector2(click_padding, click_padding));
[BackgroundDependencyLoader]
private void load(GameHost host)
{
this.host = host;
Child = new Box
{
RelativeSizeAxes = Axes.Both,
Colour = Color4.Black,
};
}
[SetUpSteps]
public void SetUpSteps()
{
AddStep("Create new game instance", () =>
{
if (game != null)
{
Remove(game);
game.Dispose();
}
game = new TestOsuGame(LocalStorage, API);
game.SetHost(host);
// todo: this can be removed once we can run audio trakcs without a device present
// see https://github.com/ppy/osu/issues/1302
game.LocalConfig.Set(OsuSetting.IntroSequence, IntroSequence.Circles);
Add(game);
});
AddUntilStep("Wait for load", () => game.IsLoaded);
AddUntilStep("Wait for intro", () => game.ScreenStack.CurrentScreen is IntroScreen);
confirmAtMainMenu();
}
[Test] [Test]
public void TestExitSongSelectWithEscape() public void TestExitSongSelectWithEscape()
{ {
TestSongSelect songSelect = null; TestSongSelect songSelect = null;
pushAndConfirm(() => songSelect = new TestSongSelect()); PushAndConfirm(() => songSelect = new TestSongSelect());
AddStep("Show mods overlay", () => songSelect.ModSelectOverlay.Show()); AddStep("Show mods overlay", () => songSelect.ModSelectOverlay.Show());
AddAssert("Overlay was shown", () => songSelect.ModSelectOverlay.State.Value == Visibility.Visible); AddAssert("Overlay was shown", () => songSelect.ModSelectOverlay.State.Value == Visibility.Visible);
pushEscape(); pushEscape();
@ -98,21 +44,21 @@ namespace osu.Game.Tests.Visual.Menus
{ {
Player player = null; Player player = null;
WorkingBeatmap beatmap() => game.Beatmap.Value; WorkingBeatmap beatmap() => Game.Beatmap.Value;
Track track() => beatmap().Track; Track track() => beatmap().Track;
pushAndConfirm(() => new TestSongSelect()); PushAndConfirm(() => new TestSongSelect());
AddStep("import beatmap", () => ImportBeatmapTest.LoadOszIntoOsu(game, virtualTrack: true).Wait()); AddStep("import beatmap", () => ImportBeatmapTest.LoadOszIntoOsu(Game, virtualTrack: true).Wait());
AddUntilStep("wait for selected", () => !game.Beatmap.IsDefault); AddUntilStep("wait for selected", () => !Game.Beatmap.IsDefault);
if (withUserPause) if (withUserPause)
AddStep("pause", () => game.Dependencies.Get<MusicController>().Stop()); AddStep("pause", () => Game.Dependencies.Get<MusicController>().Stop());
AddStep("press enter", () => pressAndRelease(Key.Enter)); AddStep("press enter", () => pressAndRelease(Key.Enter));
AddUntilStep("wait for player", () => (player = game.ScreenStack.CurrentScreen as Player) != null); AddUntilStep("wait for player", () => (player = Game.ScreenStack.CurrentScreen as Player) != null);
AddUntilStep("wait for fail", () => player.HasFailed); AddUntilStep("wait for fail", () => player.HasFailed);
AddUntilStep("wait for track stop", () => !track().IsRunning); AddUntilStep("wait for track stop", () => !track().IsRunning);
@ -129,13 +75,13 @@ namespace osu.Game.Tests.Visual.Menus
{ {
TestSongSelect songSelect = null; TestSongSelect songSelect = null;
pushAndConfirm(() => songSelect = new TestSongSelect()); PushAndConfirm(() => songSelect = new TestSongSelect());
AddStep("Show mods overlay", () => songSelect.ModSelectOverlay.Show()); AddStep("Show mods overlay", () => songSelect.ModSelectOverlay.Show());
AddAssert("Overlay was shown", () => songSelect.ModSelectOverlay.State.Value == Visibility.Visible); AddAssert("Overlay was shown", () => songSelect.ModSelectOverlay.State.Value == Visibility.Visible);
AddStep("Move mouse to backButton", () => InputManager.MoveMouseTo(backButtonPosition)); AddStep("Move mouse to backButton", () => InputManager.MoveMouseTo(backButtonPosition));
// BackButton handles hover using its child button, so this checks whether or not any of BackButton's children are hovered. // BackButton handles hover using its child button, so this checks whether or not any of BackButton's children are hovered.
AddUntilStep("Back button is hovered", () => InputManager.HoveredDrawables.Any(d => d.Parent == game.BackButton)); AddUntilStep("Back button is hovered", () => InputManager.HoveredDrawables.Any(d => d.Parent == Game.BackButton));
AddStep("Click back button", () => InputManager.Click(MouseButton.Left)); AddStep("Click back button", () => InputManager.Click(MouseButton.Left));
AddUntilStep("Overlay was hidden", () => songSelect.ModSelectOverlay.State.Value == Visibility.Hidden); AddUntilStep("Overlay was hidden", () => songSelect.ModSelectOverlay.State.Value == Visibility.Hidden);
@ -145,34 +91,27 @@ namespace osu.Game.Tests.Visual.Menus
[Test] [Test]
public void TestExitMultiWithEscape() public void TestExitMultiWithEscape()
{ {
pushAndConfirm(() => new Screens.Multi.Multiplayer()); PushAndConfirm(() => new Screens.Multi.Multiplayer());
exitViaEscapeAndConfirm(); exitViaEscapeAndConfirm();
} }
[Test] [Test]
public void TestExitMultiWithBackButton() public void TestExitMultiWithBackButton()
{ {
pushAndConfirm(() => new Screens.Multi.Multiplayer()); PushAndConfirm(() => new Screens.Multi.Multiplayer());
exitViaBackButtonAndConfirm(); exitViaBackButtonAndConfirm();
} }
[Test] [Test]
public void TestOpenOptionsAndExitWithEscape() public void TestOpenOptionsAndExitWithEscape()
{ {
AddUntilStep("Wait for options to load", () => game.Settings.IsLoaded); AddUntilStep("Wait for options to load", () => Game.Settings.IsLoaded);
AddStep("Enter menu", () => pressAndRelease(Key.Enter)); AddStep("Enter menu", () => pressAndRelease(Key.Enter));
AddStep("Move mouse to options overlay", () => InputManager.MoveMouseTo(optionsButtonPosition)); AddStep("Move mouse to options overlay", () => InputManager.MoveMouseTo(optionsButtonPosition));
AddStep("Click options overlay", () => InputManager.Click(MouseButton.Left)); AddStep("Click options overlay", () => InputManager.Click(MouseButton.Left));
AddAssert("Options overlay was opened", () => game.Settings.State.Value == Visibility.Visible); AddAssert("Options overlay was opened", () => Game.Settings.State.Value == Visibility.Visible);
AddStep("Hide options overlay using escape", () => pressAndRelease(Key.Escape)); AddStep("Hide options overlay using escape", () => pressAndRelease(Key.Escape));
AddAssert("Options overlay was closed", () => game.Settings.State.Value == Visibility.Hidden); AddAssert("Options overlay was closed", () => Game.Settings.State.Value == Visibility.Hidden);
}
private void pushAndConfirm(Func<Screen> newScreen)
{
Screen screen = null;
AddStep("Push new screen", () => game.ScreenStack.Push(screen = newScreen()));
AddUntilStep("Wait for new screen", () => game.ScreenStack.CurrentScreen == screen && screen.IsLoaded);
} }
private void pushEscape() => private void pushEscape() =>
@ -181,64 +120,25 @@ namespace osu.Game.Tests.Visual.Menus
private void exitViaEscapeAndConfirm() private void exitViaEscapeAndConfirm()
{ {
pushEscape(); pushEscape();
confirmAtMainMenu(); ConfirmAtMainMenu();
} }
private void exitViaBackButtonAndConfirm() private void exitViaBackButtonAndConfirm()
{ {
AddStep("Move mouse to backButton", () => InputManager.MoveMouseTo(backButtonPosition)); AddStep("Move mouse to backButton", () => InputManager.MoveMouseTo(backButtonPosition));
AddStep("Click back button", () => InputManager.Click(MouseButton.Left)); AddStep("Click back button", () => InputManager.Click(MouseButton.Left));
confirmAtMainMenu(); ConfirmAtMainMenu();
} }
private void confirmAtMainMenu() => AddUntilStep("Wait for main menu", () => game.ScreenStack.CurrentScreen is MainMenu menu && menu.IsLoaded);
private void pressAndRelease(Key key) private void pressAndRelease(Key key)
{ {
InputManager.PressKey(key); InputManager.PressKey(key);
InputManager.ReleaseKey(key); InputManager.ReleaseKey(key);
} }
private class TestOsuGame : OsuGame
{
public new ScreenStack ScreenStack => base.ScreenStack;
public new BackButton BackButton => base.BackButton;
public new SettingsPanel Settings => base.Settings;
public new OsuConfigManager LocalConfig => base.LocalConfig;
public new Bindable<WorkingBeatmap> Beatmap => base.Beatmap;
protected override Loader CreateLoader() => new TestLoader();
public TestOsuGame(Storage storage, IAPIProvider api)
{
Storage = storage;
API = api;
}
protected override void LoadComplete()
{
base.LoadComplete();
API.Login("Rhythm Champion", "osu!");
}
}
private class TestSongSelect : PlaySongSelect private class TestSongSelect : PlaySongSelect
{ {
public ModSelectOverlay ModSelectOverlay => ModSelect; public ModSelectOverlay ModSelectOverlay => ModSelect;
} }
private class TestLoader : Loader
{
protected override ShaderPrecompiler CreateShaderPrecompiler() => new TestShaderPrecompiler();
private class TestShaderPrecompiler : ShaderPrecompiler
{
protected override bool AllLoaded => true;
}
}
} }
} }

View File

@ -5,6 +5,7 @@ using NUnit.Framework;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Graphics.UserInterface; using osu.Framework.Graphics.UserInterface;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Overlays;
using osu.Game.Overlays.BeatmapSet; using osu.Game.Overlays.BeatmapSet;
using osu.Game.Rulesets; using osu.Game.Rulesets;
using System; using System;
@ -21,6 +22,9 @@ namespace osu.Game.Tests.Visual.Online
typeof(BeatmapRulesetTabItem), typeof(BeatmapRulesetTabItem),
}; };
[Cached]
private OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Blue);
private readonly TestRulesetSelector selector; private readonly TestRulesetSelector selector;
public TestSceneBeatmapRulesetSelector() public TestSceneBeatmapRulesetSelector()

View File

@ -3,12 +3,15 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.UserInterface; using osu.Framework.Graphics.UserInterface;
using osu.Framework.Testing;
using osu.Game.Graphics.UserInterface;
using osu.Game.Online.Chat; using osu.Game.Online.Chat;
using osu.Game.Overlays; using osu.Game.Overlays;
using osu.Game.Overlays.Chat; using osu.Game.Overlays.Chat;
@ -35,8 +38,21 @@ namespace osu.Game.Tests.Visual.Online
private TestChatOverlay chatOverlay; private TestChatOverlay chatOverlay;
private ChannelManager channelManager; private ChannelManager channelManager;
private readonly Channel channel1 = new Channel(new User()) { Name = "test really long username" }; private readonly List<Channel> channels;
private readonly Channel channel2 = new Channel(new User()) { Name = "test2" };
private Channel channel1 => channels[0];
private Channel channel2 => channels[1];
public TestSceneChatOverlay()
{
channels = Enumerable.Range(1, 10)
.Select(index => new Channel(new User())
{
Name = $"Channel no. {index}",
Topic = index == 3 ? null : $"We talk about the number {index} here"
})
.ToList();
}
[SetUp] [SetUp]
public void Setup() public void Setup()
@ -45,7 +61,7 @@ namespace osu.Game.Tests.Visual.Online
{ {
ChannelManagerContainer container; ChannelManagerContainer container;
Child = container = new ChannelManagerContainer(new List<Channel> { channel1, channel2 }) Child = container = new ChannelManagerContainer(channels)
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
}; };
@ -96,6 +112,47 @@ namespace osu.Game.Tests.Visual.Online
AddAssert("Selector is visible", () => chatOverlay.SelectionOverlayState == Visibility.Visible); AddAssert("Selector is visible", () => chatOverlay.SelectionOverlayState == Visibility.Visible);
} }
[Test]
public void TestSearchInSelector()
{
AddStep("search for 'no. 2'", () => chatOverlay.ChildrenOfType<SearchTextBox>().First().Text = "no. 2");
AddUntilStep("only channel 2 visible", () =>
{
var listItems = chatOverlay.ChildrenOfType<ChannelListItem>().Where(c => c.IsPresent);
return listItems.Count() == 1 && listItems.Single().Channel == channel2;
});
}
[Test]
public void TestChannelShortcutKeys()
{
AddStep("join 10 channels", () => channels.ForEach(channel => channelManager.JoinChannel(channel)));
AddStep("close channel selector", () =>
{
InputManager.PressKey(Key.Escape);
InputManager.ReleaseKey(Key.Escape);
});
AddUntilStep("wait for close", () => chatOverlay.SelectionOverlayState == Visibility.Hidden);
for (int zeroBasedIndex = 0; zeroBasedIndex < 10; ++zeroBasedIndex)
{
var oneBasedIndex = zeroBasedIndex + 1;
var targetNumberKey = oneBasedIndex % 10;
var targetChannel = channels[zeroBasedIndex];
AddStep($"press Alt+{targetNumberKey}", () => pressChannelHotkey(targetNumberKey));
AddAssert($"channel #{oneBasedIndex} is selected", () => channelManager.CurrentChannel.Value == targetChannel);
}
}
private void pressChannelHotkey(int number)
{
var channelKey = Key.Number0 + number;
InputManager.PressKey(Key.AltLeft);
InputManager.PressKey(channelKey);
InputManager.ReleaseKey(Key.AltLeft);
InputManager.ReleaseKey(channelKey);
}
private void clickDrawable(Drawable d) private void clickDrawable(Drawable d)
{ {
InputManager.MoveMouseTo(d); InputManager.MoveMouseTo(d);

View File

@ -24,8 +24,9 @@ namespace osu.Game.Tests.Visual.Online
typeof(HeaderButton), typeof(HeaderButton),
typeof(SortTabControl), typeof(SortTabControl),
typeof(ShowChildrenButton), typeof(ShowChildrenButton),
typeof(DeletedChildrenPlaceholder), typeof(DeletedCommentsCounter),
typeof(VotePill) typeof(VotePill),
typeof(CommentsPage),
}; };
protected override bool UseOnlineAPI => true; protected override bool UseOnlineAPI => true;

View File

@ -0,0 +1,162 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Collections.Generic;
using osu.Game.Overlays.Comments;
using osu.Game.Overlays;
using osu.Framework.Allocation;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Users;
using osu.Game.Graphics.UserInterface;
using osu.Framework.Bindables;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics;
using osuTK;
namespace osu.Game.Tests.Visual.Online
{
public class TestSceneCommentsPage : OsuTestScene
{
public override IReadOnlyList<Type> RequiredTypes => new[]
{
typeof(DrawableComment),
typeof(CommentsPage),
};
[Cached]
private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Purple);
private readonly BindableBool showDeleted = new BindableBool();
private readonly Container content;
public TestSceneCommentsPage()
{
Add(new FillFlowContainer
{
AutoSizeAxes = Axes.Y,
RelativeSizeAxes = Axes.X,
Direction = FillDirection.Vertical,
Spacing = new Vector2(0, 10),
Children = new Drawable[]
{
new Container
{
AutoSizeAxes = Axes.Y,
Width = 200,
Child = new OsuCheckbox
{
Current = showDeleted,
LabelText = @"Show Deleted"
}
},
content = new Container
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
}
}
});
AddStep("load comments", () => createPage(comment_bundle));
AddStep("load empty comments", () => createPage(empty_comment_bundle));
}
private void createPage(CommentBundle commentBundle)
{
content.Clear();
content.Add(new CommentsPage(commentBundle)
{
ShowDeleted = { BindTarget = showDeleted }
});
}
private static readonly CommentBundle empty_comment_bundle = new CommentBundle
{
Comments = new List<Comment>(),
Total = 0,
};
private static readonly CommentBundle comment_bundle = new CommentBundle
{
Comments = new List<Comment>
{
new Comment
{
Id = 1,
Message = "Simple test comment",
LegacyName = "TestUser1",
CreatedAt = DateTimeOffset.Now,
VotesCount = 5
},
new Comment
{
Id = 2,
Message = "This comment has been deleted :( but visible for admins",
LegacyName = "TestUser2",
CreatedAt = DateTimeOffset.Now,
DeletedAt = DateTimeOffset.Now,
VotesCount = 5
},
new Comment
{
Id = 3,
Message = "This comment is a top level",
LegacyName = "TestUser3",
CreatedAt = DateTimeOffset.Now,
RepliesCount = 2,
},
new Comment
{
Id = 4,
ParentId = 3,
Message = "And this is a reply",
RepliesCount = 1,
LegacyName = "TestUser1",
CreatedAt = DateTimeOffset.Now,
},
new Comment
{
Id = 15,
ParentId = 4,
Message = "Reply to reply",
LegacyName = "TestUser1",
CreatedAt = DateTimeOffset.Now,
},
new Comment
{
Id = 6,
ParentId = 3,
LegacyName = "TestUser11515",
CreatedAt = DateTimeOffset.Now,
DeletedAt = DateTimeOffset.Now,
},
new Comment
{
Id = 5,
Message = "This comment is voted and edited",
LegacyName = "BigBrainUser",
CreatedAt = DateTimeOffset.Now,
EditedAt = DateTimeOffset.Now,
VotesCount = 1000,
EditedById = 1,
}
},
IncludedComments = new List<Comment>(),
UserVotes = new List<long>
{
5
},
Users = new List<User>
{
new User
{
Id = 1,
Username = "Good_Admin"
}
},
TopLevelCount = 4,
Total = 7
};
}
}

View File

@ -4,10 +4,12 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Shapes;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Graphics.Containers; using osu.Game.Graphics.Containers;
using osu.Game.Overlays;
using osu.Game.Overlays.Profile.Sections; using osu.Game.Overlays.Profile.Sections;
using osu.Game.Overlays.Profile.Sections.Historical; using osu.Game.Overlays.Profile.Sections.Historical;
using osu.Game.Users; using osu.Game.Users;
@ -24,9 +26,11 @@ namespace osu.Game.Tests.Visual.Online
typeof(HistoricalSection), typeof(HistoricalSection),
typeof(PaginatedMostPlayedBeatmapContainer), typeof(PaginatedMostPlayedBeatmapContainer),
typeof(DrawableMostPlayedBeatmap), typeof(DrawableMostPlayedBeatmap),
typeof(DrawableProfileRow)
}; };
[Cached]
private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Pink);
public TestSceneHistoricalSection() public TestSceneHistoricalSection()
{ {
HistoricalSection section; HistoricalSection section;

View File

@ -11,6 +11,8 @@ using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Taiko; using osu.Game.Rulesets.Taiko;
using osu.Game.Users; using osu.Game.Users;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Game.Overlays;
using osu.Framework.Allocation;
namespace osu.Game.Tests.Visual.Online namespace osu.Game.Tests.Visual.Online
{ {
@ -22,10 +24,13 @@ namespace osu.Game.Tests.Visual.Online
typeof(ProfileRulesetTabItem), typeof(ProfileRulesetTabItem),
}; };
[Cached]
private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Green);
public TestSceneProfileRulesetSelector() public TestSceneProfileRulesetSelector()
{ {
ProfileRulesetSelector selector; ProfileRulesetSelector selector;
Bindable<User> user = new Bindable<User>(); var user = new Bindable<User>();
Child = selector = new ProfileRulesetSelector Child = selector = new ProfileRulesetSelector
{ {

View File

@ -4,11 +4,13 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Shapes;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterface;
using osu.Game.Overlays;
using osu.Game.Overlays.Profile.Header.Components; using osu.Game.Overlays.Profile.Header.Components;
using osu.Game.Users; using osu.Game.Users;
using osuTK; using osuTK;
@ -24,6 +26,9 @@ namespace osu.Game.Tests.Visual.Online
typeof(LineGraph) typeof(LineGraph)
}; };
[Cached]
private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Pink);
public TestSceneRankGraph() public TestSceneRankGraph()
{ {
RankGraph graph; RankGraph graph;

View File

@ -0,0 +1,87 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Collections.Generic;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Game.Online.API;
using osu.Game.Online.API.Requests;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Overlays;
using osu.Game.Overlays.Rankings;
namespace osu.Game.Tests.Visual.Online
{
public class TestSceneRankingsSpotlightSelector : OsuTestScene
{
public override IReadOnlyList<Type> RequiredTypes => new[]
{
typeof(SpotlightSelector),
};
protected override bool UseOnlineAPI => true;
[Cached]
private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Green);
[Resolved]
private IAPIProvider api { get; set; }
private readonly SpotlightSelector selector;
public TestSceneRankingsSpotlightSelector()
{
Add(selector = new SpotlightSelector());
}
[Test]
public void TestLocalSpotlights()
{
var spotlights = new[]
{
new APISpotlight
{
Name = "Spotlight 1",
StartDate = DateTimeOffset.Now,
EndDate = DateTimeOffset.Now,
},
new APISpotlight
{
Name = "Spotlight 2",
StartDate = DateTimeOffset.Now,
EndDate = DateTimeOffset.Now,
},
new APISpotlight
{
Name = "Spotlight 3",
StartDate = DateTimeOffset.Now,
EndDate = DateTimeOffset.Now,
},
};
AddStep("load spotlights", () => selector.Spotlights = spotlights);
AddStep("change to spotlight 3", () => selector.Current.Value = spotlights[2]);
}
[Test]
public void TestOnlineSpotlights()
{
List<APISpotlight> spotlights = null;
AddStep("retrieve spotlights", () =>
{
var req = new GetSpotlightsRequest();
req.Success += res => spotlights = res.Spotlights;
api.Perform(req);
});
AddStep("set spotlights", () =>
{
if (spotlights != null)
selector.Spotlights = spotlights;
});
}
}
}

View File

@ -5,6 +5,7 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Shapes;
@ -12,6 +13,7 @@ using osu.Game.Graphics;
using osu.Game.Graphics.Containers; using osu.Game.Graphics.Containers;
using osu.Game.Online.API.Requests; using osu.Game.Online.API.Requests;
using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.API.Requests.Responses;
using osu.Game.Overlays;
using osu.Game.Overlays.Profile.Sections; using osu.Game.Overlays.Profile.Sections;
using osu.Game.Overlays.Profile.Sections.Recent; using osu.Game.Overlays.Profile.Sections.Recent;
@ -28,6 +30,9 @@ namespace osu.Game.Tests.Visual.Online
typeof(MedalIcon) typeof(MedalIcon)
}; };
[Cached]
private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Green);
public TestSceneUserProfileRecentSection() public TestSceneUserProfileRecentSection()
{ {
Children = new Drawable[] Children = new Drawable[]
@ -131,6 +136,22 @@ namespace osu.Game.Tests.Visual.Online
Beatmap = dummyBeatmap, Beatmap = dummyBeatmap,
}, },
new APIRecentActivity new APIRecentActivity
{
User = dummyUser,
Type = RecentActivityType.Rank,
Rank = 1,
Mode = "vitaru",
Beatmap = dummyBeatmap,
},
new APIRecentActivity
{
User = dummyUser,
Type = RecentActivityType.Rank,
Rank = 1,
Mode = "fruits",
Beatmap = dummyBeatmap,
},
new APIRecentActivity
{ {
User = dummyUser, User = dummyUser,
Type = RecentActivityType.RankLost, Type = RecentActivityType.RankLost,

View File

@ -0,0 +1,101 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Collections.Generic;
using osu.Game.Overlays.Profile.Sections;
using osu.Game.Overlays.Profile.Sections.Ranks;
using osu.Framework.Graphics;
using osu.Game.Scoring;
using osu.Framework.Graphics.Containers;
using osuTK;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu.Mods;
using osu.Game.Overlays;
using osu.Framework.Allocation;
namespace osu.Game.Tests.Visual.Online
{
public class TestSceneUserProfileScores : OsuTestScene
{
public override IReadOnlyList<Type> RequiredTypes => new[]
{
typeof(DrawableProfileScore),
typeof(DrawableProfileWeightedScore),
typeof(ProfileItemContainer),
};
public TestSceneUserProfileScores()
{
var score = new ScoreInfo
{
PP = 134.32,
Rank = ScoreRank.A,
Beatmap = new BeatmapInfo
{
Metadata = new BeatmapMetadata
{
Title = "Triumph & Regret",
Artist = "typeMARS"
},
Version = "[4K] Regret"
},
Date = DateTimeOffset.Now,
Mods = new Mod[]
{
new OsuModHardRock(),
new OsuModDoubleTime(),
},
Accuracy = 0.998546
};
var noPPScore = new ScoreInfo
{
Rank = ScoreRank.B,
Beatmap = new BeatmapInfo
{
Metadata = new BeatmapMetadata
{
Title = "C18H27NO3(extend)",
Artist = "Team Grimoire"
},
Version = "[4K] Cataclysmic Hypernova"
},
Date = DateTimeOffset.Now,
Accuracy = 0.55879
};
Add(new FillFlowContainer
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Direction = FillDirection.Vertical,
Spacing = new Vector2(0, 10),
Children = new[]
{
new ColourProvidedContainer(OverlayColourScheme.Green, new DrawableProfileScore(score)),
new ColourProvidedContainer(OverlayColourScheme.Pink, new DrawableProfileScore(noPPScore)),
new ColourProvidedContainer(OverlayColourScheme.Pink, new DrawableProfileWeightedScore(score, 0.85))
}
});
}
private class ColourProvidedContainer : Container
{
[Cached]
private readonly OverlayColourProvider colourProvider;
public ColourProvidedContainer(OverlayColourScheme colourScheme, DrawableProfileScore score)
{
colourProvider = new OverlayColourProvider(colourScheme);
AutoSizeAxes = Axes.Y;
RelativeSizeAxes = Axes.X;
Add(score);
}
}
}
}

View File

@ -4,11 +4,13 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Shapes;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Graphics.Containers; using osu.Game.Graphics.Containers;
using osu.Game.Overlays;
using osu.Game.Overlays.Profile.Sections; using osu.Game.Overlays.Profile.Sections;
using osu.Game.Overlays.Profile.Sections.Ranks; using osu.Game.Overlays.Profile.Sections.Ranks;
using osu.Game.Users; using osu.Game.Users;
@ -20,7 +22,15 @@ namespace osu.Game.Tests.Visual.Online
{ {
protected override bool UseOnlineAPI => true; protected override bool UseOnlineAPI => true;
public override IReadOnlyList<Type> RequiredTypes => new[] { typeof(DrawableProfileScore), typeof(RanksSection) }; public override IReadOnlyList<Type> RequiredTypes => new[]
{
typeof(DrawableProfileScore),
typeof(DrawableProfileWeightedScore),
typeof(RanksSection)
};
[Cached]
private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Green);
public TestSceneUserRanks() public TestSceneUserRanks()
{ {

View File

@ -0,0 +1,175 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Linq;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Testing;
using osu.Game.Beatmaps;
using osu.Game.Graphics;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Mods;
using osu.Game.Screens.Select.Details;
using osuTK.Graphics;
namespace osu.Game.Tests.Visual.SongSelect
{
[System.ComponentModel.Description("Advanced beatmap statistics display")]
public class TestSceneAdvancedStats : OsuTestScene
{
private TestAdvancedStats advancedStats;
[Resolved]
private RulesetStore rulesets { get; set; }
[Resolved]
private OsuColour colours { get; set; }
[SetUp]
public void Setup() => Schedule(() => Child = advancedStats = new TestAdvancedStats
{
Width = 500
});
private BeatmapInfo exampleBeatmapInfo => new BeatmapInfo
{
RulesetID = 0,
Ruleset = rulesets.AvailableRulesets.First(),
BaseDifficulty = new BeatmapDifficulty
{
CircleSize = 7.2f,
DrainRate = 3,
OverallDifficulty = 5.7f,
ApproachRate = 3.5f
},
StarDifficulty = 4.5f
};
[Test]
public void TestNoMod()
{
AddStep("set beatmap", () => advancedStats.Beatmap = exampleBeatmapInfo);
AddStep("no mods selected", () => SelectedMods.Value = Array.Empty<Mod>());
AddAssert("first bar text is Circle Size", () => advancedStats.ChildrenOfType<SpriteText>().First().Text == "Circle Size");
AddAssert("circle size bar is white", () => barIsWhite(advancedStats.FirstValue));
AddAssert("HP drain bar is white", () => barIsWhite(advancedStats.HpDrain));
AddAssert("accuracy bar is white", () => barIsWhite(advancedStats.Accuracy));
AddAssert("approach rate bar is white", () => barIsWhite(advancedStats.ApproachRate));
}
[Test]
public void TestManiaFirstBarText()
{
AddStep("set beatmap", () => advancedStats.Beatmap = new BeatmapInfo
{
Ruleset = rulesets.GetRuleset(3),
BaseDifficulty = new BeatmapDifficulty
{
CircleSize = 5,
DrainRate = 4.3f,
OverallDifficulty = 4.5f,
ApproachRate = 3.1f
},
StarDifficulty = 8
});
AddAssert("first bar text is Key Count", () => advancedStats.ChildrenOfType<SpriteText>().First().Text == "Key Count");
}
[Test]
public void TestEasyMod()
{
AddStep("set beatmap", () => advancedStats.Beatmap = exampleBeatmapInfo);
AddStep("select EZ mod", () =>
{
var ruleset = advancedStats.Beatmap.Ruleset.CreateInstance();
SelectedMods.Value = new[] { ruleset.GetAllMods().OfType<ModEasy>().Single() };
});
AddAssert("circle size bar is blue", () => barIsBlue(advancedStats.FirstValue));
AddAssert("HP drain bar is blue", () => barIsBlue(advancedStats.HpDrain));
AddAssert("accuracy bar is blue", () => barIsBlue(advancedStats.Accuracy));
AddAssert("approach rate bar is blue", () => barIsBlue(advancedStats.ApproachRate));
}
[Test]
public void TestHardRockMod()
{
AddStep("set beatmap", () => advancedStats.Beatmap = exampleBeatmapInfo);
AddStep("select HR mod", () =>
{
var ruleset = advancedStats.Beatmap.Ruleset.CreateInstance();
SelectedMods.Value = new[] { ruleset.GetAllMods().OfType<ModHardRock>().Single() };
});
AddAssert("circle size bar is red", () => barIsRed(advancedStats.FirstValue));
AddAssert("HP drain bar is red", () => barIsRed(advancedStats.HpDrain));
AddAssert("accuracy bar is red", () => barIsRed(advancedStats.Accuracy));
AddAssert("approach rate bar is red", () => barIsRed(advancedStats.ApproachRate));
}
[Test]
public void TestUnchangedDifficultyAdjustMod()
{
AddStep("set beatmap", () => advancedStats.Beatmap = exampleBeatmapInfo);
AddStep("select unchanged Difficulty Adjust mod", () =>
{
var ruleset = advancedStats.Beatmap.Ruleset.CreateInstance();
var difficultyAdjustMod = ruleset.GetAllMods().OfType<ModDifficultyAdjust>().Single();
difficultyAdjustMod.ReadFromDifficulty(advancedStats.Beatmap.BaseDifficulty);
SelectedMods.Value = new[] { difficultyAdjustMod };
});
AddAssert("circle size bar is white", () => barIsWhite(advancedStats.FirstValue));
AddAssert("HP drain bar is white", () => barIsWhite(advancedStats.HpDrain));
AddAssert("accuracy bar is white", () => barIsWhite(advancedStats.Accuracy));
AddAssert("approach rate bar is white", () => barIsWhite(advancedStats.ApproachRate));
}
[Test]
public void TestChangedDifficultyAdjustMod()
{
AddStep("set beatmap", () => advancedStats.Beatmap = exampleBeatmapInfo);
AddStep("select changed Difficulty Adjust mod", () =>
{
var ruleset = advancedStats.Beatmap.Ruleset.CreateInstance();
var difficultyAdjustMod = ruleset.GetAllMods().OfType<ModDifficultyAdjust>().Single();
var originalDifficulty = advancedStats.Beatmap.BaseDifficulty;
var adjustedDifficulty = new BeatmapDifficulty
{
CircleSize = originalDifficulty.CircleSize,
DrainRate = originalDifficulty.DrainRate - 0.5f,
OverallDifficulty = originalDifficulty.OverallDifficulty,
ApproachRate = originalDifficulty.ApproachRate + 2.2f,
};
difficultyAdjustMod.ReadFromDifficulty(adjustedDifficulty);
SelectedMods.Value = new[] { difficultyAdjustMod };
});
AddAssert("circle size bar is white", () => barIsWhite(advancedStats.FirstValue));
AddAssert("drain rate bar is blue", () => barIsBlue(advancedStats.HpDrain));
AddAssert("accuracy bar is white", () => barIsWhite(advancedStats.Accuracy));
AddAssert("approach rate bar is red", () => barIsRed(advancedStats.ApproachRate));
}
private bool barIsWhite(AdvancedStats.StatisticRow row) => row.ModBar.AccentColour == Color4.White;
private bool barIsBlue(AdvancedStats.StatisticRow row) => row.ModBar.AccentColour == colours.BlueDark;
private bool barIsRed(AdvancedStats.StatisticRow row) => row.ModBar.AccentColour == colours.Red;
private class TestAdvancedStats : AdvancedStats
{
public new StatisticRow FirstValue => base.FirstValue;
public new StatisticRow HpDrain => base.HpDrain;
public new StatisticRow Accuracy => base.Accuracy;
public new StatisticRow ApproachRate => base.ApproachRate;
}
}
}

View File

@ -3,14 +3,8 @@
using System.Linq; using System.Linq;
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Testing;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Graphics;
using osu.Game.Graphics.UserInterface;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Mods;
using osu.Game.Screens.Select; using osu.Game.Screens.Select;
namespace osu.Game.Tests.Visual.SongSelect namespace osu.Game.Tests.Visual.SongSelect
@ -180,27 +174,5 @@ namespace osu.Game.Tests.Visual.SongSelect
OnlineBeatmapID = 162, OnlineBeatmapID = 162,
}); });
} }
[Resolved]
private RulesetStore rulesets { get; set; }
[Resolved]
private OsuColour colours { get; set; }
[Test]
public void TestModAdjustments()
{
TestAllMetrics();
Ruleset ruleset = rulesets.AvailableRulesets.First().CreateInstance();
AddStep("with EZ mod", () => SelectedMods.Value = new[] { ruleset.GetAllMods().First(m => m is ModEasy) });
AddAssert("first bar coloured blue", () => details.ChildrenOfType<Bar>().Skip(1).First().AccentColour == colours.BlueDark);
AddStep("with HR mod", () => SelectedMods.Value = new[] { ruleset.GetAllMods().First(m => m is ModHardRock) });
AddAssert("first bar coloured red", () => details.ChildrenOfType<Bar>().Skip(1).First().AccentColour == colours.Red);
}
} }
} }

View File

@ -23,6 +23,7 @@ using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Osu.Mods; using osu.Game.Rulesets.Osu.Mods;
using osu.Game.Rulesets.Taiko; using osu.Game.Rulesets.Taiko;
using osu.Game.Screens.Play;
using osu.Game.Screens.Select; using osu.Game.Screens.Select;
using osu.Game.Screens.Select.Carousel; using osu.Game.Screens.Select.Carousel;
using osu.Game.Screens.Select.Filter; using osu.Game.Screens.Select.Filter;
@ -72,19 +73,23 @@ namespace osu.Game.Tests.Visual.SongSelect
// required to get bindables attached // required to get bindables attached
Add(music); Add(music);
Beatmap.SetDefault();
Dependencies.Cache(config = new OsuConfigManager(LocalStorage)); Dependencies.Cache(config = new OsuConfigManager(LocalStorage));
} }
private OsuConfigManager config; private OsuConfigManager config;
[SetUp] public override void SetUpSteps()
public virtual void SetUp() => Schedule(() => {
base.SetUpSteps();
AddStep("delete all beatmaps", () =>
{ {
Ruleset.Value = new OsuRuleset().RulesetInfo; Ruleset.Value = new OsuRuleset().RulesetInfo;
manager?.Delete(manager.GetAllUsableBeatmapSets()); manager?.Delete(manager.GetAllUsableBeatmapSets());
Beatmap.SetDefault();
}); });
}
[Test] [Test]
public void TestSingleFilterOnEnter() public void TestSingleFilterOnEnter()
@ -118,10 +123,8 @@ namespace osu.Game.Tests.Visual.SongSelect
InputManager.ReleaseKey(Key.Enter); InputManager.ReleaseKey(Key.Enter);
}); });
AddUntilStep("wait for not current", () => !songSelect.IsCurrentScreen());
AddAssert("ensure selection changed", () => selected != Beatmap.Value); AddAssert("ensure selection changed", () => selected != Beatmap.Value);
AddUntilStep("wait for return to song select", () => songSelect.IsCurrentScreen());
AddUntilStep("bindable lease returned", () => !Beatmap.Disabled);
} }
[Test] [Test]
@ -145,10 +148,8 @@ namespace osu.Game.Tests.Visual.SongSelect
InputManager.ReleaseKey(Key.Down); InputManager.ReleaseKey(Key.Down);
}); });
AddUntilStep("wait for not current", () => !songSelect.IsCurrentScreen());
AddAssert("ensure selection didn't change", () => selected == Beatmap.Value); AddAssert("ensure selection didn't change", () => selected == Beatmap.Value);
AddUntilStep("wait for return to song select", () => songSelect.IsCurrentScreen());
AddUntilStep("bindable lease returned", () => !Beatmap.Disabled);
} }
[Test] [Test]
@ -176,10 +177,8 @@ namespace osu.Game.Tests.Visual.SongSelect
InputManager.ReleaseKey(Key.Enter); InputManager.ReleaseKey(Key.Enter);
}); });
AddUntilStep("wait for not current", () => !songSelect.IsCurrentScreen());
AddAssert("ensure selection changed", () => selected != Beatmap.Value); AddAssert("ensure selection changed", () => selected != Beatmap.Value);
AddUntilStep("wait for return to song select", () => songSelect.IsCurrentScreen());
AddUntilStep("bindable lease returned", () => !Beatmap.Disabled);
} }
[Test] [Test]
@ -208,10 +207,8 @@ namespace osu.Game.Tests.Visual.SongSelect
InputManager.ReleaseButton(MouseButton.Left); InputManager.ReleaseButton(MouseButton.Left);
}); });
AddUntilStep("wait for not current", () => !songSelect.IsCurrentScreen());
AddAssert("ensure selection didn't change", () => selected == Beatmap.Value); AddAssert("ensure selection didn't change", () => selected == Beatmap.Value);
AddUntilStep("wait for return to song select", () => songSelect.IsCurrentScreen());
AddUntilStep("bindable lease returned", () => !Beatmap.Disabled);
} }
[Test] [Test]
@ -429,6 +426,31 @@ namespace osu.Game.Tests.Visual.SongSelect
AddAssert("start not requested", () => !startRequested); AddAssert("start not requested", () => !startRequested);
} }
[Test]
public void TestAutoplayViaCtrlEnter()
{
addRulesetImportStep(0);
createSongSelect();
AddStep("press ctrl+enter", () =>
{
InputManager.PressKey(Key.ControlLeft);
InputManager.PressKey(Key.Enter);
InputManager.ReleaseKey(Key.ControlLeft);
InputManager.ReleaseKey(Key.Enter);
});
AddUntilStep("wait for player", () => Stack.CurrentScreen is PlayerLoader);
AddAssert("autoplay enabled", () => songSelect.Mods.Value.FirstOrDefault() is ModAutoplay);
AddUntilStep("wait for return to ss", () => songSelect.IsCurrentScreen());
AddAssert("mod disabled", () => songSelect.Mods.Value.Count == 0);
}
[Test] [Test]
public void TestHideSetSelectsCorrectBeatmap() public void TestHideSetSelectsCorrectBeatmap()
{ {

View File

@ -16,7 +16,8 @@ namespace osu.Game.Tests.Visual.UserInterface
{ {
public override IReadOnlyList<Type> RequiredTypes => new[] public override IReadOnlyList<Type> RequiredTypes => new[]
{ {
typeof(FooterButtonMods) typeof(FooterButtonMods),
typeof(FooterButton)
}; };
private readonly TestFooterButtonMods footerButtonMods; private readonly TestFooterButtonMods footerButtonMods;

View File

@ -60,9 +60,9 @@ namespace osu.Game.Tests.Visual.UserInterface
{ {
modSelect = new TestModSelectOverlay modSelect = new TestModSelectOverlay
{ {
RelativeSizeAxes = Axes.X,
Origin = Anchor.BottomCentre, Origin = Anchor.BottomCentre,
Anchor = Anchor.BottomCentre, Anchor = Anchor.BottomCentre,
SelectedMods = { BindTarget = SelectedMods }
}, },
modDisplay = new ModDisplay modDisplay = new ModDisplay

View File

@ -27,6 +27,13 @@ namespace osu.Game.Tests.Visual.UserInterface
private readonly Mod testCustomisableAutoOpenMod = new TestModCustomisable2(); private readonly Mod testCustomisableAutoOpenMod = new TestModCustomisable2();
[SetUp]
public void SetUp() => Schedule(() =>
{
SelectedMods.Value = Array.Empty<Mod>();
Ruleset.Value = new TestRulesetInfo();
});
[Test] [Test]
public void TestButtonShowsOnCustomisableMod() public void TestButtonShowsOnCustomisableMod()
{ {
@ -70,13 +77,11 @@ namespace osu.Game.Tests.Visual.UserInterface
{ {
AddStep("create mod select", () => AddStep("create mod select", () =>
{ {
Ruleset.Value = new TestRulesetInfo();
Child = modSelect = new TestModSelectOverlay Child = modSelect = new TestModSelectOverlay
{ {
RelativeSizeAxes = Axes.X,
Origin = Anchor.BottomCentre, Origin = Anchor.BottomCentre,
Anchor = Anchor.BottomCentre, Anchor = Anchor.BottomCentre,
SelectedMods = { BindTarget = SelectedMods }
}; };
}); });
} }

View File

@ -53,7 +53,7 @@ namespace osu.Game.Tests.Visual.UserInterface
addHeader("Orange OverlayHeader (no background)", new TestNoBackgroundHeader(), OverlayColourScheme.Orange); addHeader("Orange OverlayHeader (no background)", new TestNoBackgroundHeader(), OverlayColourScheme.Orange);
addHeader("Blue OverlayHeader", new TestNoControlHeader(), OverlayColourScheme.Blue); addHeader("Blue OverlayHeader", new TestNoControlHeader(), OverlayColourScheme.Blue);
addHeader("Green TabControlOverlayHeader (string)", new TestStringTabControlHeader(), OverlayColourScheme.Green); addHeader("Green TabControlOverlayHeader (string) with ruleset selector", new TestStringTabControlHeader(), OverlayColourScheme.Green);
addHeader("Pink TabControlOverlayHeader (enum)", new TestEnumTabControlHeader(), OverlayColourScheme.Pink); addHeader("Pink TabControlOverlayHeader (enum)", new TestEnumTabControlHeader(), OverlayColourScheme.Pink);
addHeader("Red BreadcrumbControlOverlayHeader (no background)", new TestBreadcrumbControlHeader(), OverlayColourScheme.Red); addHeader("Red BreadcrumbControlOverlayHeader (no background)", new TestBreadcrumbControlHeader(), OverlayColourScheme.Red);
} }
@ -116,6 +116,8 @@ namespace osu.Game.Tests.Visual.UserInterface
protected override ScreenTitle CreateTitle() => new TestTitle(); protected override ScreenTitle CreateTitle() => new TestTitle();
protected override Drawable CreateTitleContent() => new OverlayRulesetSelector();
public TestStringTabControlHeader() public TestStringTabControlHeader()
{ {
TabControl.AddItem("tab1"); TabControl.AddItem("tab1");

View File

@ -0,0 +1,82 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Graphics;
using System;
using System.Collections.Generic;
using osu.Game.Rulesets.Catch;
using osu.Game.Rulesets.Mania;
using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Taiko;
using osu.Framework.Bindables;
using osu.Game.Overlays;
using osu.Game.Rulesets;
using NUnit.Framework;
using osu.Framework.Graphics.Containers;
using osuTK;
using osu.Framework.Allocation;
namespace osu.Game.Tests.Visual.UserInterface
{
public class TestSceneOverlayRulesetSelector : OsuTestScene
{
public override IReadOnlyList<Type> RequiredTypes => new[]
{
typeof(OverlayRulesetSelector),
typeof(OverlayRulesetTabItem),
};
private readonly OverlayRulesetSelector selector;
private readonly Bindable<RulesetInfo> ruleset = new Bindable<RulesetInfo>();
public TestSceneOverlayRulesetSelector()
{
Add(new FillFlowContainer
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Vertical,
Spacing = new Vector2(0, 5),
Children = new[]
{
new ColourProvidedContainer(OverlayColourScheme.Green, selector = new OverlayRulesetSelector { Current = ruleset }),
new ColourProvidedContainer(OverlayColourScheme.Blue, new OverlayRulesetSelector { Current = ruleset }),
new ColourProvidedContainer(OverlayColourScheme.Orange, new OverlayRulesetSelector { Current = ruleset }),
new ColourProvidedContainer(OverlayColourScheme.Pink, new OverlayRulesetSelector { Current = ruleset }),
new ColourProvidedContainer(OverlayColourScheme.Purple, new OverlayRulesetSelector { Current = ruleset }),
new ColourProvidedContainer(OverlayColourScheme.Red, new OverlayRulesetSelector { Current = ruleset }),
}
});
}
private class ColourProvidedContainer : Container
{
[Cached]
private readonly OverlayColourProvider colourProvider;
public ColourProvidedContainer(OverlayColourScheme colourScheme, OverlayRulesetSelector rulesetSelector)
{
colourProvider = new OverlayColourProvider(colourScheme);
AutoSizeAxes = Axes.Both;
Add(rulesetSelector);
}
}
[Test]
public void TestSelection()
{
AddStep("Select osu!", () => ruleset.Value = new OsuRuleset().RulesetInfo);
AddAssert("Check osu! selected", () => selector.Current.Value.Equals(new OsuRuleset().RulesetInfo));
AddStep("Select mania", () => ruleset.Value = new ManiaRuleset().RulesetInfo);
AddAssert("Check mania selected", () => selector.Current.Value.Equals(new ManiaRuleset().RulesetInfo));
AddStep("Select taiko", () => ruleset.Value = new TaikoRuleset().RulesetInfo);
AddAssert("Check taiko selected", () => selector.Current.Value.Equals(new TaikoRuleset().RulesetInfo));
AddStep("Select catch", () => ruleset.Value = new CatchRuleset().RulesetInfo);
AddAssert("Check catch selected", () => selector.Current.Value.Equals(new CatchRuleset().RulesetInfo));
}
}
}

View File

@ -25,7 +25,9 @@ namespace osu.Game.Tests.Visual.UserInterface
OsuSpriteText titleText; OsuSpriteText titleText;
IScreen startScreen = new TestScreenOne(); IScreen startScreen = new TestScreenOne();
screenStack = new OsuScreenStack(startScreen) { RelativeSizeAxes = Axes.Both };
screenStack = new OsuScreenStack { RelativeSizeAxes = Axes.Both };
screenStack.Push(startScreen);
Children = new Drawable[] Children = new Drawable[]
{ {

View File

@ -29,9 +29,9 @@ namespace osu.Game.Graphics
} }
} }
public DrawableDate(DateTimeOffset date) public DrawableDate(DateTimeOffset date, float textSize = OsuFont.DEFAULT_FONT_SIZE, bool italic = true)
{ {
Font = OsuFont.GetFont(weight: FontWeight.Regular, italics: true); Font = OsuFont.GetFont(weight: FontWeight.Regular, size: textSize, italics: italic);
Date = date; Date = date;
} }

View File

@ -36,6 +36,11 @@ namespace osu.Game.Graphics.UserInterface
public virtual string TooltipText { get; private set; } public virtual string TooltipText { get; private set; }
/// <summary>
/// Whether to format the tooltip as a percentage or the actual value.
/// </summary>
public bool DisplayAsPercentage { get; set; }
private Color4 accentColour; private Color4 accentColour;
public Color4 AccentColour public Color4 AccentColour
@ -169,11 +174,11 @@ namespace osu.Game.Graphics.UserInterface
else else
{ {
double floatValue = value.ToDouble(NumberFormatInfo.InvariantInfo); double floatValue = value.ToDouble(NumberFormatInfo.InvariantInfo);
double floatMinValue = CurrentNumber.MinValue.ToDouble(NumberFormatInfo.InvariantInfo);
double floatMaxValue = CurrentNumber.MaxValue.ToDouble(NumberFormatInfo.InvariantInfo);
if (floatMaxValue == 1 && floatMinValue >= -1) if (DisplayAsPercentage)
TooltipText = floatValue.ToString("P0"); {
TooltipText = floatValue.ToString("0%");
}
else else
{ {
var decimalPrecision = normalise(CurrentNumber.Precision.ToDecimal(NumberFormatInfo.InvariantInfo), max_decimal_digits); var decimalPrecision = normalise(CurrentNumber.Precision.ToDecimal(NumberFormatInfo.InvariantInfo), max_decimal_digits);

View File

@ -17,6 +17,7 @@ namespace osu.Game.Graphics.UserInterface
stack.ScreenPushed += onPushed; stack.ScreenPushed += onPushed;
stack.ScreenExited += onExited; stack.ScreenExited += onExited;
if (stack.CurrentScreen != null)
onPushed(null, stack.CurrentScreen); onPushed(null, stack.CurrentScreen);
Current.ValueChanged += current => current.NewValue.MakeCurrent(); Current.ValueChanged += current => current.NewValue.MakeCurrent();

View File

@ -40,13 +40,14 @@ namespace osu.Game.Graphics.UserInterface
public ShowMoreButton() public ShowMoreButton()
{ {
AutoSizeAxes = Axes.Both; Height = 30;
Width = 140;
} }
protected override Drawable CreateContent() => new CircularContainer protected override Drawable CreateContent() => new CircularContainer
{ {
Masking = true, Masking = true,
Size = new Vector2(140, 30), RelativeSizeAxes = Axes.Both,
Children = new Drawable[] Children = new Drawable[]
{ {
background = new Box background = new Box

View File

@ -0,0 +1,20 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System.Collections.Generic;
using Newtonsoft.Json;
using osu.Game.Online.API.Requests.Responses;
namespace osu.Game.Online.API.Requests
{
public class GetSpotlightsRequest : APIRequest<SpotlightsCollection>
{
protected override string Target => "spotlights";
}
public class SpotlightsCollection
{
[JsonProperty("spotlights")]
public List<APISpotlight> Spotlights;
}
}

View File

@ -0,0 +1,31 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using Newtonsoft.Json;
namespace osu.Game.Online.API.Requests.Responses
{
public class APISpotlight
{
[JsonProperty("id")]
public int Id;
[JsonProperty("name")]
public string Name;
[JsonProperty("type")]
public string Type;
[JsonProperty("mode_specific")]
public bool ModeSpecific;
[JsonProperty(@"start_date")]
public DateTimeOffset StartDate;
[JsonProperty(@"end_date")]
public DateTimeOffset EndDate;
public override string ToString() => Name;
}
}

View File

@ -6,8 +6,6 @@ using osu.Game.Users;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Net;
using System.Text.RegularExpressions;
namespace osu.Game.Online.API.Requests.Responses namespace osu.Game.Online.API.Requests.Responses
{ {
@ -70,12 +68,10 @@ namespace osu.Game.Online.API.Requests.Responses
public bool IsDeleted => DeletedAt.HasValue; public bool IsDeleted => DeletedAt.HasValue;
public bool HasMessage => !string.IsNullOrEmpty(MessageHtml); public bool HasMessage => !string.IsNullOrEmpty(Message);
public bool IsVoted { get; set; } public bool IsVoted { get; set; }
public string GetMessage => HasMessage ? WebUtility.HtmlDecode(Regex.Replace(MessageHtml, @"<(.|\n)*?>", string.Empty)) : string.Empty;
public int DeletedChildrenCount => ChildComments.Count(c => c.IsDeleted); public int DeletedChildrenCount => ChildComments.Count(c => c.IsDeleted);
} }
} }

View File

@ -47,17 +47,18 @@ namespace osu.Game.Online.API.Requests.Responses
[JsonProperty(@"included_comments")] [JsonProperty(@"included_comments")]
public List<Comment> IncludedComments { get; set; } public List<Comment> IncludedComments { get; set; }
private List<long> userVotes;
[JsonProperty(@"user_votes")] [JsonProperty(@"user_votes")]
private List<long> userVotes public List<long> UserVotes
{ {
set => value.ForEach(v => get => userVotes;
set
{ {
Comments.ForEach(c => userVotes = value;
{
if (v == c.Id) Comments.ForEach(c => c.IsVoted = value.Contains(c.Id));
c.IsVoted = true; }
});
});
} }
private List<User> users; private List<User> users;

View File

@ -277,7 +277,7 @@ namespace osu.Game.Online.Leaderboards
protected virtual IEnumerable<LeaderboardScoreStatistic> GetStatistics(ScoreInfo model) => new[] protected virtual IEnumerable<LeaderboardScoreStatistic> GetStatistics(ScoreInfo model) => new[]
{ {
new LeaderboardScoreStatistic(FontAwesome.Solid.Link, "Max Combo", model.MaxCombo.ToString()), new LeaderboardScoreStatistic(FontAwesome.Solid.Link, "Max Combo", model.MaxCombo.ToString()),
new LeaderboardScoreStatistic(FontAwesome.Solid.Crosshairs, "Accuracy", string.Format(model.Accuracy % 1 == 0 ? @"{0:P0}" : @"{0:P2}", model.Accuracy)) new LeaderboardScoreStatistic(FontAwesome.Solid.Crosshairs, "Accuracy", model.DisplayAccuracy)
}; };
protected override bool OnHover(HoverEvent e) protected override bool OnHover(HoverEvent e)

View File

@ -327,10 +327,10 @@ namespace osu.Game
return; return;
} }
performFromMainMenu(() => PerformFromScreen(screen =>
{ {
// we might already be at song select, so a check is required before performing the load to solo. // we might already be at song select, so a check is required before performing the load to solo.
if (menuScreen.IsCurrentScreen()) if (screen is MainMenu)
menuScreen.LoadToSolo(); menuScreen.LoadToSolo();
// we might even already be at the song // we might even already be at the song
@ -344,7 +344,7 @@ namespace osu.Game
Ruleset.Value = first.Ruleset; Ruleset.Value = first.Ruleset;
Beatmap.Value = BeatmapManager.GetWorkingBeatmap(first); Beatmap.Value = BeatmapManager.GetWorkingBeatmap(first);
}, $"load {beatmap}", bypassScreenAllowChecks: true, targetScreen: typeof(PlaySongSelect)); }, validScreens: new[] { typeof(PlaySongSelect) });
} }
/// <summary> /// <summary>
@ -381,12 +381,12 @@ namespace osu.Game
return; return;
} }
performFromMainMenu(() => PerformFromScreen(screen =>
{ {
Beatmap.Value = BeatmapManager.GetWorkingBeatmap(databasedBeatmap); Beatmap.Value = BeatmapManager.GetWorkingBeatmap(databasedBeatmap);
menuScreen.Push(new ReplayPlayerLoader(databasedScore)); screen.Push(new ReplayPlayerLoader(databasedScore));
}, $"watch {databasedScoreInfo}", bypassScreenAllowChecks: true); }, validScreens: new[] { typeof(PlaySongSelect) });
} }
protected virtual Loader CreateLoader() => new Loader(); protected virtual Loader CreateLoader() => new Loader();
@ -441,53 +441,42 @@ namespace osu.Game
private ScheduledDelegate performFromMainMenuTask; private ScheduledDelegate performFromMainMenuTask;
/// <summary> /// <summary>
/// Perform an action only after returning to the main menu. /// Perform an action only after returning to a specific screen as indicated by <paramref name="validScreens"/>.
/// Eagerly tries to exit the current screen until it succeeds. /// Eagerly tries to exit the current screen until it succeeds.
/// </summary> /// </summary>
/// <param name="action">The action to perform once we are in the correct state.</param> /// <param name="action">The action to perform once we are in the correct state.</param>
/// <param name="taskName">The task name to display in a notification (if we can't immediately reach the main menu state).</param> /// <param name="validScreens">An optional collection of valid screen types. If any of these screens are already current we can perform the action immediately, else the first valid parent will be made current before performing the action. <see cref="MainMenu"/> is used if not specified.</param>
/// <param name="targetScreen">An optional target screen type. If this screen is already current we can immediately perform the action without returning to the menu.</param> protected void PerformFromScreen(Action<IScreen> action, IEnumerable<Type> validScreens = null)
/// <param name="bypassScreenAllowChecks">Whether checking <see cref="IOsuScreen.AllowExternalScreenChange"/> should be bypassed.</param>
private void performFromMainMenu(Action action, string taskName, Type targetScreen = null, bool bypassScreenAllowChecks = false)
{ {
performFromMainMenuTask?.Cancel(); performFromMainMenuTask?.Cancel();
// if the current screen does not allow screen changing, give the user an option to try again later. validScreens ??= Enumerable.Empty<Type>();
if (!bypassScreenAllowChecks && (ScreenStack.CurrentScreen as IOsuScreen)?.AllowExternalScreenChange == false) validScreens = validScreens.Append(typeof(MainMenu));
{
notifications.Post(new SimpleNotification
{
Text = $"Click here to {taskName}",
Activated = () =>
{
performFromMainMenu(action, taskName, targetScreen, true);
return true;
}
});
return;
}
CloseAllOverlays(false); CloseAllOverlays(false);
// we may already be at the target screen type. // we may already be at the target screen type.
if (targetScreen != null && ScreenStack.CurrentScreen?.GetType() == targetScreen) if (validScreens.Contains(ScreenStack.CurrentScreen?.GetType()) && !Beatmap.Disabled)
{ {
action(); action(ScreenStack.CurrentScreen);
return; return;
} }
// all conditions have been met to continue with the action. // find closest valid target
if (menuScreen?.IsCurrentScreen() == true && !Beatmap.Disabled) IScreen screen = ScreenStack.CurrentScreen;
while (screen != null)
{ {
action(); if (validScreens.Contains(screen.GetType()))
return; {
screen.MakeCurrent();
break;
} }
// menuScreen may not be initialised yet (null check required). screen = screen.GetParentScreen();
menuScreen?.MakeCurrent(); }
performFromMainMenuTask = Schedule(() => performFromMainMenu(action, taskName)); performFromMainMenuTask = Schedule(() => PerformFromScreen(action, validScreens));
} }
/// <summary> /// <summary>

View File

@ -2,17 +2,14 @@
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.UserInterface; using osu.Framework.Graphics.UserInterface;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Rulesets; using osu.Game.Rulesets;
using osuTK;
using System.Linq; using System.Linq;
namespace osu.Game.Overlays.BeatmapSet namespace osu.Game.Overlays.BeatmapSet
{ {
public class BeatmapRulesetSelector : RulesetSelector public class BeatmapRulesetSelector : OverlayRulesetSelector
{ {
private readonly Bindable<BeatmapSetInfo> beatmapSet = new Bindable<BeatmapSetInfo>(); private readonly Bindable<BeatmapSetInfo> beatmapSet = new Bindable<BeatmapSetInfo>();
@ -28,21 +25,9 @@ namespace osu.Game.Overlays.BeatmapSet
} }
} }
public BeatmapRulesetSelector()
{
AutoSizeAxes = Axes.Both;
}
protected override TabItem<RulesetInfo> CreateTabItem(RulesetInfo value) => new BeatmapRulesetTabItem(value) protected override TabItem<RulesetInfo> CreateTabItem(RulesetInfo value) => new BeatmapRulesetTabItem(value)
{ {
BeatmapSet = { BindTarget = beatmapSet } BeatmapSet = { BindTarget = beatmapSet }
}; };
protected override TabFillFlowContainer CreateTabFlow() => new TabFillFlowContainer
{
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Horizontal,
Spacing = new Vector2(10, 0),
};
} }
} }

View File

@ -3,63 +3,40 @@
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.UserInterface;
using osu.Framework.Input.Events;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Graphics.Sprites; using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterface;
using osu.Game.Rulesets; using osu.Game.Rulesets;
using osuTK;
using osuTK.Graphics;
using System.Linq; using System.Linq;
namespace osu.Game.Overlays.BeatmapSet namespace osu.Game.Overlays.BeatmapSet
{ {
public class BeatmapRulesetTabItem : TabItem<RulesetInfo> public class BeatmapRulesetTabItem : OverlayRulesetTabItem
{ {
private readonly OsuSpriteText name, count;
private readonly Box bar;
public readonly Bindable<BeatmapSetInfo> BeatmapSet = new Bindable<BeatmapSetInfo>(); public readonly Bindable<BeatmapSetInfo> BeatmapSet = new Bindable<BeatmapSetInfo>();
public override bool PropagatePositionalInputSubTree => Enabled.Value && !Active.Value && base.PropagatePositionalInputSubTree; [Resolved]
private OverlayColourProvider colourProvider { get; set; }
private OsuSpriteText count;
private Container countContainer;
public BeatmapRulesetTabItem(RulesetInfo value) public BeatmapRulesetTabItem(RulesetInfo value)
: base(value) : base(value)
{ {
AutoSizeAxes = Axes.Both; }
FillFlowContainer nameContainer; [BackgroundDependencyLoader]
private void load()
Children = new Drawable[]
{ {
nameContainer = new FillFlowContainer Add(countContainer = new Container
{ {
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
AutoSizeAxes = Axes.Both, AutoSizeAxes = Axes.Both,
Direction = FillDirection.Horizontal,
Margin = new MarginPadding { Bottom = 7.5f },
Spacing = new Vector2(2.5f),
Children = new Drawable[]
{
name = new OsuSpriteText
{
Anchor = Anchor.Centre, Anchor = Anchor.Centre,
Origin = Anchor.Centre, Origin = Anchor.Centre,
Text = value.Name,
Font = OsuFont.Default.With(size: 18),
},
new Container
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
AutoSizeAxes = Axes.Both,
Masking = true, Masking = true,
CornerRadius = 4f, CornerRadius = 4f,
Children = new Drawable[] Children = new Drawable[]
@ -67,79 +44,33 @@ namespace osu.Game.Overlays.BeatmapSet
new Box new Box
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
Colour = Color4.Black.Opacity(0.5f), Colour = colourProvider.Background6
}, },
count = new OsuSpriteText count = new OsuSpriteText
{ {
Alpha = 0,
Anchor = Anchor.Centre, Anchor = Anchor.Centre,
Origin = Anchor.Centre, Origin = Anchor.Centre,
Margin = new MarginPadding { Horizontal = 5f }, Margin = new MarginPadding { Horizontal = 5f },
Font = OsuFont.Default.With(weight: FontWeight.SemiBold), Font = OsuFont.Default.With(weight: FontWeight.SemiBold),
Colour = colourProvider.Foreground1,
} }
} }
});
} }
}
}, protected override void LoadComplete()
bar = new Box
{ {
Anchor = Anchor.BottomCentre, base.LoadComplete();
Origin = Anchor.BottomCentre,
RelativeSizeAxes = Axes.X,
},
new HoverClickSounds(),
};
BeatmapSet.BindValueChanged(setInfo => BeatmapSet.BindValueChanged(setInfo =>
{ {
var beatmapsCount = setInfo.NewValue?.Beatmaps.Count(b => b.Ruleset.Equals(Value)) ?? 0; var beatmapsCount = setInfo.NewValue?.Beatmaps.Count(b => b.Ruleset.Equals(Value)) ?? 0;
count.Text = beatmapsCount.ToString(); count.Text = beatmapsCount.ToString();
count.Alpha = beatmapsCount > 0 ? 1f : 0f; countContainer.FadeTo(beatmapsCount > 0 ? 1 : 0);
Enabled.Value = beatmapsCount > 0; Enabled.Value = beatmapsCount > 0;
}, true); }, true);
Enabled.BindValueChanged(v => nameContainer.Alpha = v.NewValue ? 1f : 0.5f, true);
} }
[Resolved]
private OsuColour colour { get; set; }
protected override void LoadComplete()
{
base.LoadComplete();
count.Colour = colour.Gray9;
bar.Colour = colour.Blue;
updateState();
}
private void updateState()
{
var isHoveredOrActive = IsHovered || Active.Value;
bar.ResizeHeightTo(isHoveredOrActive ? 4 : 0, 200, Easing.OutQuint);
name.Colour = isHoveredOrActive ? colour.GrayE : colour.GrayC;
name.Font = name.Font.With(weight: Active.Value ? FontWeight.Bold : FontWeight.Regular);
}
#region Hovering and activation logic
protected override void OnActivated() => updateState();
protected override void OnDeactivated() => updateState();
protected override bool OnHover(HoverEvent e)
{
updateState();
return false;
}
protected override void OnHoverLost(HoverLostEvent e) => updateState();
#endregion
} }
} }

View File

@ -0,0 +1,35 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Game.Graphics.UserInterface;
using osu.Game.Rulesets;
namespace osu.Game.Overlays.BeatmapSet
{
public class BeatmapSetHeader : OverlayHeader
{
public readonly Bindable<RulesetInfo> Ruleset = new Bindable<RulesetInfo>();
public BeatmapRulesetSelector RulesetSelector { get; private set; }
protected override ScreenTitle CreateTitle() => new BeatmapHeaderTitle();
protected override Drawable CreateTitleContent() => RulesetSelector = new BeatmapRulesetSelector
{
Current = Ruleset
};
private class BeatmapHeaderTitle : ScreenTitle
{
public BeatmapHeaderTitle()
{
Title = @"beatmap";
Section = @"info";
}
protected override Drawable CreateIcon() => new ScreenTitleTextureIcon(@"Icons/changelog");
}
}
}

View File

@ -26,11 +26,9 @@ namespace osu.Game.Overlays.BeatmapSet
public class Header : BeatmapDownloadTrackingComposite public class Header : BeatmapDownloadTrackingComposite
{ {
private const float transition_duration = 200; private const float transition_duration = 200;
private const float tabs_height = 50;
private const float buttons_height = 45; private const float buttons_height = 45;
private const float buttons_spacing = 5; private const float buttons_spacing = 5;
private readonly Box tabsBg;
private readonly UpdateableBeatmapSetCover cover; private readonly UpdateableBeatmapSetCover cover;
private readonly OsuSpriteText title, artist; private readonly OsuSpriteText title, artist;
private readonly AuthorInfo author; private readonly AuthorInfo author;
@ -41,14 +39,13 @@ namespace osu.Game.Overlays.BeatmapSet
public bool DownloadButtonsVisible => downloadButtonsContainer.Any(); public bool DownloadButtonsVisible => downloadButtonsContainer.Any();
public readonly BeatmapRulesetSelector RulesetSelector; public BeatmapRulesetSelector RulesetSelector => beatmapSetHeader.RulesetSelector;
public readonly BeatmapPicker Picker; public readonly BeatmapPicker Picker;
private readonly FavouriteButton favouriteButton; private readonly FavouriteButton favouriteButton;
private readonly FillFlowContainer fadeContent; private readonly FillFlowContainer fadeContent;
private readonly LoadingAnimation loading; private readonly LoadingAnimation loading;
private readonly BeatmapSetHeader beatmapSetHeader;
[Cached(typeof(IBindable<RulesetInfo>))] [Cached(typeof(IBindable<RulesetInfo>))]
private readonly Bindable<RulesetInfo> ruleset = new Bindable<RulesetInfo>(); private readonly Bindable<RulesetInfo> ruleset = new Bindable<RulesetInfo>();
@ -69,31 +66,21 @@ namespace osu.Game.Overlays.BeatmapSet
Offset = new Vector2(0f, 1f), Offset = new Vector2(0f, 1f),
}; };
InternalChildren = new Drawable[] InternalChild = new FillFlowContainer
{
new Container
{ {
RelativeSizeAxes = Axes.X, RelativeSizeAxes = Axes.X,
Height = tabs_height, AutoSizeAxes = Axes.Y,
Direction = FillDirection.Vertical,
Children = new Drawable[] Children = new Drawable[]
{ {
tabsBg = new Box beatmapSetHeader = new BeatmapSetHeader
{ {
RelativeSizeAxes = Axes.Both, Ruleset = { BindTarget = ruleset },
},
RulesetSelector = new BeatmapRulesetSelector
{
Current = ruleset,
Anchor = Anchor.BottomCentre,
Origin = Anchor.BottomCentre,
}
},
}, },
new Container new Container
{ {
RelativeSizeAxes = Axes.X, RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y, AutoSizeAxes = Axes.Y,
Padding = new MarginPadding { Top = tabs_height },
Children = new Drawable[] Children = new Drawable[]
{ {
new Container new Container
@ -217,6 +204,7 @@ namespace osu.Game.Overlays.BeatmapSet
}, },
}, },
}, },
}
}; };
Picker.Beatmap.ValueChanged += b => Picker.Beatmap.ValueChanged += b =>
@ -229,8 +217,6 @@ namespace osu.Game.Overlays.BeatmapSet
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(OsuColour colours) private void load(OsuColour colours)
{ {
tabsBg.Colour = colours.Gray3;
State.BindValueChanged(_ => updateDownloadButtons()); State.BindValueChanged(_ => updateDownloadButtons());
BeatmapSet.BindValueChanged(setInfo => BeatmapSet.BindValueChanged(setInfo =>

View File

@ -116,7 +116,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores
new OsuSpriteText new OsuSpriteText
{ {
Margin = new MarginPadding { Right = horizontal_inset }, Margin = new MarginPadding { Right = horizontal_inset },
Text = $@"{score.Accuracy:P2}", Text = score.DisplayAccuracy,
Font = OsuFont.GetFont(size: text_size), Font = OsuFont.GetFont(size: text_size),
Colour = score.Accuracy == 1 ? highAccuracyColour : Color4.White Colour = score.Accuracy == 1 ? highAccuracyColour : Color4.White
}, },

View File

@ -92,7 +92,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores
set set
{ {
totalScoreColumn.Text = $@"{value.TotalScore:N0}"; totalScoreColumn.Text = $@"{value.TotalScore:N0}";
accuracyColumn.Text = $@"{value.Accuracy:P2}"; accuracyColumn.Text = value.DisplayAccuracy;
maxComboColumn.Text = $@"{value.MaxCombo:N0}x"; maxComboColumn.Text = $@"{value.MaxCombo:N0}x";
ppColumn.Text = $@"{value.PP:N0}"; ppColumn.Text = $@"{value.PP:N0}";

View File

@ -42,7 +42,7 @@ namespace osu.Game.Overlays.BeatmapSet
int playCount = beatmap?.OnlineInfo?.PlayCount ?? 0; int playCount = beatmap?.OnlineInfo?.PlayCount ?? 0;
var rate = playCount != 0 ? (float)passCount / playCount : 0; var rate = playCount != 0 ? (float)passCount / playCount : 0;
successPercent.Text = rate.ToString("P0"); successPercent.Text = rate.ToString("0%");
successRate.Length = rate; successRate.Length = rate;
percentContainer.ResizeWidthTo(successRate.Length, 250, Easing.InOutCubic); percentContainer.ResizeWidthTo(successRate.Length, 250, Easing.InOutCubic);

View File

@ -14,7 +14,7 @@ namespace osu.Game.Overlays.Changelog
{ {
public class ChangelogHeader : BreadcrumbControlOverlayHeader public class ChangelogHeader : BreadcrumbControlOverlayHeader
{ {
public readonly Bindable<APIChangelogBuild> Current = new Bindable<APIChangelogBuild>(); public readonly Bindable<APIChangelogBuild> Build = new Bindable<APIChangelogBuild>();
public Action ListingSelected; public Action ListingSelected;
@ -25,18 +25,18 @@ namespace osu.Game.Overlays.Changelog
public ChangelogHeader() public ChangelogHeader()
{ {
TabControl.AddItem(listing_string); TabControl.AddItem(listing_string);
TabControl.Current.ValueChanged += e => Current.ValueChanged += e =>
{ {
if (e.NewValue == listing_string) if (e.NewValue == listing_string)
ListingSelected?.Invoke(); ListingSelected?.Invoke();
}; };
Current.ValueChanged += showBuild; Build.ValueChanged += showBuild;
Streams.Current.ValueChanged += e => Streams.Current.ValueChanged += e =>
{ {
if (e.NewValue?.LatestBuild != null && !e.NewValue.Equals(Current.Value?.UpdateStream)) if (e.NewValue?.LatestBuild != null && !e.NewValue.Equals(Build.Value?.UpdateStream))
Current.Value = e.NewValue.LatestBuild; Build.Value = e.NewValue.LatestBuild;
}; };
} }
@ -50,7 +50,7 @@ namespace osu.Game.Overlays.Changelog
if (e.NewValue != null) if (e.NewValue != null)
{ {
TabControl.AddItem(e.NewValue.ToString()); TabControl.AddItem(e.NewValue.ToString());
TabControl.Current.Value = e.NewValue.ToString(); Current.Value = e.NewValue.ToString();
updateCurrentStream(); updateCurrentStream();
@ -58,7 +58,7 @@ namespace osu.Game.Overlays.Changelog
} }
else else
{ {
TabControl.Current.Value = listing_string; Current.Value = listing_string;
Streams.Current.Value = null; Streams.Current.Value = null;
title.Version = null; title.Version = null;
} }
@ -86,10 +86,10 @@ namespace osu.Game.Overlays.Changelog
private void updateCurrentStream() private void updateCurrentStream()
{ {
if (Current.Value == null) if (Build.Value == null)
return; return;
Streams.Current.Value = Streams.Items.FirstOrDefault(s => s.Name == Current.Value.UpdateStream.Name); Streams.Current.Value = Streams.Items.FirstOrDefault(s => s.Name == Build.Value.UpdateStream.Name);
} }
private class ChangelogHeaderTitle : ScreenTitle private class ChangelogHeaderTitle : ScreenTitle

View File

@ -78,7 +78,7 @@ namespace osu.Game.Overlays
sampleBack = audio.Samples.Get(@"UI/generic-select-soft"); sampleBack = audio.Samples.Get(@"UI/generic-select-soft");
Header.Current.BindTo(Current); Header.Build.BindTo(Current);
Current.BindValueChanged(e => Current.BindValueChanged(e =>
{ {

View File

@ -25,7 +25,7 @@ namespace osu.Game.Overlays.Chat.Selection
private const float text_size = 15; private const float text_size = 15;
private const float transition_duration = 100; private const float transition_duration = 100;
private readonly Channel channel; public readonly Channel Channel;
private readonly Bindable<bool> joinedBind = new Bindable<bool>(); private readonly Bindable<bool> joinedBind = new Bindable<bool>();
private readonly OsuSpriteText name; private readonly OsuSpriteText name;
@ -36,7 +36,7 @@ namespace osu.Game.Overlays.Chat.Selection
private Color4 topicColour; private Color4 topicColour;
private Color4 hoverColour; private Color4 hoverColour;
public IEnumerable<string> FilterTerms => new[] { channel.Name, channel.Topic }; public IEnumerable<string> FilterTerms => new[] { Channel.Name, Channel.Topic ?? string.Empty };
public bool MatchingFilter public bool MatchingFilter
{ {
@ -50,7 +50,7 @@ namespace osu.Game.Overlays.Chat.Selection
public ChannelListItem(Channel channel) public ChannelListItem(Channel channel)
{ {
this.channel = channel; Channel = channel;
RelativeSizeAxes = Axes.X; RelativeSizeAxes = Axes.X;
AutoSizeAxes = Axes.Y; AutoSizeAxes = Axes.Y;
@ -148,7 +148,7 @@ namespace osu.Game.Overlays.Chat.Selection
hoverColour = colours.Yellow; hoverColour = colours.Yellow;
joinedBind.ValueChanged += joined => updateColour(joined.NewValue); joinedBind.ValueChanged += joined => updateColour(joined.NewValue);
joinedBind.BindTo(channel.Joined); joinedBind.BindTo(Channel.Joined);
joinedBind.TriggerChange(); joinedBind.TriggerChange();
FinishTransforms(true); FinishTransforms(true);
@ -156,7 +156,7 @@ namespace osu.Game.Overlays.Chat.Selection
protected override bool OnHover(HoverEvent e) protected override bool OnHover(HoverEvent e)
{ {
if (!channel.Joined.Value) if (!Channel.Joined.Value)
name.FadeColour(hoverColour, 50, Easing.OutQuint); name.FadeColour(hoverColour, 50, Easing.OutQuint);
return base.OnHover(e); return base.OnHover(e);
@ -164,7 +164,7 @@ namespace osu.Game.Overlays.Chat.Selection
protected override void OnHoverLost(HoverLostEvent e) protected override void OnHoverLost(HoverLostEvent e)
{ {
if (!channel.Joined.Value) if (!Channel.Joined.Value)
name.FadeColour(Color4.White, transition_duration); name.FadeColour(Color4.White, transition_duration);
} }

View File

@ -321,8 +321,10 @@ namespace osu.Game.Overlays
private void selectTab(int index) private void selectTab(int index)
{ {
var channel = ChannelTabControl.Items.Skip(index).FirstOrDefault(); var channel = ChannelTabControl.Items
if (channel != null && !(channel is ChannelSelectorTabItem.ChannelSelectorTabChannel)) .Where(tab => !(tab is ChannelSelectorTabItem.ChannelSelectorTabChannel))
.ElementAtOrDefault(index);
if (channel != null)
ChannelTabControl.Current.Value = channel; ChannelTabControl.Current.Value = channel;
} }

View File

@ -31,7 +31,7 @@ namespace osu.Game.Overlays.Comments
private int currentPage; private int currentPage;
private FillFlowContainer content; private FillFlowContainer content;
private DeletedChildrenPlaceholder deletedChildrenPlaceholder; private DeletedCommentsCounter deletedCommentsCounter;
private CommentsShowMoreButton moreButton; private CommentsShowMoreButton moreButton;
private TotalCommentsCounter commentCounter; private TotalCommentsCounter commentCounter;
@ -68,6 +68,7 @@ namespace osu.Game.Overlays.Comments
}, },
new Container new Container
{ {
Name = @"Footer",
RelativeSizeAxes = Axes.X, RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y, AutoSizeAxes = Axes.Y,
Children = new Drawable[] Children = new Drawable[]
@ -84,7 +85,7 @@ namespace osu.Game.Overlays.Comments
Direction = FillDirection.Vertical, Direction = FillDirection.Vertical,
Children = new Drawable[] Children = new Drawable[]
{ {
deletedChildrenPlaceholder = new DeletedChildrenPlaceholder deletedCommentsCounter = new DeletedCommentsCounter
{ {
ShowDeleted = { BindTarget = ShowDeleted } ShowDeleted = { BindTarget = ShowDeleted }
}, },
@ -153,7 +154,8 @@ namespace osu.Game.Overlays.Comments
private void clearComments() private void clearComments()
{ {
currentPage = 1; currentPage = 1;
deletedChildrenPlaceholder.DeletedCount.Value = 0; deletedCommentsCounter.Count.Value = 0;
moreButton.Show();
moreButton.IsLoading = true; moreButton.IsLoading = true;
content.Clear(); content.Clear();
} }
@ -162,29 +164,14 @@ namespace osu.Game.Overlays.Comments
{ {
loadCancellation = new CancellationTokenSource(); loadCancellation = new CancellationTokenSource();
var page = new FillFlowContainer LoadComponentAsync(new CommentsPage(response)
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Direction = FillDirection.Vertical,
};
foreach (var c in response.Comments)
{
if (c.IsTopLevel)
{
page.Add(new DrawableComment(c)
{ {
ShowDeleted = { BindTarget = ShowDeleted } ShowDeleted = { BindTarget = ShowDeleted }
}); }, loaded =>
}
}
LoadComponentAsync(page, loaded =>
{ {
content.Add(loaded); content.Add(loaded);
deletedChildrenPlaceholder.DeletedCount.Value += response.Comments.Count(c => c.IsDeleted && c.IsTopLevel); deletedCommentsCounter.Count.Value += response.Comments.Count(c => c.IsDeleted && c.IsTopLevel);
if (response.HasMore) if (response.HasMore)
{ {
@ -194,10 +181,12 @@ namespace osu.Game.Overlays.Comments
moreButton.Current.Value = response.TopLevelCount - loadedTopLevelComments; moreButton.Current.Value = response.TopLevelCount - loadedTopLevelComments;
moreButton.IsLoading = false; moreButton.IsLoading = false;
} }
else
{
moreButton.Hide();
}
commentCounter.Current.Value = response.Total; commentCounter.Current.Value = response.Total;
moreButton.FadeTo(response.HasMore ? 1 : 0);
}, loadCancellation.Token); }, loadCancellation.Token);
} }

View File

@ -0,0 +1,92 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Allocation;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics;
using osu.Framework.Bindables;
using osu.Game.Online.API.Requests.Responses;
using osu.Framework.Graphics.Shapes;
using osu.Game.Graphics.Sprites;
using System.Linq;
namespace osu.Game.Overlays.Comments
{
public class CommentsPage : CompositeDrawable
{
public readonly BindableBool ShowDeleted = new BindableBool();
private readonly CommentBundle commentBundle;
public CommentsPage(CommentBundle commentBundle)
{
this.commentBundle = commentBundle;
}
[BackgroundDependencyLoader]
private void load(OverlayColourProvider colourProvider)
{
FillFlowContainer flow;
RelativeSizeAxes = Axes.X;
AutoSizeAxes = Axes.Y;
AddRangeInternal(new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = colourProvider.Background5
},
flow = new FillFlowContainer
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Direction = FillDirection.Vertical,
}
});
if (!commentBundle.Comments.Any())
{
flow.Add(new NoCommentsPlaceholder());
return;
}
foreach (var c in commentBundle.Comments)
{
if (c.IsTopLevel)
{
flow.Add(new DrawableComment(c)
{
ShowDeleted = { BindTarget = ShowDeleted }
});
}
}
}
private class NoCommentsPlaceholder : CompositeDrawable
{
[BackgroundDependencyLoader]
private void load(OverlayColourProvider colourProvider)
{
Height = 80;
RelativeSizeAxes = Axes.X;
AddRangeInternal(new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = colourProvider.Background4
},
new OsuSpriteText
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
Margin = new MarginPadding { Left = 50 },
Text = @"No comments yet."
}
});
}
}
}
}

View File

@ -14,6 +14,8 @@ namespace osu.Game.Overlays.Comments
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(OverlayColourProvider colourProvider) private void load(OverlayColourProvider colourProvider)
{ {
Height = 20;
IdleColour = colourProvider.Background2; IdleColour = colourProvider.Background2;
HoverColour = colourProvider.Background1; HoverColour = colourProvider.Background1;
ChevronIconColour = colourProvider.Foreground1; ChevronIconColour = colourProvider.Foreground1;

View File

@ -12,19 +12,24 @@ using osu.Game.Graphics.Sprites;
namespace osu.Game.Overlays.Comments namespace osu.Game.Overlays.Comments
{ {
public class DeletedChildrenPlaceholder : FillFlowContainer public class DeletedCommentsCounter : CompositeDrawable
{ {
public readonly BindableBool ShowDeleted = new BindableBool(); public readonly BindableBool ShowDeleted = new BindableBool();
public readonly BindableInt DeletedCount = new BindableInt();
public readonly BindableInt Count = new BindableInt();
private readonly SpriteText countText; private readonly SpriteText countText;
public DeletedChildrenPlaceholder() public DeletedCommentsCounter()
{ {
AutoSizeAxes = Axes.Both; AutoSizeAxes = Axes.Both;
Direction = FillDirection.Horizontal;
Spacing = new Vector2(3, 0);
Margin = new MarginPadding { Vertical = 10, Left = 80 }; Margin = new MarginPadding { Vertical = 10, Left = 80 };
InternalChild = new FillFlowContainer
{
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Horizontal,
Spacing = new Vector2(3, 0),
Children = new Drawable[] Children = new Drawable[]
{ {
new SpriteIcon new SpriteIcon
@ -36,27 +41,27 @@ namespace osu.Game.Overlays.Comments
{ {
Font = OsuFont.GetFont(size: 14, weight: FontWeight.Bold, italics: true), Font = OsuFont.GetFont(size: 14, weight: FontWeight.Bold, italics: true),
} }
}
}; };
} }
protected override void LoadComplete() protected override void LoadComplete()
{ {
DeletedCount.BindValueChanged(_ => updateDisplay(), true);
ShowDeleted.BindValueChanged(_ => updateDisplay(), true);
base.LoadComplete(); base.LoadComplete();
Count.BindValueChanged(_ => updateDisplay(), true);
ShowDeleted.BindValueChanged(_ => updateDisplay(), true);
} }
private void updateDisplay() private void updateDisplay()
{ {
if (DeletedCount.Value != 0) if (!ShowDeleted.Value && Count.Value != 0)
{ {
countText.Text = @"deleted comment".ToQuantity(DeletedCount.Value); countText.Text = @"deleted comment".ToQuantity(Count.Value);
this.FadeTo(ShowDeleted.Value ? 0 : 1); Show();
} }
else else
{
Hide(); Hide();
} }
} }
}
} }

View File

@ -42,7 +42,7 @@ namespace osu.Game.Overlays.Comments
{ {
LinkFlowContainer username; LinkFlowContainer username;
FillFlowContainer childCommentsContainer; FillFlowContainer childCommentsContainer;
DeletedChildrenPlaceholder deletedChildrenPlaceholder; DeletedCommentsCounter deletedCommentsCounter;
FillFlowContainer info; FillFlowContainer info;
LinkFlowContainer message; LinkFlowContainer message;
GridContainer content; GridContainer content;
@ -184,7 +184,7 @@ namespace osu.Game.Overlays.Comments
AutoSizeAxes = Axes.Y, AutoSizeAxes = Axes.Y,
Direction = FillDirection.Vertical Direction = FillDirection.Vertical
}, },
deletedChildrenPlaceholder = new DeletedChildrenPlaceholder deletedCommentsCounter = new DeletedCommentsCounter
{ {
ShowDeleted = { BindTarget = ShowDeleted } ShowDeleted = { BindTarget = ShowDeleted }
} }
@ -193,7 +193,7 @@ namespace osu.Game.Overlays.Comments
} }
}; };
deletedChildrenPlaceholder.DeletedCount.Value = comment.DeletedChildrenCount; deletedCommentsCounter.Count.Value = comment.DeletedChildrenCount;
if (comment.UserId.HasValue) if (comment.UserId.HasValue)
username.AddUserLink(comment.User); username.AddUserLink(comment.User);
@ -213,7 +213,7 @@ namespace osu.Game.Overlays.Comments
if (comment.HasMessage) if (comment.HasMessage)
{ {
var formattedSource = MessageFormatter.FormatText(comment.GetMessage); var formattedSource = MessageFormatter.FormatText(comment.Message);
message.AddLinks(formattedSource.Text, formattedSource.Links); message.AddLinks(formattedSource.Text, formattedSource.Links);
} }
@ -343,7 +343,7 @@ namespace osu.Game.Overlays.Comments
if (parentComment == null) if (parentComment == null)
return string.Empty; return string.Empty;
return parentComment.HasMessage ? parentComment.GetMessage : parentComment.IsDeleted ? @"deleted" : string.Empty; return parentComment.HasMessage ? parentComment.Message : parentComment.IsDeleted ? @"deleted" : string.Empty;
} }
} }
} }

View File

@ -18,11 +18,11 @@ namespace osu.Game.Overlays
protected IAPIProvider API { get; private set; } protected IAPIProvider API { get; private set; }
[Cached] [Cached]
private readonly OverlayColourProvider colourProvider; protected readonly OverlayColourProvider ColourProvider;
protected FullscreenOverlay(OverlayColourScheme colourScheme) protected FullscreenOverlay(OverlayColourScheme colourScheme)
{ {
colourProvider = new OverlayColourProvider(colourScheme); ColourProvider = new OverlayColourProvider(colourScheme);
RelativeSizeAxes = Axes.Both; RelativeSizeAxes = Axes.Both;
RelativePositionAxes = Axes.Both; RelativePositionAxes = Axes.Both;
@ -43,10 +43,10 @@ namespace osu.Game.Overlays
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load() private void load()
{ {
Waves.FirstWaveColour = colourProvider.Light4; Waves.FirstWaveColour = ColourProvider.Light4;
Waves.SecondWaveColour = colourProvider.Light3; Waves.SecondWaveColour = ColourProvider.Light3;
Waves.ThirdWaveColour = colourProvider.Dark4; Waves.ThirdWaveColour = ColourProvider.Dark4;
Waves.FourthWaveColour = colourProvider.Dark3; Waves.FourthWaveColour = ColourProvider.Dark3;
} }
public override void Show() public override void Show()

View File

@ -30,6 +30,8 @@ namespace osu.Game.Overlays.Mods
{ {
public class ModSelectOverlay : WaveOverlayContainer public class ModSelectOverlay : WaveOverlayContainer
{ {
public const float HEIGHT = 510;
protected readonly TriangleButton DeselectAllButton; protected readonly TriangleButton DeselectAllButton;
protected readonly TriangleButton CustomiseButton; protected readonly TriangleButton CustomiseButton;
protected readonly TriangleButton CloseButton; protected readonly TriangleButton CloseButton;
@ -47,7 +49,7 @@ namespace osu.Game.Overlays.Mods
protected readonly Container ModSettingsContainer; protected readonly Container ModSettingsContainer;
protected readonly Bindable<IReadOnlyList<Mod>> SelectedMods = new Bindable<IReadOnlyList<Mod>>(Array.Empty<Mod>()); public readonly Bindable<IReadOnlyList<Mod>> SelectedMods = new Bindable<IReadOnlyList<Mod>>(Array.Empty<Mod>());
private Bindable<Dictionary<ModType, IReadOnlyList<Mod>>> availableMods; private Bindable<Dictionary<ModType, IReadOnlyList<Mod>>> availableMods;
@ -66,7 +68,8 @@ namespace osu.Game.Overlays.Mods
Waves.ThirdWaveColour = OsuColour.FromHex(@"005774"); Waves.ThirdWaveColour = OsuColour.FromHex(@"005774");
Waves.FourthWaveColour = OsuColour.FromHex(@"003a4e"); Waves.FourthWaveColour = OsuColour.FromHex(@"003a4e");
Height = 510; RelativeSizeAxes = Axes.Both;
Padding = new MarginPadding { Horizontal = -OsuScreen.HORIZONTAL_OVERFLOW_PADDING }; Padding = new MarginPadding { Horizontal = -OsuScreen.HORIZONTAL_OVERFLOW_PADDING };
Children = new Drawable[] Children = new Drawable[]
@ -85,8 +88,7 @@ namespace osu.Game.Overlays.Mods
new Triangles new Triangles
{ {
TriangleScale = 5, TriangleScale = 5,
RelativeSizeAxes = Axes.X, RelativeSizeAxes = Axes.Both,
Height = Height, //set the height from the start to ensure correct triangle density.
ColourLight = new Color4(53, 66, 82, 255), ColourLight = new Color4(53, 66, 82, 255),
ColourDark = new Color4(41, 54, 70, 255), ColourDark = new Color4(41, 54, 70, 255),
}, },
@ -321,14 +323,13 @@ namespace osu.Game.Overlays.Mods
} }
[BackgroundDependencyLoader(true)] [BackgroundDependencyLoader(true)]
private void load(OsuColour colours, AudioManager audio, Bindable<IReadOnlyList<Mod>> selectedMods, OsuGameBase osu) private void load(OsuColour colours, AudioManager audio, OsuGameBase osu)
{ {
LowMultiplierColour = colours.Red; LowMultiplierColour = colours.Red;
HighMultiplierColour = colours.Green; HighMultiplierColour = colours.Green;
UnrankedLabel.Colour = colours.Blue; UnrankedLabel.Colour = colours.Blue;
availableMods = osu.AvailableMods.GetBoundCopy(); availableMods = osu.AvailableMods.GetBoundCopy();
SelectedMods.BindTo(selectedMods);
sampleOn = audio.Samples.Get(@"UI/check-on"); sampleOn = audio.Samples.Get(@"UI/check-on");
sampleOff = audio.Samples.Get(@"UI/check-off"); sampleOff = audio.Samples.Get(@"UI/check-off");

View File

@ -196,7 +196,7 @@ namespace osu.Game.Overlays
if (!instant) if (!instant)
queuedDirection = TrackChangeDirection.Next; queuedDirection = TrackChangeDirection.Next;
var playable = BeatmapSets.SkipWhile(i => i.ID != current.BeatmapSetInfo.ID).Skip(1).FirstOrDefault() ?? BeatmapSets.FirstOrDefault(); var playable = BeatmapSets.SkipWhile(i => i.ID != current.BeatmapSetInfo.ID).ElementAtOrDefault(1) ?? BeatmapSets.FirstOrDefault();
if (playable != null) if (playable != null)
{ {

View File

@ -14,7 +14,7 @@ namespace osu.Game.Overlays.News
private NewsHeaderTitle title; private NewsHeaderTitle title;
public readonly Bindable<string> Current = new Bindable<string>(null); public readonly Bindable<string> Post = new Bindable<string>(null);
public Action ShowFrontPage; public Action ShowFrontPage;
@ -22,13 +22,13 @@ namespace osu.Game.Overlays.News
{ {
TabControl.AddItem(front_page_string); TabControl.AddItem(front_page_string);
TabControl.Current.ValueChanged += e => Current.ValueChanged += e =>
{ {
if (e.NewValue == front_page_string) if (e.NewValue == front_page_string)
ShowFrontPage?.Invoke(); ShowFrontPage?.Invoke();
}; };
Current.ValueChanged += showPost; Post.ValueChanged += showPost;
} }
private void showPost(ValueChangedEvent<string> e) private void showPost(ValueChangedEvent<string> e)
@ -39,13 +39,13 @@ namespace osu.Game.Overlays.News
if (e.NewValue != null) if (e.NewValue != null)
{ {
TabControl.AddItem(e.NewValue); TabControl.AddItem(e.NewValue);
TabControl.Current.Value = e.NewValue; Current.Value = e.NewValue;
title.IsReadingPost = true; title.IsReadingPost = true;
} }
else else
{ {
TabControl.Current.Value = front_page_string; Current.Value = front_page_string;
title.IsReadingPost = false; title.IsReadingPost = false;
} }
} }

View File

@ -60,7 +60,7 @@ namespace osu.Game.Overlays
}, },
}; };
header.Current.BindTo(Current); header.Post.BindTo(Current);
Current.TriggerChange(); Current.TriggerChange();
} }

View File

@ -50,15 +50,30 @@ namespace osu.Game.Overlays
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
Colour = Color4.Gray, Colour = Color4.Gray,
}, },
new Container
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Padding = new MarginPadding
{
Horizontal = UserProfileOverlay.CONTENT_X_MARGIN,
Vertical = 10,
},
Children = new[]
{
title = CreateTitle().With(title => title = CreateTitle().With(title =>
{ {
title.Margin = new MarginPadding title.Anchor = Anchor.CentreLeft;
title.Origin = Anchor.CentreLeft;
}),
CreateTitleContent().With(content =>
{ {
Vertical = 10, content.Anchor = Anchor.CentreRight;
Left = UserProfileOverlay.CONTENT_X_MARGIN content.Origin = Anchor.CentreRight;
};
}) })
} }
}
}
}, },
} }
}, },
@ -75,10 +90,16 @@ namespace osu.Game.Overlays
} }
[NotNull] [NotNull]
protected virtual Drawable CreateContent() => Drawable.Empty(); protected virtual Drawable CreateContent() => Empty();
[NotNull] [NotNull]
protected virtual Drawable CreateBackground() => Drawable.Empty(); protected virtual Drawable CreateBackground() => Empty();
/// <summary>
/// Creates a <see cref="Drawable"/> on the opposite side of the <see cref="ScreenTitle"/>. Used mostly to create <see cref="OverlayRulesetSelector"/>.
/// </summary>
[NotNull]
protected virtual Drawable CreateTitleContent() => Empty();
protected abstract ScreenTitle CreateTitle(); protected abstract ScreenTitle CreateTitle();
} }

View File

@ -0,0 +1,28 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.UserInterface;
using osu.Game.Rulesets;
using osuTK;
namespace osu.Game.Overlays
{
public class OverlayRulesetSelector : RulesetSelector
{
public OverlayRulesetSelector()
{
AutoSizeAxes = Axes.Both;
}
protected override TabItem<RulesetInfo> CreateTabItem(RulesetInfo value) => new OverlayRulesetTabItem(value);
protected override TabFillFlowContainer CreateTabFlow() => new TabFillFlowContainer
{
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Horizontal,
Spacing = new Vector2(25, 0),
};
}
}

View File

@ -0,0 +1,97 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.UserInterface;
using osu.Framework.Input.Events;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterface;
using osu.Game.Rulesets;
using osuTK.Graphics;
using osuTK;
using osu.Framework.Allocation;
namespace osu.Game.Overlays
{
public class OverlayRulesetTabItem : TabItem<RulesetInfo>
{
private Color4 accentColour;
protected virtual Color4 AccentColour
{
get => accentColour;
set
{
accentColour = value;
text.FadeColour(value, 120, Easing.OutQuint);
}
}
protected override Container<Drawable> Content { get; }
[Resolved]
private OverlayColourProvider colourProvider { get; set; }
private readonly OsuSpriteText text;
public OverlayRulesetTabItem(RulesetInfo value)
: base(value)
{
AutoSizeAxes = Axes.Both;
AddRangeInternal(new Drawable[]
{
Content = new FillFlowContainer
{
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Horizontal,
Spacing = new Vector2(3, 0),
Child = text = new OsuSpriteText
{
Origin = Anchor.Centre,
Anchor = Anchor.Centre,
Text = value.Name,
}
},
new HoverClickSounds()
});
Enabled.Value = true;
}
protected override void LoadComplete()
{
base.LoadComplete();
Enabled.BindValueChanged(_ => updateState(), true);
}
public override bool PropagatePositionalInputSubTree => Enabled.Value && !Active.Value && base.PropagatePositionalInputSubTree;
protected override bool OnHover(HoverEvent e)
{
base.OnHover(e);
updateState();
return true;
}
protected override void OnHoverLost(HoverLostEvent e)
{
base.OnHoverLost(e);
updateState();
}
protected override void OnActivated() => updateState();
protected override void OnDeactivated() => updateState();
private void updateState()
{
text.Font = text.Font.With(weight: Active.Value ? FontWeight.Bold : FontWeight.Medium);
AccentColour = Enabled.Value ? getActiveColour() : colourProvider.Foreground1;
}
private Color4 getActiveColour() => IsHovered || Active.Value ? Color4.White : colourProvider.Highlight1;
}
}

View File

@ -1,4 +1,4 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using System; using System;
@ -33,16 +33,16 @@ namespace osu.Game.Overlays.Profile.Header
} }
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(OsuColour colours) private void load(OverlayColourProvider colourProvider)
{ {
iconColour = colours.GreySeafoamLighter; iconColour = colourProvider.Foreground1;
InternalChildren = new Drawable[] InternalChildren = new Drawable[]
{ {
new Box new Box
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
Colour = colours.GreySeafoamDark, Colour = colourProvider.Background4
}, },
new FillFlowContainer new FillFlowContainer
{ {
@ -82,7 +82,7 @@ namespace osu.Game.Overlays.Profile.Header
else else
{ {
topLinkContainer.AddText("Joined "); topLinkContainer.AddText("Joined ");
topLinkContainer.AddText(new DrawableDate(user.JoinDate), embolden); topLinkContainer.AddText(new DrawableDate(user.JoinDate, italic: false), embolden);
} }
addSpacer(topLinkContainer); addSpacer(topLinkContainer);
@ -95,7 +95,7 @@ namespace osu.Game.Overlays.Profile.Header
else if (user.LastVisit.HasValue) else if (user.LastVisit.HasValue)
{ {
topLinkContainer.AddText("Last seen "); topLinkContainer.AddText("Last seen ");
topLinkContainer.AddText(new DrawableDate(user.LastVisit.Value), embolden); topLinkContainer.AddText(new DrawableDate(user.LastVisit.Value, italic: false), embolden);
addSpacer(topLinkContainer); addSpacer(topLinkContainer);
} }
@ -111,34 +111,42 @@ namespace osu.Game.Overlays.Profile.Header
topLinkContainer.AddText("Contributed "); topLinkContainer.AddText("Contributed ");
topLinkContainer.AddLink($@"{user.PostCount:#,##0} forum posts", $"https://osu.ppy.sh/users/{user.Id}/posts", creationParameters: embolden); topLinkContainer.AddLink($@"{user.PostCount:#,##0} forum posts", $"https://osu.ppy.sh/users/{user.Id}/posts", creationParameters: embolden);
string websiteWithoutProtcol = user.Website; string websiteWithoutProtocol = user.Website;
if (!string.IsNullOrEmpty(websiteWithoutProtcol)) if (!string.IsNullOrEmpty(websiteWithoutProtocol))
{ {
if (Uri.TryCreate(websiteWithoutProtcol, UriKind.Absolute, out var uri)) if (Uri.TryCreate(websiteWithoutProtocol, UriKind.Absolute, out var uri))
{ {
websiteWithoutProtcol = uri.Host + uri.PathAndQuery + uri.Fragment; websiteWithoutProtocol = uri.Host + uri.PathAndQuery + uri.Fragment;
websiteWithoutProtcol = websiteWithoutProtcol.TrimEnd('/'); websiteWithoutProtocol = websiteWithoutProtocol.TrimEnd('/');
} }
} }
tryAddInfo(FontAwesome.Solid.MapMarker, user.Location); bool anyInfoAdded = false;
tryAddInfo(OsuIcon.Heart, user.Interests);
tryAddInfo(FontAwesome.Solid.Suitcase, user.Occupation); anyInfoAdded |= tryAddInfo(FontAwesome.Solid.MapMarker, user.Location);
anyInfoAdded |= tryAddInfo(OsuIcon.Heart, user.Interests);
anyInfoAdded |= tryAddInfo(FontAwesome.Solid.Suitcase, user.Occupation);
if (anyInfoAdded)
bottomLinkContainer.NewLine(); bottomLinkContainer.NewLine();
if (!string.IsNullOrEmpty(user.Twitter)) if (!string.IsNullOrEmpty(user.Twitter))
tryAddInfo(FontAwesome.Brands.Twitter, "@" + user.Twitter, $@"https://twitter.com/{user.Twitter}"); anyInfoAdded |= tryAddInfo(FontAwesome.Brands.Twitter, "@" + user.Twitter, $@"https://twitter.com/{user.Twitter}");
tryAddInfo(FontAwesome.Brands.Discord, user.Discord); anyInfoAdded |= tryAddInfo(FontAwesome.Brands.Discord, user.Discord);
tryAddInfo(FontAwesome.Brands.Skype, user.Skype, @"skype:" + user.Skype + @"?chat"); anyInfoAdded |= tryAddInfo(FontAwesome.Brands.Skype, user.Skype, @"skype:" + user.Skype + @"?chat");
tryAddInfo(FontAwesome.Brands.Lastfm, user.Lastfm, $@"https://last.fm/users/{user.Lastfm}"); anyInfoAdded |= tryAddInfo(FontAwesome.Brands.Lastfm, user.Lastfm, $@"https://last.fm/users/{user.Lastfm}");
tryAddInfo(FontAwesome.Solid.Link, websiteWithoutProtcol, user.Website); anyInfoAdded |= tryAddInfo(FontAwesome.Solid.Link, websiteWithoutProtocol, user.Website);
// If no information was added to the bottomLinkContainer, hide it to avoid unwanted padding
bottomLinkContainer.Alpha = anyInfoAdded ? 1 : 0;
} }
private void addSpacer(OsuTextFlowContainer textFlow) => textFlow.AddArbitraryDrawable(new Container { Width = 15 }); private void addSpacer(OsuTextFlowContainer textFlow) => textFlow.AddArbitraryDrawable(new Container { Width = 15 });
private void tryAddInfo(IconUsage icon, string content, string link = null) private bool tryAddInfo(IconUsage icon, string content, string link = null)
{ {
if (string.IsNullOrEmpty(content)) return; if (string.IsNullOrEmpty(content)) return false;
// newlines could be contained in API returned user content. // newlines could be contained in API returned user content.
content = content.Replace("\n", " "); content = content.Replace("\n", " ");
@ -155,6 +163,7 @@ namespace osu.Game.Overlays.Profile.Header
bottomLinkContainer.AddText(" " + content, embolden); bottomLinkContainer.AddText(" " + content, embolden);
addSpacer(bottomLinkContainer); addSpacer(bottomLinkContainer);
return true;
} }
private void embolden(SpriteText text) => text.Font = text.Font.With(weight: FontWeight.Bold); private void embolden(SpriteText text) => text.Font = text.Font.With(weight: FontWeight.Bold);

View File

@ -7,7 +7,6 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Textures; using osu.Framework.Graphics.Textures;
using osu.Game.Graphics;
using osu.Game.Overlays.Profile.Header.Components; using osu.Game.Overlays.Profile.Header.Components;
using osu.Game.Users; using osu.Game.Users;
using osuTK; using osuTK;
@ -28,7 +27,7 @@ namespace osu.Game.Overlays.Profile.Header
} }
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(OsuColour colours, TextureStore textures) private void load(OverlayColourProvider colourProvider, TextureStore textures)
{ {
Container<Drawable> hiddenDetailContainer; Container<Drawable> hiddenDetailContainer;
Container<Drawable> expandedDetailContainer; Container<Drawable> expandedDetailContainer;
@ -38,7 +37,7 @@ namespace osu.Game.Overlays.Profile.Header
new Box new Box
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
Colour = colours.GreySeafoam Colour = colourProvider.Background4
}, },
new FillFlowContainer new FillFlowContainer
{ {
@ -119,12 +118,12 @@ namespace osu.Game.Overlays.Profile.Header
hiddenDetailGlobal = new OverlinedInfoContainer hiddenDetailGlobal = new OverlinedInfoContainer
{ {
Title = "Global Ranking", Title = "Global Ranking",
LineColour = colours.Yellow LineColour = colourProvider.Highlight1
}, },
hiddenDetailCountry = new OverlinedInfoContainer hiddenDetailCountry = new OverlinedInfoContainer
{ {
Title = "Country Ranking", Title = "Country Ranking",
LineColour = colours.Yellow LineColour = colourProvider.Highlight1
}, },
} }
} }

View File

@ -6,7 +6,6 @@ using osu.Framework.Bindables;
using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Sprites;
using osu.Game.Graphics;
using osuTK; using osuTK;
namespace osu.Game.Overlays.Profile.Header.Components namespace osu.Game.Overlays.Profile.Header.Components
@ -25,10 +24,10 @@ namespace osu.Game.Overlays.Profile.Header.Components
} }
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(OsuColour colours) private void load(OverlayColourProvider colourProvider)
{ {
IdleColour = colours.GreySeafoamLight; IdleColour = colourProvider.Background2;
HoverColour = colours.GreySeafoamLight.Darken(0.2f); HoverColour = colourProvider.Background2.Lighten(0.2f);
Child = icon = new SpriteIcon Child = icon = new SpriteIcon
{ {

View File

@ -29,7 +29,7 @@ namespace osu.Game.Overlays.Profile.Header.Components
} }
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(OsuColour colours) private void load(OverlayColourProvider colourProvider)
{ {
InternalChildren = new Drawable[] InternalChildren = new Drawable[]
{ {
@ -42,7 +42,7 @@ namespace osu.Game.Overlays.Profile.Header.Components
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
BackgroundColour = Color4.Black, BackgroundColour = Color4.Black,
Direction = BarDirection.LeftToRight, Direction = BarDirection.LeftToRight,
AccentColour = colours.Yellow AccentColour = colourProvider.Highlight1
} }
}, },
levelProgressText = new OsuSpriteText levelProgressText = new OsuSpriteText

View File

@ -43,7 +43,8 @@ namespace osu.Game.Overlays.Profile.Header.Components
line = new Circle line = new Circle
{ {
RelativeSizeAxes = Axes.X, RelativeSizeAxes = Axes.X,
Height = 4, Height = 2,
Margin = new MarginPadding { Bottom = 2 }
}, },
title = new OsuSpriteText title = new OsuSpriteText
{ {

View File

@ -6,7 +6,6 @@ using osu.Framework.Bindables;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Cursor;
using osu.Game.Graphics;
using osu.Game.Users; using osu.Game.Users;
namespace osu.Game.Overlays.Profile.Header.Components namespace osu.Game.Overlays.Profile.Header.Components
@ -27,12 +26,12 @@ namespace osu.Game.Overlays.Profile.Header.Components
} }
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(OsuColour colours) private void load(OverlayColourProvider colourProvider)
{ {
InternalChild = info = new OverlinedInfoContainer InternalChild = info = new OverlinedInfoContainer
{ {
Title = "Total Play Time", Title = "Total Play Time",
LineColour = colours.Yellow, LineColour = colourProvider.Highlight1,
}; };
User.BindValueChanged(updateTime, true); User.BindValueChanged(updateTime, true);

View File

@ -2,12 +2,11 @@
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using System.Collections.Generic; using System.Collections.Generic;
using osu.Framework.Allocation;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Shapes;
using osu.Game.Graphics;
using osu.Game.Graphics.Containers; using osu.Game.Graphics.Containers;
using osuTK.Graphics;
namespace osu.Game.Overlays.Profile.Header.Components namespace osu.Game.Overlays.Profile.Header.Components
{ {
@ -24,9 +23,6 @@ namespace osu.Game.Overlays.Profile.Header.Components
{ {
AutoSizeAxes = Axes.X; AutoSizeAxes = Axes.X;
IdleColour = Color4.Black;
HoverColour = OsuColour.Gray(0.1f);
base.Content.Add(new CircularContainer base.Content.Add(new CircularContainer
{ {
Masking = true, Masking = true,
@ -47,5 +43,12 @@ namespace osu.Game.Overlays.Profile.Header.Components
} }
}); });
} }
[BackgroundDependencyLoader]
private void load(OverlayColourProvider colourProvider)
{
IdleColour = colourProvider.Background6;
HoverColour = colourProvider.Background5;
}
} }
} }

View File

@ -1,63 +1,29 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using osu.Framework.Allocation;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.UserInterface; using osu.Framework.Graphics.UserInterface;
using osu.Game.Graphics;
using osu.Game.Rulesets; using osu.Game.Rulesets;
using osu.Game.Users; using osu.Game.Users;
using osuTK;
using osuTK.Graphics;
namespace osu.Game.Overlays.Profile.Header.Components namespace osu.Game.Overlays.Profile.Header.Components
{ {
public class ProfileRulesetSelector : RulesetSelector public class ProfileRulesetSelector : OverlayRulesetSelector
{ {
private Color4 accentColour = Color4.White;
public readonly Bindable<User> User = new Bindable<User>(); public readonly Bindable<User> User = new Bindable<User>();
public ProfileRulesetSelector()
{
TabContainer.Masking = false;
TabContainer.Spacing = new Vector2(10, 0);
AutoSizeAxes = Axes.Both;
}
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
accentColour = colours.Seafoam;
foreach (TabItem<RulesetInfo> tabItem in TabContainer)
((ProfileRulesetTabItem)tabItem).AccentColour = accentColour;
}
protected override void LoadComplete() protected override void LoadComplete()
{ {
base.LoadComplete(); base.LoadComplete();
User.BindValueChanged(u => SetDefaultRuleset(Rulesets.GetRuleset(u.NewValue?.PlayMode ?? "osu")), true); User.BindValueChanged(u => SetDefaultRuleset(Rulesets.GetRuleset(u.NewValue?.PlayMode ?? "osu")), true);
} }
public void SetDefaultRuleset(RulesetInfo ruleset) public void SetDefaultRuleset(RulesetInfo ruleset)
{ {
foreach (TabItem<RulesetInfo> tabItem in TabContainer) foreach (var tabItem in TabContainer)
((ProfileRulesetTabItem)tabItem).IsDefault = ((ProfileRulesetTabItem)tabItem).Value.ID == ruleset.ID; ((ProfileRulesetTabItem)tabItem).IsDefault = ((ProfileRulesetTabItem)tabItem).Value.ID == ruleset.ID;
} }
protected override TabItem<RulesetInfo> CreateTabItem(RulesetInfo value) => new ProfileRulesetTabItem(value) protected override TabItem<RulesetInfo> CreateTabItem(RulesetInfo value) => new ProfileRulesetTabItem(value);
{
AccentColour = accentColour
};
protected override TabFillFlowContainer CreateTabFlow() => new TabFillFlowContainer
{
Direction = FillDirection.Horizontal,
AutoSizeAxes = Axes.Both,
};
} }
} }

View File

@ -2,40 +2,15 @@
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites; 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.Rulesets; using osu.Game.Rulesets;
using osuTK; using osuTK;
using osuTK.Graphics; using osuTK.Graphics;
namespace osu.Game.Overlays.Profile.Header.Components namespace osu.Game.Overlays.Profile.Header.Components
{ {
public class ProfileRulesetTabItem : TabItem<RulesetInfo>, IHasAccentColour public class ProfileRulesetTabItem : OverlayRulesetTabItem
{ {
private readonly OsuSpriteText text;
private readonly SpriteIcon icon;
private Color4 accentColour;
public Color4 AccentColour
{
get => accentColour;
set
{
if (accentColour == value)
return;
accentColour = value;
updateState();
}
}
private bool isDefault; private bool isDefault;
public bool IsDefault public bool IsDefault
@ -52,29 +27,22 @@ namespace osu.Game.Overlays.Profile.Header.Components
} }
} }
protected override Color4 AccentColour
{
get => base.AccentColour;
set
{
base.AccentColour = value;
icon.FadeColour(value, 120, Easing.OutQuint);
}
}
private readonly SpriteIcon icon;
public ProfileRulesetTabItem(RulesetInfo value) public ProfileRulesetTabItem(RulesetInfo value)
: base(value) : base(value)
{ {
AutoSizeAxes = Axes.Both; Add(icon = new SpriteIcon
Children = new Drawable[]
{
new FillFlowContainer
{
AutoSizeAxes = Axes.Both,
Origin = Anchor.BottomLeft,
Anchor = Anchor.BottomLeft,
Direction = FillDirection.Horizontal,
Spacing = new Vector2(3, 0),
Children = new Drawable[]
{
text = new OsuSpriteText
{
Origin = Anchor.Centre,
Anchor = Anchor.Centre,
Text = value.Name,
},
icon = new SpriteIcon
{ {
Origin = Anchor.Centre, Origin = Anchor.Centre,
Anchor = Anchor.Centre, Anchor = Anchor.Centre,
@ -82,44 +50,7 @@ namespace osu.Game.Overlays.Profile.Header.Components
AlwaysPresent = true, AlwaysPresent = true,
Icon = FontAwesome.Solid.Star, Icon = FontAwesome.Solid.Star,
Size = new Vector2(12), Size = new Vector2(12),
}, });
}
},
new HoverClickSounds()
};
}
protected override bool OnHover(HoverEvent e)
{
base.OnHover(e);
updateState();
return true;
}
protected override void OnHoverLost(HoverLostEvent e)
{
base.OnHoverLost(e);
updateState();
}
protected override void OnActivated() => updateState();
protected override void OnDeactivated() => updateState();
private void updateState()
{
text.Font = text.Font.With(weight: Active.Value ? FontWeight.Bold : FontWeight.Medium);
if (IsHovered || Active.Value)
{
text.FadeColour(Color4.White, 120, Easing.InQuad);
icon.FadeColour(Color4.White, 120, Easing.InQuad);
}
else
{
text.FadeColour(AccentColour, 120, Easing.InQuad);
icon.FadeColour(AccentColour, 120, Easing.InQuad);
}
} }
} }
} }

Some files were not shown because too many files have changed in this diff Show More