1
0
mirror of https://github.com/ppy/osu.git synced 2024-09-22 04:07:25 +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>
<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>
</Project>

View File

@ -7,12 +7,10 @@ using System.Linq;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Audio;
using osu.Framework.Graphics;
using osu.Framework.IO.Stores;
using osu.Framework.Testing;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Screens;
using osu.Game.Screens.Play;
using osu.Game.Skinning;
using osu.Game.Tests.Visual;
@ -21,7 +19,7 @@ using osuTK.Graphics;
namespace osu.Game.Rulesets.Osu.Tests
{
public class TestSceneLegacyBeatmapSkin : OsuTestScene
public class TestSceneLegacyBeatmapSkin : ScreenTestScene
{
[Resolved]
private AudioManager audio { get; set; }
@ -65,7 +63,8 @@ namespace osu.Game.Rulesets.Osu.Tests
ExposedPlayer player;
Beatmap.Value = new CustomSkinWorkingBeatmap(audio, beatmapHasColours);
Child = new OsuScreenStack(player = new ExposedPlayer(userHasCustomColours)) { RelativeSizeAxes = Axes.Both };
LoadScreen(player = new ExposedPlayer(userHasCustomColours));
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_3 = 3500;
private const double time_during_slide_4 = 3800;
private const double time_slider_end = 4000;
private List<JudgementResult> judgementResults;
private bool allJudgedFired;
@ -284,6 +285,48 @@ namespace osu.Game.Rulesets.Osu.Tests
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 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 const float slider_path_length = 25;
private void performTest(List<ReplayFrame> frames)
{
AddStep("load player", () =>
@ -309,8 +354,8 @@ namespace osu.Game.Rulesets.Osu.Tests
Path = new SliderPath(PathType.PerfectCurve, new[]
{
Vector2.Zero,
new Vector2(25, 0),
}, 25),
new Vector2(slider_path_length, 0),
}, slider_path_length),
}
},
BeatmapInfo =

View File

@ -4,7 +4,7 @@
<PackageReference Include="Appveyor.TestLogger" Version="2.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.4.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" />
</ItemGroup>
<PropertyGroup Label="Project">

View File

@ -23,7 +23,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections
/// <summary>
/// The start time of <see cref="Start"/>.
/// </summary>
public readonly Bindable<double> StartTime = new Bindable<double>();
public readonly Bindable<double> StartTime = new BindableDouble();
/// <summary>
/// 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<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;

View File

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

View File

@ -23,7 +23,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
public Func<OsuAction?> GetInitialHitAction;
private readonly Slider slider;
public readonly Drawable FollowCircle;
private readonly Drawable followCircle;
private readonly Drawable trackingArea;
private readonly DrawableSlider drawableSlider;
public SliderBall(Slider slider, DrawableSlider drawableSlider = null)
@ -38,7 +39,14 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
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,
Anchor = Anchor.Centre,
@ -95,8 +103,10 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
tracking = value;
FollowCircle.ScaleTo(tracking ? 2f : 1, 300, Easing.OutQuint);
FollowCircle.FadeTo(tracking ? 1f : 0, 300, Easing.OutQuint);
// Tracking area is bigger than the visible followcircle and scales instantly to match osu-stable
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
Time.Current >= slider.StartTime && Time.Current < slider.EndTime &&
// in valid position range
lastScreenSpaceMousePosition.HasValue && FollowCircle.ReceivePositionalInputAt(lastScreenSpaceMousePosition.Value) &&
lastScreenSpaceMousePosition.HasValue && trackingArea.ReceivePositionalInputAt(lastScreenSpaceMousePosition.Value) &&
// valid action
(actions?.Any(isValidTrackingAction) ?? false);
}

View File

