1
0
mirror of https://github.com/ppy/osu.git synced 2024-11-11 17:47:29 +08:00

Merge branch 'master' into overlay-ruleset-selector

This commit is contained in:
Dean Herbert 2020-02-03 13:48:28 +09:00 committed by GitHub
commit fae5bf0f18
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
66 changed files with 950 additions and 423 deletions

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

@ -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 + 100 },
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 + 100 },
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,8 @@ 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 Drawable trackingArea;
private readonly DrawableSlider drawableSlider; private readonly DrawableSlider drawableSlider;
public SliderBall(Slider slider, DrawableSlider drawableSlider = null) public SliderBall(Slider slider, DrawableSlider drawableSlider = null)
@ -38,7 +39,14 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
Children = new[] Children = new[]
{ {
FollowCircle = new FollowCircleContainer // This is separate from the visible followcircle to ensure consistent internal tracking area (needed to match osu-stable)
trackingArea = new CircularContainer
{
Origin = Anchor.Centre,
Anchor = Anchor.Centre,
RelativeSizeAxes = Axes.Both
},
followCircle = new FollowCircleContainer
{ {
Origin = Anchor.Centre, Origin = Anchor.Centre,
Anchor = Anchor.Centre, Anchor = Anchor.Centre,
@ -95,8 +103,10 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
tracking = value; tracking = value;
FollowCircle.ScaleTo(tracking ? 2f : 1, 300, Easing.OutQuint); // Tracking area is bigger than the visible followcircle and scales instantly to match osu-stable
FollowCircle.FadeTo(tracking ? 1f : 0, 300, Easing.OutQuint); trackingArea.ScaleTo(tracking ? 2.4f : 1f);
followCircle.ScaleTo(tracking ? 2f : 1f, 300, Easing.OutQuint);
followCircle.FadeTo(tracking ? 1f : 0, 300, Easing.OutQuint);
} }
} }
@ -149,7 +159,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 && trackingArea.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

@ -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

@ -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

@ -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

@ -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

@ -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

@ -25,7 +25,8 @@ namespace osu.Game.Tests.Visual.Online
typeof(SortTabControl), typeof(SortTabControl),
typeof(ShowChildrenButton), typeof(ShowChildrenButton),
typeof(DeletedCommentsCounter), 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

@ -26,7 +26,6 @@ namespace osu.Game.Tests.Visual.Online
typeof(HistoricalSection), typeof(HistoricalSection),
typeof(PaginatedMostPlayedBeatmapContainer), typeof(PaginatedMostPlayedBeatmapContainer),
typeof(DrawableMostPlayedBeatmap), typeof(DrawableMostPlayedBeatmap),
typeof(DrawableProfileRow)
}; };
[Cached] [Cached]

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

@ -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,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

@ -72,19 +72,24 @@ 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] [SetUpSteps]
public virtual void SetUp() => Schedule(() => public override void SetUpSteps()
{
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()
@ -120,9 +125,6 @@ namespace osu.Game.Tests.Visual.SongSelect
AddUntilStep("wait for not current", () => !songSelect.IsCurrentScreen()); 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]
@ -148,9 +150,6 @@ namespace osu.Game.Tests.Visual.SongSelect
AddUntilStep("wait for not current", () => !songSelect.IsCurrentScreen()); 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]
@ -180,9 +179,6 @@ namespace osu.Game.Tests.Visual.SongSelect
AddUntilStep("wait for not current", () => !songSelect.IsCurrentScreen()); 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]
@ -213,9 +209,6 @@ namespace osu.Game.Tests.Visual.SongSelect
AddUntilStep("wait for not current", () => !songSelect.IsCurrentScreen()); 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]

View File

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

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

@ -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

@ -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

@ -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[]
@ -154,6 +155,7 @@ namespace osu.Game.Overlays.Comments
{ {
currentPage = 1; currentPage = 1;
deletedCommentsCounter.Count.Value = 0; deletedCommentsCounter.Count.Value = 0;
moreButton.Show();
moreButton.IsLoading = true; moreButton.IsLoading = true;
content.Clear(); content.Clear();
} }
@ -162,25 +164,10 @@ 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);
@ -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

@ -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

@ -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

@ -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;
@ -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

@ -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

@ -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

@ -167,9 +167,9 @@ namespace osu.Game.Overlays.Profile.Header.Components
} }
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(OsuColour colours) private void load(OverlayColourProvider colourProvider, OsuColour colours)
{ {
ballBg.Colour = colours.GreySeafoamDarker; ballBg.Colour = colourProvider.Background5;
movingBall.BorderColour = line.Colour = colours.Yellow; movingBall.BorderColour = line.Colour = colours.Yellow;
} }
@ -270,7 +270,9 @@ namespace osu.Game.Overlays.Profile.Header.Components
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(OsuColour colours) private void load(OsuColour colours)
{ {
background.Colour = colours.GreySeafoamDark; // Temporary colour since it's currently impossible to change it without bugs (see https://github.com/ppy/osu-framework/issues/3231)
// If above is fixed, this should use OverlayColourProvider
background.Colour = colours.Gray1;
} }
public bool SetContent(object content) public bool SetContent(object content)

View File

@ -10,7 +10,7 @@ using osu.Game.Graphics.Containers;
namespace osu.Game.Overlays.Profile.Sections namespace osu.Game.Overlays.Profile.Sections
{ {
/// <summary> /// <summary>
/// Display artist/title/mapper information, commonly used as the left portion of a profile or score display row (see <see cref="DrawableProfileRow"/>). /// Display artist/title/mapper information, commonly used as the left portion of a profile or score display row.
/// </summary> /// </summary>
public abstract class BeatmapMetadataContainer : OsuHoverContainer public abstract class BeatmapMetadataContainer : OsuHoverContainer
{ {

View File

@ -1,124 +0,0 @@
// 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.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Effects;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Input.Events;
using osu.Game.Graphics;
using osuTK;
using osuTK.Graphics;
namespace osu.Game.Overlays.Profile.Sections
{
public abstract class DrawableProfileRow : Container
{
private const int fade_duration = 200;
private Box underscoreLine;
private Box coloredBackground;
private Container background;
/// <summary>
/// A visual element displayed to the left of <see cref="LeftFlowContainer"/> content.
/// </summary>
protected abstract Drawable CreateLeftVisual();
protected FillFlowContainer LeftFlowContainer { get; private set; }
protected FillFlowContainer RightFlowContainer { get; private set; }
protected override Container<Drawable> Content { get; }
protected DrawableProfileRow()
{
RelativeSizeAxes = Axes.X;
Height = 60;
Content = new Container
{
RelativeSizeAxes = Axes.Both,
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
Width = 0.97f,
};
}
[BackgroundDependencyLoader(true)]
private void load(OsuColour colour)
{
InternalChildren = new Drawable[]
{
background = new Container
{
RelativeSizeAxes = Axes.Both,
Masking = true,
CornerRadius = 3,
Alpha = 0,
EdgeEffect = new EdgeEffectParameters
{
Type = EdgeEffectType.Shadow,
Offset = new Vector2(0f, 1f),
Radius = 1f,
Colour = Color4.Black.Opacity(0.2f),
},
Child = coloredBackground = new Box { RelativeSizeAxes = Axes.Both }
},
Content,
underscoreLine = new Box
{
Anchor = Anchor.BottomCentre,
Origin = Anchor.BottomCentre,
RelativeSizeAxes = Axes.X,
Height = 1,
},
new FillFlowContainer
{
RelativeSizeAxes = Axes.Both,
Direction = FillDirection.Horizontal,
Children = new[]
{
CreateLeftVisual(),
LeftFlowContainer = new FillFlowContainer
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
Margin = new MarginPadding { Left = 10 },
Direction = FillDirection.Vertical,
},
}
},
RightFlowContainer = new FillFlowContainer
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Anchor = Anchor.CentreRight,
Origin = Anchor.CentreRight,
Direction = FillDirection.Vertical,
},
};
coloredBackground.Colour = underscoreLine.Colour = colour.Gray4;
}
protected override bool OnClick(ClickEvent e) => true;
protected override bool OnHover(HoverEvent e)
{
background.FadeIn(fade_duration, Easing.OutQuint);
underscoreLine.FadeOut(fade_duration, Easing.OutQuint);
return true;
}
protected override void OnHoverLost(HoverLostEvent e)
{
background.FadeOut(fade_duration, Easing.OutQuint);
underscoreLine.FadeIn(fade_duration, Easing.OutQuint);
base.OnHoverLost(e);
}
}
}

View File

@ -44,8 +44,8 @@ namespace osu.Game.Overlays.Profile.Sections
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(OverlayColourProvider colourProvider) private void load(OverlayColourProvider colourProvider)
{ {
background.Colour = idleColour = colourProvider.Background4; background.Colour = idleColour = colourProvider.Background3;
hoverColour = colourProvider.Background3; hoverColour = colourProvider.Background2;
} }
protected override bool OnHover(HoverEvent e) protected override bool OnHover(HoverEvent e)

View File

@ -1,10 +1,10 @@
// 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 JetBrains.Annotations; using JetBrains.Annotations;
using osu.Framework.Allocation; using osu.Framework.Allocation;
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;
@ -16,14 +16,16 @@ using osu.Game.Online.Leaderboards;
using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI;
using osu.Game.Scoring; using osu.Game.Scoring;
using osuTK; using osuTK;
using osuTK.Graphics;
namespace osu.Game.Overlays.Profile.Sections.Ranks namespace osu.Game.Overlays.Profile.Sections.Ranks
{ {
public class DrawableProfileScore : CompositeDrawable public class DrawableProfileScore : CompositeDrawable
{ {
private const int height = 40;
private const int performance_width = 80; private const int performance_width = 80;
private const int content_padding = 10;
private const float performance_background_shear = 0.45f;
private static readonly float performance_background_width = performance_width + (height / 4f * MathF.Tan(performance_background_shear));
protected readonly ScoreInfo Score; protected readonly ScoreInfo Score;
@ -38,7 +40,7 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks
Score = score; Score = score;
RelativeSizeAxes = Axes.X; RelativeSizeAxes = Axes.X;
Height = 40; Height = height;
} }
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
@ -51,7 +53,7 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks
new Container new Container
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding { Left = content_padding, Right = performance_width + content_padding }, Padding = new MarginPadding { Left = 10, Right = performance_width + 30 },
Children = new Drawable[] Children = new Drawable[]
{ {
new FillFlowContainer new FillFlowContainer
@ -142,20 +144,26 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks
{ {
new Box new Box
{ {
RelativeSizeAxes = Axes.Both, Anchor = Anchor.TopRight,
Size = new Vector2(1, 0.5f), Origin = Anchor.TopRight,
Colour = Color4.Black.Opacity(0.5f), RelativeSizeAxes = Axes.Y,
Shear = new Vector2(-0.45f, 0), Width = performance_background_width,
Height = 0.5f,
Colour = colourProvider.Background4,
Shear = new Vector2(-performance_background_shear, 0),
EdgeSmoothness = new Vector2(2, 0), EdgeSmoothness = new Vector2(2, 0),
}, },
new Box new Box
{ {
RelativeSizeAxes = Axes.Both, Anchor = Anchor.TopRight,
Origin = Anchor.TopRight,
RelativeSizeAxes = Axes.Y,
RelativePositionAxes = Axes.Y, RelativePositionAxes = Axes.Y,
Size = new Vector2(1, -0.5f), Width = performance_background_width,
Height = -0.5f,
Position = new Vector2(0, 1), Position = new Vector2(0, 1),
Colour = Color4.Black.Opacity(0.5f), Colour = colourProvider.Background4,
Shear = new Vector2(0.45f, 0), Shear = new Vector2(performance_background_shear, 0),
EdgeSmoothness = new Vector2(2, 0), EdgeSmoothness = new Vector2(2, 0),
}, },
createDrawablePerformance().With(d => createDrawablePerformance().With(d =>

View File

@ -1,9 +1,11 @@
// 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.Linq;
using osu.Framework.Allocation; 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.Sprites;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Graphics.Containers; using osu.Game.Graphics.Containers;
using osu.Game.Online.API; using osu.Game.Online.API;
@ -11,12 +13,19 @@ using osu.Game.Online.API.Requests;
using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.API.Requests.Responses;
using osu.Game.Online.Chat; using osu.Game.Online.Chat;
using osu.Game.Online.Leaderboards; using osu.Game.Online.Leaderboards;
using osu.Game.Rulesets;
namespace osu.Game.Overlays.Profile.Sections.Recent namespace osu.Game.Overlays.Profile.Sections.Recent
{ {
public class DrawableRecentActivity : DrawableProfileRow public class DrawableRecentActivity : CompositeDrawable
{ {
private IAPIProvider api; private const int font_size = 14;
[Resolved]
private IAPIProvider api { get; set; }
[Resolved]
private RulesetStore rulesets { get; set; }
private readonly APIRecentActivity activity; private readonly APIRecentActivity activity;
@ -28,139 +37,191 @@ namespace osu.Game.Overlays.Profile.Sections.Recent
} }
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(IAPIProvider api) private void load(OverlayColourProvider colourProvider)
{ {
this.api = api; RelativeSizeAxes = Axes.X;
AutoSizeAxes = Axes.Y;
LeftFlowContainer.Padding = new MarginPadding { Left = 10, Right = 160 }; AddInternal(new GridContainer
LeftFlowContainer.Add(content = new LinkFlowContainer
{ {
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
ColumnDimensions = new[]
{
new Dimension(GridSizeMode.Absolute, size: 28),
new Dimension(),
new Dimension(GridSizeMode.AutoSize)
},
RowDimensions = new[]
{
new Dimension(GridSizeMode.AutoSize)
},
Content = new[]
{
new Drawable[]
{
new Container
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Child = createIcon().With(icon =>
{
icon.Anchor = Anchor.Centre;
icon.Origin = Anchor.Centre;
})
},
content = new LinkFlowContainer(t => t.Font = OsuFont.GetFont(size: font_size))
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
AutoSizeAxes = Axes.Y, AutoSizeAxes = Axes.Y,
RelativeSizeAxes = Axes.X, RelativeSizeAxes = Axes.X,
}); },
new DrawableDate(activity.CreatedAt)
RightFlowContainer.Add(new DrawableDate(activity.CreatedAt)
{ {
Font = OsuFont.GetFont(size: 13), Anchor = Anchor.CentreRight,
Colour = OsuColour.Gray(0xAA), Origin = Anchor.CentreRight,
Anchor = Anchor.TopRight, Colour = colourProvider.Foreground1,
Origin = Anchor.TopRight, Font = OsuFont.GetFont(size: font_size),
}
}
}
}); });
var formatted = createMessage(); createMessage();
content.AddLinks(formatted.Text, formatted.Links);
} }
protected override Drawable CreateLeftVisual() private Drawable createIcon()
{ {
switch (activity.Type) switch (activity.Type)
{ {
case RecentActivityType.Rank: case RecentActivityType.Rank:
return new UpdateableRank(activity.ScoreRank) return new UpdateableRank(activity.ScoreRank)
{ {
RelativeSizeAxes = Axes.Y, RelativeSizeAxes = Axes.X,
Width = 60, Height = 11,
FillMode = FillMode.Fit, FillMode = FillMode.Fit,
Margin = new MarginPadding { Top = 2 }
}; };
case RecentActivityType.Achievement: case RecentActivityType.Achievement:
return new DelayedLoadWrapper(new MedalIcon(activity.Achievement.Slug) return new DelayedLoadWrapper(new MedalIcon(activity.Achievement.Slug)
{ {
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
FillMode = FillMode.Fit, FillMode = FillMode.Fit,
}) })
{ {
RelativeSizeAxes = Axes.Y, RelativeSizeAxes = Axes.X,
Width = 60, Width = 0.5f,
Height = 18
}; };
default: default:
return new Container return Empty();
{
RelativeSizeAxes = Axes.Y,
Width = 60,
FillMode = FillMode.Fit,
};
} }
} }
private string toAbsoluteUrl(string url) => $"{api.Endpoint}{url}"; private void createMessage()
private MessageFormatter.MessageFormatterResult createMessage()
{ {
string userLinkTemplate() => $"[{toAbsoluteUrl(activity.User?.Url)} {activity.User?.Username}]";
string beatmapLinkTemplate() => $"[{toAbsoluteUrl(activity.Beatmap?.Url)} {activity.Beatmap?.Title}]";
string beatmapsetLinkTemplate() => $"[{toAbsoluteUrl(activity.Beatmapset?.Url)} {activity.Beatmapset?.Title}]";
string message;
switch (activity.Type) switch (activity.Type)
{ {
case RecentActivityType.Achievement: case RecentActivityType.Achievement:
message = $"{userLinkTemplate()} unlocked the {activity.Achievement.Name} medal!"; addUserLink();
addText($" unlocked the \"{activity.Achievement.Name}\" medal!");
break; break;
case RecentActivityType.BeatmapPlaycount: case RecentActivityType.BeatmapPlaycount:
message = $"{beatmapLinkTemplate()} has been played {activity.Count} times!"; addBeatmapLink();
addText($" has been played {activity.Count} times!");
break; break;
case RecentActivityType.BeatmapsetApprove: case RecentActivityType.BeatmapsetApprove:
message = $"{beatmapsetLinkTemplate()} has been {activity.Approval.ToString().ToLowerInvariant()}!"; addBeatmapsetLink();
addText($" has been {activity.Approval.ToString().ToLowerInvariant()}!");
break; break;
case RecentActivityType.BeatmapsetDelete: case RecentActivityType.BeatmapsetDelete:
message = $"{beatmapsetLinkTemplate()} has been deleted."; addBeatmapsetLink();
addText(" has been deleted.");
break; break;
case RecentActivityType.BeatmapsetRevive: case RecentActivityType.BeatmapsetRevive:
message = $"{beatmapsetLinkTemplate()} has been revived from eternal slumber by {userLinkTemplate()}."; addBeatmapsetLink();
addText(" has been revived from eternal slumber by ");
addUserLink();
break; break;
case RecentActivityType.BeatmapsetUpdate: case RecentActivityType.BeatmapsetUpdate:
message = $"{userLinkTemplate()} has updated the beatmap {beatmapsetLinkTemplate()}!"; addUserLink();
addText(" has updated the beatmap ");
addBeatmapsetLink();
break; break;
case RecentActivityType.BeatmapsetUpload: case RecentActivityType.BeatmapsetUpload:
message = $"{userLinkTemplate()} has submitted a new beatmap {beatmapsetLinkTemplate()}!"; addUserLink();
addText(" has submitted a new beatmap ");
addBeatmapsetLink();
break; break;
case RecentActivityType.Medal: case RecentActivityType.Medal:
// apparently this shouldn't exist look at achievement instead (https://github.com/ppy/osu-web/blob/master/resources/assets/coffee/react/profile-page/recent-activity.coffee#L111) // apparently this shouldn't exist look at achievement instead (https://github.com/ppy/osu-web/blob/master/resources/assets/coffee/react/profile-page/recent-activity.coffee#L111)
message = string.Empty;
break; break;
case RecentActivityType.Rank: case RecentActivityType.Rank:
message = $"{userLinkTemplate()} achieved rank #{activity.Rank} on {beatmapLinkTemplate()} ({activity.Mode}!)"; addUserLink();
addText($" achieved rank #{activity.Rank} on ");
addBeatmapLink();
addText($" ({getRulesetName()})");
break; break;
case RecentActivityType.RankLost: case RecentActivityType.RankLost:
message = $"{userLinkTemplate()} has lost first place on {beatmapLinkTemplate()} ({activity.Mode}!)"; addUserLink();
addText(" has lost first place on ");
addBeatmapLink();
addText($" ({getRulesetName()})");
break; break;
case RecentActivityType.UserSupportAgain: case RecentActivityType.UserSupportAgain:
message = $"{userLinkTemplate()} has once again chosen to support osu! - thanks for your generosity!"; addUserLink();
addText(" has once again chosen to support osu! - thanks for your generosity!");
break; break;
case RecentActivityType.UserSupportFirst: case RecentActivityType.UserSupportFirst:
message = $"{userLinkTemplate()} has become an osu!supporter - thanks for your generosity!"; addUserLink();
addText(" has become an osu!supporter - thanks for your generosity!");
break; break;
case RecentActivityType.UserSupportGift: case RecentActivityType.UserSupportGift:
message = $"{userLinkTemplate()} has received the gift of osu!supporter!"; addUserLink();
addText(" has received the gift of osu!supporter!");
break; break;
case RecentActivityType.UsernameChange: case RecentActivityType.UsernameChange:
message = $"{activity.User?.PreviousUsername} has changed their username to {userLinkTemplate()}!"; addText($"{activity.User?.PreviousUsername} has changed their username to ");
break; addUserLink();
default:
message = string.Empty;
break; break;
} }
}
return MessageFormatter.FormatText(message); private string getRulesetName() =>
} rulesets.AvailableRulesets.FirstOrDefault(r => r.ShortName == activity.Mode)?.Name ?? activity.Mode;
private void addUserLink()
=> content.AddLink(activity.User?.Username, LinkAction.OpenUserProfile, getLinkArgument(activity.User?.Url), creationParameters: t => t.Font = getLinkFont(FontWeight.Bold));
private void addBeatmapLink()
=> content.AddLink(activity.Beatmap?.Title, LinkAction.OpenBeatmap, getLinkArgument(activity.Beatmap?.Url), creationParameters: t => t.Font = getLinkFont());
private void addBeatmapsetLink()
=> content.AddLink(activity.Beatmapset?.Title, LinkAction.OpenBeatmapSet, getLinkArgument(activity.Beatmapset?.Url), creationParameters: t => t.Font = getLinkFont());
private string getLinkArgument(string url) => MessageFormatter.GetLinkDetails($"{api.Endpoint}{url}").Argument;
private FontUsage getLinkFont(FontWeight fontWeight = FontWeight.Regular)
=> OsuFont.GetFont(size: font_size, weight: fontWeight, italics: true);
private void addText(string text)
=> content.AddText(text, t => t.Font = OsuFont.GetFont(size: font_size, weight: FontWeight.SemiBold));
} }
} }

View File

@ -23,8 +23,7 @@ namespace osu.Game.Overlays.Profile.Sections.Recent
Child = sprite = new Sprite Child = sprite = new Sprite
{ {
Height = 40, RelativeSizeAxes = Axes.Both,
Width = 40,
Anchor = Anchor.Centre, Anchor = Anchor.Centre,
Origin = Anchor.Centre, Origin = Anchor.Centre,
}; };

View File

@ -8,6 +8,7 @@ using osu.Framework.Bindables;
using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.API.Requests.Responses;
using osu.Game.Online.API; using osu.Game.Online.API;
using System.Collections.Generic; using System.Collections.Generic;
using osuTK;
namespace osu.Game.Overlays.Profile.Sections.Recent namespace osu.Game.Overlays.Profile.Sections.Recent
{ {
@ -16,7 +17,8 @@ namespace osu.Game.Overlays.Profile.Sections.Recent
public PaginatedRecentActivityContainer(Bindable<User> user, string header, string missing) public PaginatedRecentActivityContainer(Bindable<User> user, string header, string missing)
: base(user, header, missing) : base(user, header, missing)
{ {
ItemsPerPage = 5; ItemsPerPage = 10;
ItemsContainer.Spacing = new Vector2(0, 8);
} }
protected override APIRequest<List<APIRecentActivity>> CreateRequest() => protected override APIRequest<List<APIRecentActivity>> CreateRequest() =>

View File

@ -99,7 +99,7 @@ namespace osu.Game.Overlays.Toolbar
{ {
int requested = e.Key - Key.Number1; int requested = e.Key - Key.Number1;
RulesetInfo found = Rulesets.AvailableRulesets.Skip(requested).FirstOrDefault(); RulesetInfo found = Rulesets.AvailableRulesets.ElementAtOrDefault(requested);
if (found != null) if (found != null)
Current.Value = found; Current.Value = found;
return true; return true;

View File

@ -67,7 +67,7 @@ namespace osu.Game.Overlays
RelativeSizeAxes = Axes.X, RelativeSizeAxes = Axes.X,
Anchor = Anchor.TopCentre, Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre, Origin = Anchor.TopCentre,
Height = 30 Height = 34
}; };
Add(new Box Add(new Box

View File

@ -154,7 +154,7 @@ namespace osu.Game.Rulesets.Edit
{ {
if (e.Key >= Key.Number1 && e.Key <= Key.Number9) if (e.Key >= Key.Number1 && e.Key <= Key.Number9)
{ {
var item = toolboxCollection.Items.Skip(e.Key - Key.Number1).FirstOrDefault(); var item = toolboxCollection.Items.ElementAtOrDefault(e.Key - Key.Number1);
if (item != null) if (item != null)
{ {

View File

@ -34,7 +34,7 @@ namespace osu.Game.Rulesets.Objects
/// </summary> /// </summary>
public event Action DefaultsApplied; public event Action DefaultsApplied;
public readonly Bindable<double> StartTimeBindable = new Bindable<double>(); public readonly Bindable<double> StartTimeBindable = new BindableDouble();
/// <summary> /// <summary>
/// The time at which the HitObject starts. /// The time at which the HitObject starts.

View File

@ -220,9 +220,11 @@ namespace osu.Game.Scoring.Legacy
float lastTime = 0; float lastTime = 0;
ReplayFrame currentFrame = null; ReplayFrame currentFrame = null;
foreach (var l in reader.ReadToEnd().Split(',')) var frames = reader.ReadToEnd().Split(',');
for (var i = 0; i < frames.Length; i++)
{ {
var split = l.Split('|'); var split = frames[i].Split('|');
if (split.Length < 4) if (split.Length < 4)
continue; continue;
@ -234,8 +236,14 @@ namespace osu.Game.Scoring.Legacy
} }
var diff = Parsing.ParseFloat(split[0]); var diff = Parsing.ParseFloat(split[0]);
lastTime += diff; lastTime += diff;
if (i == 0 && diff == 0)
// osu-stable adds a zero-time frame before potentially valid negative user frames.
// we need to ignore this.
continue;
// Todo: At some point we probably want to rewind and play back the negative-time frames // Todo: At some point we probably want to rewind and play back the negative-time frames
// but for now we'll achieve equal playback to stable by skipping negative frames // but for now we'll achieve equal playback to stable by skipping negative frames
if (diff < 0) if (diff < 0)

View File

@ -36,7 +36,7 @@ namespace osu.Game.Screens.Backgrounds
/// <summary> /// <summary>
/// The amount of blur to be applied in addition to user-specified blur. /// The amount of blur to be applied in addition to user-specified blur.
/// </summary> /// </summary>
public readonly Bindable<float> BlurAmount = new Bindable<float>(); public readonly Bindable<float> BlurAmount = new BindableFloat();
internal readonly IBindable<bool> IsBreakTime = new Bindable<bool>(); internal readonly IBindable<bool> IsBreakTime = new Bindable<bool>();
@ -119,7 +119,7 @@ namespace osu.Game.Screens.Backgrounds
/// <remarks> /// <remarks>
/// Used in contexts where there can potentially be both user and screen-specified blurring occuring at the same time, such as in <see cref="PlayerLoader"/> /// Used in contexts where there can potentially be both user and screen-specified blurring occuring at the same time, such as in <see cref="PlayerLoader"/>
/// </remarks> /// </remarks>
public readonly Bindable<float> BlurAmount = new Bindable<float>(); public readonly Bindable<float> BlurAmount = new BindableFloat();
public Background Background public Background Background
{ {

View File

@ -16,6 +16,7 @@ using System.Collections.Generic;
using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Mods;
using System.Linq; using System.Linq;
using osu.Framework.Threading; using osu.Framework.Threading;
using osu.Framework.Utils;
using osu.Game.Configuration; using osu.Game.Configuration;
using osu.Game.Overlays.Settings; using osu.Game.Overlays.Settings;
@ -26,7 +27,8 @@ namespace osu.Game.Screens.Select.Details
[Resolved] [Resolved]
private IBindable<IReadOnlyList<Mod>> mods { get; set; } private IBindable<IReadOnlyList<Mod>> mods { get; set; }
private readonly StatisticRow firstValue, hpDrain, accuracy, approachRate, starDifficulty; protected readonly StatisticRow FirstValue, HpDrain, Accuracy, ApproachRate;
private readonly StatisticRow starDifficulty;
private BeatmapInfo beatmap; private BeatmapInfo beatmap;
@ -52,10 +54,10 @@ namespace osu.Game.Screens.Select.Details
Spacing = new Vector2(4f), Spacing = new Vector2(4f),
Children = new[] Children = new[]
{ {
firstValue = new StatisticRow(), //circle size/key amount FirstValue = new StatisticRow(), //circle size/key amount
hpDrain = new StatisticRow { Title = "HP Drain" }, HpDrain = new StatisticRow { Title = "HP Drain" },
accuracy = new StatisticRow { Title = "Accuracy" }, Accuracy = new StatisticRow { Title = "Accuracy" },
approachRate = new StatisticRow { Title = "Approach Rate" }, ApproachRate = new StatisticRow { Title = "Approach Rate" },
starDifficulty = new StatisticRow(10, true) { Title = "Star Difficulty" }, starDifficulty = new StatisticRow(10, true) { Title = "Star Difficulty" },
}, },
}; };
@ -122,24 +124,24 @@ namespace osu.Game.Screens.Select.Details
case 3: case 3:
// Account for mania differences locally for now // Account for mania differences locally for now
// Eventually this should be handled in a more modular way, allowing rulesets to return arbitrary difficulty attributes // Eventually this should be handled in a more modular way, allowing rulesets to return arbitrary difficulty attributes
firstValue.Title = "Key Count"; FirstValue.Title = "Key Count";
firstValue.Value = (baseDifficulty?.CircleSize ?? 0, null); FirstValue.Value = (baseDifficulty?.CircleSize ?? 0, null);
break; break;
default: default:
firstValue.Title = "Circle Size"; FirstValue.Title = "Circle Size";
firstValue.Value = (baseDifficulty?.CircleSize ?? 0, adjustedDifficulty?.CircleSize); FirstValue.Value = (baseDifficulty?.CircleSize ?? 0, adjustedDifficulty?.CircleSize);
break; break;
} }
starDifficulty.Value = ((float)(Beatmap?.StarDifficulty ?? 0), null); starDifficulty.Value = ((float)(Beatmap?.StarDifficulty ?? 0), null);
hpDrain.Value = (baseDifficulty?.DrainRate ?? 0, adjustedDifficulty?.DrainRate); HpDrain.Value = (baseDifficulty?.DrainRate ?? 0, adjustedDifficulty?.DrainRate);
accuracy.Value = (baseDifficulty?.OverallDifficulty ?? 0, adjustedDifficulty?.OverallDifficulty); Accuracy.Value = (baseDifficulty?.OverallDifficulty ?? 0, adjustedDifficulty?.OverallDifficulty);
approachRate.Value = (baseDifficulty?.ApproachRate ?? 0, adjustedDifficulty?.ApproachRate); ApproachRate.Value = (baseDifficulty?.ApproachRate ?? 0, adjustedDifficulty?.ApproachRate);
} }
private class StatisticRow : Container, IHasAccentColour public class StatisticRow : Container, IHasAccentColour
{ {
private const float value_width = 25; private const float value_width = 25;
private const float name_width = 70; private const float name_width = 70;
@ -147,7 +149,8 @@ namespace osu.Game.Screens.Select.Details
private readonly float maxValue; private readonly float maxValue;
private readonly bool forceDecimalPlaces; private readonly bool forceDecimalPlaces;
private readonly OsuSpriteText name, valueText; private readonly OsuSpriteText name, valueText;
private readonly Bar bar, modBar; private readonly Bar bar;
public readonly Bar ModBar;
[Resolved] [Resolved]
private OsuColour colours { get; set; } private OsuColour colours { get; set; }
@ -173,14 +176,14 @@ namespace osu.Game.Screens.Select.Details
bar.Length = value.baseValue / maxValue; bar.Length = value.baseValue / maxValue;
valueText.Text = (value.adjustedValue ?? value.baseValue).ToString(forceDecimalPlaces ? "0.00" : "0.##"); valueText.Text = (value.adjustedValue ?? value.baseValue).ToString(forceDecimalPlaces ? "0.00" : "0.##");
modBar.Length = (value.adjustedValue ?? 0) / maxValue; ModBar.Length = (value.adjustedValue ?? 0) / maxValue;
if (value.adjustedValue > value.baseValue) if (Precision.AlmostEquals(value.baseValue, value.adjustedValue ?? value.baseValue, 0.05f))
modBar.AccentColour = valueText.Colour = colours.Red; ModBar.AccentColour = valueText.Colour = Color4.White;
else if (value.adjustedValue > value.baseValue)
ModBar.AccentColour = valueText.Colour = colours.Red;
else if (value.adjustedValue < value.baseValue) else if (value.adjustedValue < value.baseValue)
modBar.AccentColour = valueText.Colour = colours.BlueDark; ModBar.AccentColour = valueText.Colour = colours.BlueDark;
else
modBar.AccentColour = valueText.Colour = Color4.White;
} }
} }
@ -217,7 +220,7 @@ namespace osu.Game.Screens.Select.Details
BackgroundColour = Color4.White.Opacity(0.5f), BackgroundColour = Color4.White.Opacity(0.5f),
Padding = new MarginPadding { Left = name_width + 10, Right = value_width + 10 }, Padding = new MarginPadding { Left = name_width + 10, Right = value_width + 10 },
}, },
modBar = new Bar ModBar = new Bar
{ {
Origin = Anchor.CentreLeft, Origin = Anchor.CentreLeft,
Anchor = Anchor.CentreLeft, Anchor = Anchor.CentreLeft,

View File

@ -149,8 +149,8 @@ namespace osu.Game.Screens.Select
private readonly IBindable<RulesetInfo> ruleset = new Bindable<RulesetInfo>(); private readonly IBindable<RulesetInfo> ruleset = new Bindable<RulesetInfo>();
private readonly Bindable<bool> showConverted = new Bindable<bool>(); private readonly Bindable<bool> showConverted = new Bindable<bool>();
private readonly Bindable<double> minimumStars = new Bindable<double>(); private readonly Bindable<double> minimumStars = new BindableDouble();
private readonly Bindable<double> maximumStars = new Bindable<double>(); private readonly Bindable<double> maximumStars = new BindableDouble();
public readonly Box Background; public readonly Box Background;

View File

@ -345,8 +345,8 @@ namespace osu.Game.Screens.Select
selectionChangedDebounce = null; selectionChangedDebounce = null;
} }
if (performStartAction) if (performStartAction && OnStart())
OnStart(); Carousel.AllowSelection = false;
} }
/// <summary> /// <summary>
@ -500,6 +500,8 @@ namespace osu.Game.Screens.Select
public override void OnResuming(IScreen last) public override void OnResuming(IScreen last)
{ {
Carousel.AllowSelection = true;
BeatmapDetails.Leaderboard.RefreshScores(); BeatmapDetails.Leaderboard.RefreshScores();
Beatmap.Value.Track.Looping = true; Beatmap.Value.Track.Looping = true;
@ -647,7 +649,6 @@ namespace osu.Game.Screens.Select
decoupledRuleset.ValueChanged += r => Ruleset.Value = r.NewValue; decoupledRuleset.ValueChanged += r => Ruleset.Value = r.NewValue;
decoupledRuleset.DisabledChanged += r => Ruleset.Disabled = r; decoupledRuleset.DisabledChanged += r => Ruleset.Disabled = r;
Beatmap.BindDisabledChanged(disabled => Carousel.AllowSelection = !disabled, true);
Beatmap.BindValueChanged(workingBeatmapChanged); Beatmap.BindValueChanged(workingBeatmapChanged);
boundLocalBindables = true; boundLocalBindables = true;

View File

@ -33,8 +33,10 @@ namespace osu.Game.Tests.Visual
} }
[SetUpSteps] [SetUpSteps]
public virtual void SetUpSteps() public override void SetUpSteps()
{ {
base.SetUpSteps();
AddStep(ruleset.RulesetInfo.Name, loadPlayer); AddStep(ruleset.RulesetInfo.Name, loadPlayer);
AddUntilStep("player loaded", () => Player.IsLoaded && Player.Alpha == 1); AddUntilStep("player loaded", () => Player.IsLoaded && Player.Alpha == 1);
} }

View File

@ -3,6 +3,7 @@
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Testing;
using osu.Game.Screens; using osu.Game.Screens;
namespace osu.Game.Tests.Visual namespace osu.Game.Tests.Visual
@ -27,11 +28,23 @@ namespace osu.Game.Tests.Visual
}); });
} }
protected void LoadScreen(OsuScreen screen) protected void LoadScreen(OsuScreen screen) => Stack.Push(screen);
[SetUpSteps]
public virtual void SetUpSteps() => addExitAllScreensStep();
[TearDownSteps]
public void TearDownSteps() => addExitAllScreensStep();
private void addExitAllScreensStep()
{ {
if (Stack.CurrentScreen != null) AddUntilStep("exit all screens", () =>
{
if (Stack.CurrentScreen == null) return true;
Stack.Exit(); Stack.Exit();
Stack.Push(screen); return false;
});
} }
} }
} }