@ -57,7 +57,7 @@ namespace osu.Game.Rulesets.Osu.Objects
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
{

View File

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

View File

@ -50,7 +50,7 @@ namespace osu.Game.Rulesets.Osu.UI
{
Add(localCursorContainer = new OsuCursorContainer());
localCursorScale = new Bindable<float>();
localCursorScale = new BindableFloat();
localCursorScale.BindTo(localCursorContainer.CursorScale);
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<double> DimLevel = new Bindable<double>();
public readonly Bindable<double> BlurLevel = new Bindable<double>();
public readonly Bindable<double> DimLevel = new BindableDouble();
public readonly Bindable<double> BlurLevel = new BindableDouble();
public new BeatmapCarousel Carousel => base.Carousel;

View File

@ -2,49 +2,66 @@
// See the LICENCE file in the repository root for full licence text.
using System.Linq;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Screens;
using osu.Game.Configuration;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Catch;
using osu.Game.Rulesets.Mania;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Taiko;
using osu.Game.Screens.Play;
namespace osu.Game.Tests.Visual
namespace osu.Game.Tests.Visual.Gameplay
{
/// <summary>
/// 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"/>.
/// </summary>
public abstract class AllPlayersTestScene : RateAdjustedBeatmapTestScene
public abstract class TestSceneAllRulesetPlayers : RateAdjustedBeatmapTestScene
{
protected Player Player;
[BackgroundDependencyLoader]
private void load(RulesetStore rulesets)
{
foreach (var r in rulesets.AvailableRulesets)
{
Player p = null;
AddStep(r.Name, () => p = loadPlayerFor(r));
AddUntilStep("player loaded", () =>
{
if (p?.IsLoaded == true)
{
p = null;
return true;
}
return false;
});
AddCheckSteps();
}
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;
AddStep($"load {ruleset.Name} player", () => p = loadPlayerFor(ruleset));
AddUntilStep("player loaded", () =>
{
if (p?.IsLoaded == true)
{
p = null;
return true;
}
return false;
});
AddCheckSteps();
}
protected abstract void AddCheckSteps();
private Player loadPlayerFor(RulesetInfo rulesetInfo)

View File

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

View File

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

View File

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

View File

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

View File

@ -10,7 +10,7 @@ using osu.Game.Storyboards;
namespace osu.Game.Tests.Visual.Gameplay
{
public class TestScenePlayerReferenceLeaking : AllPlayersTestScene
public class TestScenePlayerReferenceLeaking : TestSceneAllRulesetPlayers
{
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
{
[Description("Player instantiated with a replay.")]
public class TestSceneReplay : AllPlayersTestScene
public class TestSceneReplay : TestSceneAllRulesetPlayers
{
protected override Player CreatePlayer(Ruleset ruleset)
{

View File

@ -3,12 +3,15 @@
using System;
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.UserInterface;
using osu.Framework.Testing;
using osu.Game.Graphics.UserInterface;
using osu.Game.Online.Chat;
using osu.Game.Overlays;
using osu.Game.Overlays.Chat;
@ -35,8 +38,21 @@ namespace osu.Game.Tests.Visual.Online
private TestChatOverlay chatOverlay;
private ChannelManager channelManager;
private readonly Channel channel1 = new Channel(new User()) { Name = "test really long username" };
private readonly Channel channel2 = new Channel(new User()) { Name = "test2" };
private readonly List<Channel> channels;
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]
public void Setup()
@ -45,7 +61,7 @@ namespace osu.Game.Tests.Visual.Online
{
ChannelManagerContainer container;
Child = container = new ChannelManagerContainer(new List<Channel> { channel1, channel2 })
Child = container = new ChannelManagerContainer(channels)
{
RelativeSizeAxes = Axes.Both,
};
@ -96,6 +112,47 @@ namespace osu.Game.Tests.Visual.Online
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)
{
InputManager.MoveMouseTo(d);

View File

@ -25,7 +25,8 @@ namespace osu.Game.Tests.Visual.Online
typeof(SortTabControl),
typeof(ShowChildrenButton),
typeof(DeletedCommentsCounter),
typeof(VotePill)
typeof(VotePill),
typeof(CommentsPage),
};
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(PaginatedMostPlayedBeatmapContainer),
typeof(DrawableMostPlayedBeatmap),
typeof(DrawableProfileRow)
};
[Cached]

View File

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

View File

@ -5,6 +5,7 @@ using System;
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
@ -12,6 +13,7 @@ using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
using osu.Game.Online.API.Requests;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Overlays;
using osu.Game.Overlays.Profile.Sections;
using osu.Game.Overlays.Profile.Sections.Recent;
@ -28,6 +30,9 @@ namespace osu.Game.Tests.Visual.Online
typeof(MedalIcon)
};
[Cached]
private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Green);
public TestSceneUserProfileRecentSection()
{
Children = new Drawable[]
@ -131,6 +136,22 @@ namespace osu.Game.Tests.Visual.Online
Beatmap = dummyBeatmap,
},
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,
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 NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Testing;
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;
namespace osu.Game.Tests.Visual.SongSelect
@ -180,27 +174,5 @@ namespace osu.Game.Tests.Visual.SongSelect
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
Add(music);
Beatmap.SetDefault();
Dependencies.Cache(config = new OsuConfigManager(LocalStorage));
}
private OsuConfigManager config;
[SetUp]
public virtual void SetUp() => Schedule(() =>
[SetUpSteps]
public override void SetUpSteps()
{
Ruleset.Value = new OsuRuleset().RulesetInfo;
manager?.Delete(manager.GetAllUsableBeatmapSets());
});
base.SetUpSteps();
AddStep("delete all beatmaps", () =>
{
Ruleset.Value = new OsuRuleset().RulesetInfo;
manager?.Delete(manager.GetAllUsableBeatmapSets());
Beatmap.SetDefault();
});
}
[Test]
public void TestSingleFilterOnEnter()
@ -120,9 +125,6 @@ namespace osu.Game.Tests.Visual.SongSelect
AddUntilStep("wait for not current", () => !songSelect.IsCurrentScreen());
AddAssert("ensure selection changed", () => selected != Beatmap.Value);
AddUntilStep("wait for return to song select", () => songSelect.IsCurrentScreen());
AddUntilStep("bindable lease returned", () => !Beatmap.Disabled);
}
[Test]
@ -148,9 +150,6 @@ namespace osu.Game.Tests.Visual.SongSelect
AddUntilStep("wait for not current", () => !songSelect.IsCurrentScreen());
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]
@ -180,9 +179,6 @@ namespace osu.Game.Tests.Visual.SongSelect
AddUntilStep("wait for not current", () => !songSelect.IsCurrentScreen());
AddAssert("ensure selection changed", () => selected != Beatmap.Value);
AddUntilStep("wait for return to song select", () => songSelect.IsCurrentScreen());
AddUntilStep("bindable lease returned", () => !Beatmap.Disabled);
}
[Test]
@ -213,9 +209,6 @@ namespace osu.Game.Tests.Visual.SongSelect
AddUntilStep("wait for not current", () => !songSelect.IsCurrentScreen());
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]

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

View File

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

View File

@ -6,8 +6,6 @@ using osu.Game.Users;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Text.RegularExpressions;
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 HasMessage => !string.IsNullOrEmpty(MessageHtml);
public bool HasMessage => !string.IsNullOrEmpty(Message);
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);
}
}

View File

@ -47,17 +47,18 @@ namespace osu.Game.Online.API.Requests.Responses
[JsonProperty(@"included_comments")]
public List<Comment> IncludedComments { get; set; }
private List<long> userVotes;
[JsonProperty(@"user_votes")]
private List<long> userVotes
public List<long> UserVotes
{
set => value.ForEach(v =>
get => userVotes;
set
{
Comments.ForEach(c =>
{
if (v == c.Id)
c.IsVoted = true;
});
});
userVotes = value;
Comments.ForEach(c => c.IsVoted = value.Contains(c.Id));
}
}
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 transition_duration = 100;
private readonly Channel channel;
public readonly Channel Channel;
private readonly Bindable<bool> joinedBind = new Bindable<bool>();
private readonly OsuSpriteText name;
@ -36,7 +36,7 @@ namespace osu.Game.Overlays.Chat.Selection
private Color4 topicColour;
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
{
@ -50,7 +50,7 @@ namespace osu.Game.Overlays.Chat.Selection
public ChannelListItem(Channel channel)
{
this.channel = channel;
Channel = channel;
RelativeSizeAxes = Axes.X;
AutoSizeAxes = Axes.Y;
@ -148,7 +148,7 @@ namespace osu.Game.Overlays.Chat.Selection
hoverColour = colours.Yellow;
joinedBind.ValueChanged += joined => updateColour(joined.NewValue);
joinedBind.BindTo(channel.Joined);
joinedBind.BindTo(Channel.Joined);
joinedBind.TriggerChange();
FinishTransforms(true);
@ -156,7 +156,7 @@ namespace osu.Game.Overlays.Chat.Selection
protected override bool OnHover(HoverEvent e)
{
if (!channel.Joined.Value)
if (!Channel.Joined.Value)
name.FadeColour(hoverColour, 50, Easing.OutQuint);
return base.OnHover(e);
@ -164,7 +164,7 @@ namespace osu.Game.Overlays.Chat.Selection
protected override void OnHoverLost(HoverLostEvent e)
{
if (!channel.Joined.Value)
if (!Channel.Joined.Value)
name.FadeColour(Color4.White, transition_duration);
}

View File

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

View File

@ -68,6 +68,7 @@ namespace osu.Game.Overlays.Comments
},
new Container
{
Name = @"Footer",
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Children = new Drawable[]
@ -154,6 +155,7 @@ namespace osu.Game.Overlays.Comments
{
currentPage = 1;
deletedCommentsCounter.Count.Value = 0;
moreButton.Show();
moreButton.IsLoading = true;
content.Clear();
}
@ -162,25 +164,10 @@ namespace osu.Game.Overlays.Comments
{
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 }
});
}
}
LoadComponentAsync(page, loaded =>
ShowDeleted = { BindTarget = ShowDeleted }
}, loaded =>
{
content.Add(loaded);
@ -194,10 +181,12 @@ namespace osu.Game.Overlays.Comments
moreButton.Current.Value = response.TopLevelCount - loadedTopLevelComments;
moreButton.IsLoading = false;
}
else
{
moreButton.Hide();
}
commentCounter.Current.Value = response.Total;
moreButton.FadeTo(response.HasMore ? 1 : 0);
}, 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]
private void load(OverlayColourProvider colourProvider)
{
Height = 20;
IdleColour = colourProvider.Background2;
HoverColour = colourProvider.Background1;
ChevronIconColour = colourProvider.Foreground1;

View File

@ -213,7 +213,7 @@ namespace osu.Game.Overlays.Comments
if (comment.HasMessage)
{
var formattedSource = MessageFormatter.FormatText(comment.GetMessage);
var formattedSource = MessageFormatter.FormatText(comment.Message);
message.AddLinks(formattedSource.Text, formattedSource.Links);
}
@ -343,7 +343,7 @@ namespace osu.Game.Overlays.Comments
if (parentComment == null)
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)
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)
{

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.
using System;
@ -82,7 +82,7 @@ namespace osu.Game.Overlays.Profile.Header
else
{
topLinkContainer.AddText("Joined ");
topLinkContainer.AddText(new DrawableDate(user.JoinDate), embolden);
topLinkContainer.AddText(new DrawableDate(user.JoinDate, italic: false), embolden);
}
addSpacer(topLinkContainer);
@ -95,7 +95,7 @@ namespace osu.Game.Overlays.Profile.Header
else if (user.LastVisit.HasValue)
{
topLinkContainer.AddText("Last seen ");
topLinkContainer.AddText(new DrawableDate(user.LastVisit.Value), embolden);
topLinkContainer.AddText(new DrawableDate(user.LastVisit.Value, italic: false), embolden);
addSpacer(topLinkContainer);
}
@ -111,34 +111,42 @@ namespace osu.Game.Overlays.Profile.Header
topLinkContainer.AddText("Contributed ");
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;
websiteWithoutProtcol = websiteWithoutProtcol.TrimEnd('/');
websiteWithoutProtocol = uri.Host + uri.PathAndQuery + uri.Fragment;
websiteWithoutProtocol = websiteWithoutProtocol.TrimEnd('/');
}
}
tryAddInfo(FontAwesome.Solid.MapMarker, user.Location);
tryAddInfo(OsuIcon.Heart, user.Interests);
tryAddInfo(FontAwesome.Solid.Suitcase, user.Occupation);
bottomLinkContainer.NewLine();
bool anyInfoAdded = false;
anyInfoAdded |= tryAddInfo(FontAwesome.Solid.MapMarker, user.Location);
anyInfoAdded |= tryAddInfo(OsuIcon.Heart, user.Interests);
anyInfoAdded |= tryAddInfo(FontAwesome.Solid.Suitcase, user.Occupation);
if (anyInfoAdded)
bottomLinkContainer.NewLine();
if (!string.IsNullOrEmpty(user.Twitter))
tryAddInfo(FontAwesome.Brands.Twitter, "@" + user.Twitter, $@"https://twitter.com/{user.Twitter}");
tryAddInfo(FontAwesome.Brands.Discord, user.Discord);
tryAddInfo(FontAwesome.Brands.Skype, user.Skype, @"skype:" + user.Skype + @"?chat");
tryAddInfo(FontAwesome.Brands.Lastfm, user.Lastfm, $@"https://last.fm/users/{user.Lastfm}");
tryAddInfo(FontAwesome.Solid.Link, websiteWithoutProtcol, user.Website);
anyInfoAdded |= tryAddInfo(FontAwesome.Brands.Twitter, "@" + user.Twitter, $@"https://twitter.com/{user.Twitter}");
anyInfoAdded |= tryAddInfo(FontAwesome.Brands.Discord, user.Discord);
anyInfoAdded |= tryAddInfo(FontAwesome.Brands.Skype, user.Skype, @"skype:" + user.Skype + @"?chat");
anyInfoAdded |= tryAddInfo(FontAwesome.Brands.Lastfm, user.Lastfm, $@"https://last.fm/users/{user.Lastfm}");
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 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.
content = content.Replace("\n", " ");
@ -155,6 +163,7 @@ namespace osu.Game.Overlays.Profile.Header
bottomLinkContainer.AddText(" " + content, embolden);
addSpacer(bottomLinkContainer);
return true;
}
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]
private void load(OsuColour colours)
private void load(OverlayColourProvider colourProvider)
{
InternalChildren = new Drawable[]
{
@ -42,7 +42,7 @@ namespace osu.Game.Overlays.Profile.Header.Components
RelativeSizeAxes = Axes.Both,
BackgroundColour = Color4.Black,
Direction = BarDirection.LeftToRight,
AccentColour = colours.Yellow
AccentColour = colourProvider.Highlight1
}
},
levelProgressText = new OsuSpriteText