View File

@ -47,7 +47,7 @@ namespace osu.Game.Tests.Visual
public readonly Bindable<ScrollingDirection> Direction = new Bindable<ScrollingDirection>(); public readonly Bindable<ScrollingDirection> Direction = new Bindable<ScrollingDirection>();
IBindable<ScrollingDirection> IScrollingInfo.Direction => Direction; IBindable<ScrollingDirection> IScrollingInfo.Direction => Direction;
public readonly Bindable<double> TimeRange = new Bindable<double>(1000) { Value = 1000 }; public readonly Bindable<double> TimeRange = new BindableDouble(1000) { Value = 1000 };
IBindable<double> IScrollingInfo.TimeRange => TimeRange; IBindable<double> IScrollingInfo.TimeRange => TimeRange;
public readonly TestScrollAlgorithm Algorithm = new TestScrollAlgorithm(); public readonly TestScrollAlgorithm Algorithm = new TestScrollAlgorithm();

View File

@ -23,7 +23,7 @@
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="2.2.6" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="2.2.6" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" /> <PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
<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" Version="2020.125.0" /> <PackageReference Include="ppy.osu.Framework" Version="2020.131.0" />
<PackageReference Include="Sentry" Version="1.2.0" /> <PackageReference Include="Sentry" Version="1.2.0" />
<PackageReference Include="SharpCompress" Version="0.24.0" /> <PackageReference Include="SharpCompress" Version="0.24.0" />
<PackageReference Include="NUnit" Version="3.12.0" /> <PackageReference Include="NUnit" Version="3.12.0" />