View File

@ -2,12 +2,11 @@
// See the LICENCE file in the repository root for full licence text.
using System.Collections.Generic;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
using osuTK.Graphics;
namespace osu.Game.Overlays.Profile.Header.Components
{
@ -24,9 +23,6 @@ namespace osu.Game.Overlays.Profile.Header.Components
{
AutoSizeAxes = Axes.X;
IdleColour = Color4.Black;
HoverColour = OsuColour.Gray(0.1f);
base.Content.Add(new CircularContainer
{
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]
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;
}
@ -270,7 +270,9 @@ namespace osu.Game.Overlays.Profile.Header.Components
[BackgroundDependencyLoader]
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)

View File

@ -10,7 +10,7 @@ using osu.Game.Graphics.Containers;
namespace osu.Game.Overlays.Profile.Sections
{
/// <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>
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]
private void load(OverlayColourProvider colourProvider)
{
background.Colour = idleColour = colourProvider.Background4;
hoverColour = colourProvider.Background3;
background.Colour = idleColour = colourProvider.Background3;
hoverColour = colourProvider.Background2;
}
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.
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Linq;
using JetBrains.Annotations;
using osu.Framework.Allocation;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
@ -16,14 +16,16 @@ using osu.Game.Online.Leaderboards;
using osu.Game.Rulesets.UI;
using osu.Game.Scoring;
using osuTK;
using osuTK.Graphics;
namespace osu.Game.Overlays.Profile.Sections.Ranks
{
public class DrawableProfileScore : CompositeDrawable
{
private const int height = 40;
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;
@ -38,7 +40,7 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks
Score = score;
RelativeSizeAxes = Axes.X;
Height = 40;
Height = height;
}
[BackgroundDependencyLoader]
@ -51,7 +53,7 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks
new Container
{
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[]
{
new FillFlowContainer
@ -142,20 +144,26 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks
{
new Box
{
RelativeSizeAxes = Axes.Both,
Size = new Vector2(1, 0.5f),
Colour = Color4.Black.Opacity(0.5f),
Shear = new Vector2(-0.45f, 0),
Anchor = Anchor.TopRight,
Origin = Anchor.TopRight,
RelativeSizeAxes = Axes.Y,
Width = performance_background_width,
Height = 0.5f,
Colour = colourProvider.Background4,
Shear = new Vector2(-performance_background_shear, 0),
EdgeSmoothness = new Vector2(2, 0),
},
new Box
{
RelativeSizeAxes = Axes.Both,
Anchor = Anchor.TopRight,
Origin = Anchor.TopRight,
RelativeSizeAxes = Axes.Y,
RelativePositionAxes = Axes.Y,
Size = new Vector2(1, -0.5f),
Width = performance_background_width,
Height = -0.5f,
Position = new Vector2(0, 1),
Colour = Color4.Black.Opacity(0.5f),
Shear = new Vector2(0.45f, 0),
Colour = colourProvider.Background4,
Shear = new Vector2(performance_background_shear, 0),
EdgeSmoothness = new Vector2(2, 0),
},
createDrawablePerformance().With(d =>

View File

@ -1,9 +1,11 @@
// 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.Linq;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
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.Chat;
using osu.Game.Online.Leaderboards;
using osu.Game.Rulesets;
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;
@ -28,139 +37,191 @@ namespace osu.Game.Overlays.Profile.Sections.Recent
}
[BackgroundDependencyLoader]
private void load(IAPIProvider api)
private void load(OverlayColourProvider colourProvider)
{
this.api = api;
LeftFlowContainer.Padding = new MarginPadding { Left = 10, Right = 160 };
LeftFlowContainer.Add(content = new LinkFlowContainer
RelativeSizeAxes = Axes.X;
AutoSizeAxes = Axes.Y;
AddInternal(new GridContainer
{
AutoSizeAxes = Axes.Y,
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,
RelativeSizeAxes = Axes.X,
},
new DrawableDate(activity.CreatedAt)
{
Anchor = Anchor.CentreRight,
Origin = Anchor.CentreRight,
Colour = colourProvider.Foreground1,
Font = OsuFont.GetFont(size: font_size),
}
}
}
});
RightFlowContainer.Add(new DrawableDate(activity.CreatedAt)
{
Font = OsuFont.GetFont(size: 13),
Colour = OsuColour.Gray(0xAA),
Anchor = Anchor.TopRight,
Origin = Anchor.TopRight,
});
var formatted = createMessage();
content.AddLinks(formatted.Text, formatted.Links);
createMessage();
}
protected override Drawable CreateLeftVisual()
private Drawable createIcon()
{
switch (activity.Type)
{
case RecentActivityType.Rank:
return new UpdateableRank(activity.ScoreRank)
{
RelativeSizeAxes = Axes.Y,
Width = 60,
RelativeSizeAxes = Axes.X,
Height = 11,
FillMode = FillMode.Fit,
Margin = new MarginPadding { Top = 2 }
};
case RecentActivityType.Achievement:
return new DelayedLoadWrapper(new MedalIcon(activity.Achievement.Slug)
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.Both,
FillMode = FillMode.Fit,
})
{
RelativeSizeAxes = Axes.Y,
Width = 60,
RelativeSizeAxes = Axes.X,
Width = 0.5f,
Height = 18
};
default:
return new Container
{
RelativeSizeAxes = Axes.Y,
Width = 60,
FillMode = FillMode.Fit,
};
return Empty();
}
}
private string toAbsoluteUrl(string url) => $"{api.Endpoint}{url}";
private MessageFormatter.MessageFormatterResult createMessage()
private void 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)
{
case RecentActivityType.Achievement:
message = $"{userLinkTemplate()} unlocked the {activity.Achievement.Name} medal!";
addUserLink();
addText($" unlocked the \"{activity.Achievement.Name}\" medal!");
break;
case RecentActivityType.BeatmapPlaycount:
message = $"{beatmapLinkTemplate()} has been played {activity.Count} times!";
addBeatmapLink();
addText($" has been played {activity.Count} times!");
break;
case RecentActivityType.BeatmapsetApprove:
message = $"{beatmapsetLinkTemplate()} has been {activity.Approval.ToString().ToLowerInvariant()}!";
addBeatmapsetLink();
addText($" has been {activity.Approval.ToString().ToLowerInvariant()}!");
break;
case RecentActivityType.BeatmapsetDelete:
message = $"{beatmapsetLinkTemplate()} has been deleted.";
addBeatmapsetLink();
addText(" has been deleted.");
break;
case RecentActivityType.BeatmapsetRevive:
message = $"{beatmapsetLinkTemplate()} has been revived from eternal slumber by {userLinkTemplate()}.";
addBeatmapsetLink();
addText(" has been revived from eternal slumber by ");
addUserLink();
break;
case RecentActivityType.BeatmapsetUpdate:
message = $"{userLinkTemplate()} has updated the beatmap {beatmapsetLinkTemplate()}!";
addUserLink();
addText(" has updated the beatmap ");
addBeatmapsetLink();
break;
case RecentActivityType.BeatmapsetUpload:
message = $"{userLinkTemplate()} has submitted a new beatmap {beatmapsetLinkTemplate()}!";
addUserLink();
addText(" has submitted a new beatmap ");
addBeatmapsetLink();
break;
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)
message = string.Empty;
break;
case RecentActivityType.Rank:
message = $"{userLinkTemplate()} achieved rank #{activity.Rank} on {beatmapLinkTemplate()} ({activity.Mode}!)";
addUserLink();
addText($" achieved rank #{activity.Rank} on ");
addBeatmapLink();
addText($" ({getRulesetName()})");
break;
case RecentActivityType.RankLost:
message = $"{userLinkTemplate()} has lost first place on {beatmapLinkTemplate()} ({activity.Mode}!)";
addUserLink();
addText(" has lost first place on ");
addBeatmapLink();
addText($" ({getRulesetName()})");
break;
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;
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;
case RecentActivityType.UserSupportGift:
message = $"{userLinkTemplate()} has received the gift of osu!supporter!";
addUserLink();
addText(" has received the gift of osu!supporter!");
break;
case RecentActivityType.UsernameChange:
message = $"{activity.User?.PreviousUsername} has changed their username to {userLinkTemplate()}!";
break;
default:
message = string.Empty;
addText($"{activity.User?.PreviousUsername} has changed their username to ");
addUserLink();
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
{
Height = 40,
Width = 40,
RelativeSizeAxes = Axes.Both,
Anchor = 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;
using System.Collections.Generic;
using osuTK;
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)
: base(user, header, missing)
{
ItemsPerPage = 5;
ItemsPerPage = 10;
ItemsContainer.Spacing = new Vector2(0, 8);
}
protected override APIRequest<List<APIRecentActivity>> CreateRequest() =>

View File

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

View File

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

View File

@ -154,7 +154,7 @@ namespace osu.Game.Rulesets.Edit
{
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)
{

View File

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

View File

@ -220,9 +220,11 @@ namespace osu.Game.Scoring.Legacy
float lastTime = 0;
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)
continue;
@ -234,8 +236,14 @@ namespace osu.Game.Scoring.Legacy
}
var diff = Parsing.ParseFloat(split[0]);
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
// but for now we'll achieve equal playback to stable by skipping negative frames
if (diff < 0)

View File

@ -36,7 +36,7 @@ namespace osu.Game.Screens.Backgrounds
/// <summary>
/// The amount of blur to be applied in addition to user-specified blur.
/// </summary>
public readonly Bindable<float> BlurAmount = new Bindable<float>();
public readonly Bindable<float> BlurAmount = new BindableFloat();
internal readonly IBindable<bool> IsBreakTime = new Bindable<bool>();
@ -119,7 +119,7 @@ namespace osu.Game.Screens.Backgrounds
/// <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"/>
/// </remarks>
public readonly Bindable<float> BlurAmount = new Bindable<float>();
public readonly Bindable<float> BlurAmount = new BindableFloat();
public Background Background
{

View File

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

View File

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

View File

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

View File

@ -3,6 +3,7 @@
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Testing;
using osu.Game.Screens;
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.Push(screen);
return false;
});
}
}
}

View File

@ -47,7 +47,7 @@ namespace osu.Game.Tests.Visual
public readonly Bindable<ScrollingDirection> Direction = new Bindable<ScrollingDirection>();
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;
public readonly TestScrollAlgorithm Algorithm = new TestScrollAlgorithm();

View File

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

View File

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