View File

@ -74,7 +74,7 @@
</ItemGroup> </ItemGroup>
<ItemGroup Label="Package References"> <ItemGroup Label="Package References">
<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.iOS" Version="2020.125.0" /> <PackageReference Include="ppy.osu.Framework.iOS" Version="2020.131.0" />
</ItemGroup> </ItemGroup>
<!-- Xamarin.iOS does not automatically handle transitive dependencies from NuGet packages. --> <!-- Xamarin.iOS does not automatically handle transitive dependencies from NuGet packages. -->
<ItemGroup Label="Transitive Dependencies"> <ItemGroup Label="Transitive Dependencies">
@ -82,7 +82,7 @@
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.2.6" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.2.6" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="2.2.6" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="2.2.6" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" /> <PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
<PackageReference Include="ppy.osu.Framework" Version="2020.125.0" /> <PackageReference Include="ppy.osu.Framework" Version="2020.131.0" />
<PackageReference Include="SharpCompress" Version="0.24.0" /> <PackageReference Include="SharpCompress" Version="0.24.0" />
<PackageReference Include="NUnit" Version="3.12.0" /> <PackageReference Include="NUnit" Version="3.12.0" />
<PackageReference Include="SharpRaven" Version="2.4.0" /> <PackageReference Include="SharpRaven" Version="2.4.0" />