1
0
mirror of https://github.com/ppy/osu.git synced 2024-09-22 04:07:25 +08:00

Merge branch 'master' into safe-screen-lease

This commit is contained in:
Dan Balasescu 2020-02-03 18:18:13 +09:00 committed by GitHub
commit c6e7603e5e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
66 changed files with 1223 additions and 546 deletions

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 + 250 },
new OsuReplayFrame { Position = new Vector2(slider_path_length, OsuHitObject.OBJECT_RADIUS * 1.199f), Actions = { OsuAction.LeftButton }, Time = time_slider_end },
});
AddAssert("Tracking kept", assertGreatJudge);
}
/// <summary>
/// Scenario:
/// - Press a key on the slider head
/// - While holding the key, move cursor just outside the tracking area
/// - Keep the cursor just outside the tracking area until the slider ends
/// Expected Result:
/// A passing test case will have the slider drop the tracking on frame 2.
/// </summary>
[Test]
public void TestTrackingAreaOutsideEdge()
{
performTest(new List<ReplayFrame>
{
new OsuReplayFrame { Position = new Vector2(0, 0), Actions = { OsuAction.LeftButton }, Time = time_slider_start },
new OsuReplayFrame { Position = new Vector2(0, OsuHitObject.OBJECT_RADIUS * 1.21f), Actions = { OsuAction.LeftButton }, Time = time_slider_start + 250 },
new OsuReplayFrame { Position = new Vector2(slider_path_length, OsuHitObject.OBJECT_RADIUS * 1.201f), Actions = { OsuAction.LeftButton }, Time = time_slider_end },
});
AddAssert("Tracking dropped", assertMidSliderJudgementFail);
}
private bool assertGreatJudge() => judgementResults.Last().Type == HitResult.Great;
private bool 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

@ -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,7 @@ 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 DrawableSlider drawableSlider;
public SliderBall(Slider slider, DrawableSlider drawableSlider = null)
@ -38,7 +38,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
Children = new[]
{
FollowCircle = new FollowCircleContainer
followCircle = new FollowCircleContainer
{
Origin = Anchor.Centre,
Anchor = Anchor.Centre,
@ -95,8 +95,8 @@ 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);
followCircle.ScaleTo(tracking ? 2.4f : 1f, 300, Easing.OutQuint);
followCircle.FadeTo(tracking ? 1f : 0, 300, Easing.OutQuint);
}
}
@ -149,7 +149,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
// in valid time range
Time.Current >= slider.StartTime && Time.Current < slider.EndTime &&
// in valid position range
lastScreenSpaceMousePosition.HasValue && FollowCircle.ReceivePositionalInputAt(lastScreenSpaceMousePosition.Value) &&
lastScreenSpaceMousePosition.HasValue && followCircle.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

@ -1,6 +1,8 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Collections.Generic;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Configuration;
@ -76,6 +78,13 @@ namespace osu.Game.Tests.Visual.Navigation
ConfirmAtMainMenu();
}
protected void PushAndConfirm(Func<Screen> newScreen)
{
Screen screen = null;
AddStep("Push new screen", () => Game.ScreenStack.Push(screen = newScreen()));
AddUntilStep("Wait for new screen", () => Game.ScreenStack.CurrentScreen == screen && screen.IsLoaded);
}
protected void ConfirmAtMainMenu() => AddUntilStep("Wait for main menu", () => Game.ScreenStack.CurrentScreen is MainMenu menu && menu.IsLoaded);
public class TestOsuGame : OsuGame
@ -96,6 +105,8 @@ namespace osu.Game.Tests.Visual.Navigation
protected override Loader CreateLoader() => new TestLoader();
public new void PerformFromScreen(Action<IScreen> action, IEnumerable<Type> validScreens = null) => base.PerformFromScreen(action, validScreens);
public TestOsuGame(Storage storage, IAPIProvider api)
{
Storage = storage;

View File

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

View File

@ -1,13 +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;
using System.Linq;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Audio.Track;
using osu.Framework.Graphics.Containers;
using osu.Framework.Screens;
using osu.Game.Beatmaps;
using osu.Game.Overlays;
using osu.Game.Overlays.Mods;
@ -32,7 +30,7 @@ namespace osu.Game.Tests.Visual.Navigation
{
TestSongSelect songSelect = null;
pushAndConfirm(() => songSelect = new TestSongSelect());
PushAndConfirm(() => songSelect = new TestSongSelect());
AddStep("Show mods overlay", () => songSelect.ModSelectOverlay.Show());
AddAssert("Overlay was shown", () => songSelect.ModSelectOverlay.State.Value == Visibility.Visible);
pushEscape();
@ -49,7 +47,7 @@ namespace osu.Game.Tests.Visual.Navigation
WorkingBeatmap beatmap() => Game.Beatmap.Value;
Track track() => beatmap().Track;
pushAndConfirm(() => new TestSongSelect());
PushAndConfirm(() => new TestSongSelect());
AddStep("import beatmap", () => ImportBeatmapTest.LoadOszIntoOsu(Game, virtualTrack: true).Wait());
@ -77,7 +75,7 @@ namespace osu.Game.Tests.Visual.Navigation
{
TestSongSelect songSelect = null;
pushAndConfirm(() => songSelect = new TestSongSelect());
PushAndConfirm(() => songSelect = new TestSongSelect());
AddStep("Show mods overlay", () => songSelect.ModSelectOverlay.Show());
AddAssert("Overlay was shown", () => songSelect.ModSelectOverlay.State.Value == Visibility.Visible);
AddStep("Move mouse to backButton", () => InputManager.MoveMouseTo(backButtonPosition));
@ -93,14 +91,14 @@ namespace osu.Game.Tests.Visual.Navigation
[Test]
public void TestExitMultiWithEscape()
{
pushAndConfirm(() => new Screens.Multi.Multiplayer());
PushAndConfirm(() => new Screens.Multi.Multiplayer());
exitViaEscapeAndConfirm();
}
[Test]
public void TestExitMultiWithBackButton()
{
pushAndConfirm(() => new Screens.Multi.Multiplayer());
PushAndConfirm(() => new Screens.Multi.Multiplayer());
exitViaBackButtonAndConfirm();
}
@ -116,13 +114,6 @@ namespace osu.Game.Tests.Visual.Navigation
AddAssert("Options overlay was closed", () => Game.Settings.State.Value == Visibility.Hidden);
}
private void pushAndConfirm(Func<Screen> newScreen)
{
Screen screen = null;
AddStep("Push new screen", () => Game.ScreenStack.Push(screen = newScreen()));
AddUntilStep("Wait for new screen", () => Game.ScreenStack.CurrentScreen == screen && screen.IsLoaded);
}
private void pushEscape() =>
AddStep("Press escape", () => pressAndRelease(Key.Escape));

View File

@ -38,9 +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", Topic = "Topic for channel 1" };
private readonly Channel channel2 = new Channel(new User()) { Name = "test2", Topic = "Topic for channel 2" };
private readonly Channel channel3 = new Channel(new User()) { Name = "channel with no topic" };
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()
@ -49,7 +61,7 @@ namespace osu.Game.Tests.Visual.Online
{
ChannelManagerContainer container;
Child = container = new ChannelManagerContainer(new List<Channel> { channel1, channel2, channel3 })
Child = container = new ChannelManagerContainer(channels)
{
RelativeSizeAxes = Axes.Both,
};
@ -103,7 +115,7 @@ namespace osu.Game.Tests.Visual.Online
[Test]
public void TestSearchInSelector()
{
AddStep("search for 'test2'", () => chatOverlay.ChildrenOfType<SearchTextBox>().First().Text = "test2");
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);
@ -111,6 +123,36 @@ namespace osu.Game.Tests.Visual.Online
});
}
[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

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

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

@ -23,6 +23,7 @@ using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Osu.Mods;
using osu.Game.Rulesets.Taiko;
using osu.Game.Screens.Play;
using osu.Game.Screens.Select;
using osu.Game.Screens.Select.Carousel;
using osu.Game.Screens.Select.Filter;
@ -77,7 +78,6 @@ namespace osu.Game.Tests.Visual.SongSelect
private OsuConfigManager config;
[SetUpSteps]
public override void SetUpSteps()
{
base.SetUpSteps();
@ -426,6 +426,31 @@ namespace osu.Game.Tests.Visual.SongSelect
AddAssert("start not requested", () => !startRequested);
}
[Test]
public void TestAutoplayViaCtrlEnter()
{
addRulesetImportStep(0);
createSongSelect();
AddStep("press ctrl+enter", () =>
{
InputManager.PressKey(Key.ControlLeft);
InputManager.PressKey(Key.Enter);
InputManager.ReleaseKey(Key.ControlLeft);
InputManager.ReleaseKey(Key.Enter);
});
AddUntilStep("wait for player", () => Stack.CurrentScreen is PlayerLoader);
AddAssert("autoplay enabled", () => songSelect.Mods.Value.FirstOrDefault() is ModAutoplay);
AddUntilStep("wait for return to ss", () => songSelect.IsCurrentScreen());
AddAssert("mod disabled", () => songSelect.Mods.Value.Count == 0);
}
[Test]
public void TestHideSetSelectsCorrectBeatmap()
{

View File

@ -63,6 +63,7 @@ namespace osu.Game.Tests.Visual.UserInterface
RelativeSizeAxes = Axes.X,
Origin = Anchor.BottomCentre,
Anchor = Anchor.BottomCentre,
SelectedMods = { BindTarget = SelectedMods }
},
modDisplay = new ModDisplay

View File

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

View File

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

View File

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

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

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

@ -47,7 +47,7 @@ namespace osu.Game.Overlays.Mods
protected readonly Container ModSettingsContainer;
protected readonly Bindable<IReadOnlyList<Mod>> SelectedMods = new Bindable<IReadOnlyList<Mod>>(Array.Empty<Mod>());
public readonly Bindable<IReadOnlyList<Mod>> SelectedMods = new Bindable<IReadOnlyList<Mod>>(Array.Empty<Mod>());
private Bindable<Dictionary<ModType, IReadOnlyList<Mod>>> availableMods;
@ -321,14 +321,13 @@ namespace osu.Game.Overlays.Mods
}
[BackgroundDependencyLoader(true)]
private void load(OsuColour colours, AudioManager audio, Bindable<IReadOnlyList<Mod>> selectedMods, OsuGameBase osu)
private void load(OsuColour colours, AudioManager audio, OsuGameBase osu)
{
LowMultiplierColour = colours.Red;
HighMultiplierColour = colours.Green;
UnrankedLabel.Colour = colours.Blue;
availableMods = osu.AvailableMods.GetBoundCopy();
SelectedMods.BindTo(selectedMods);
sampleOn = audio.Samples.Get(@"UI/check-on");
sampleOff = audio.Samples.Get(@"UI/check-off");

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

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

View File

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

View File

@ -1,4 +1,4 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
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

@ -1,63 +1,29 @@
// 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.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.UserInterface;
using osu.Game.Graphics;
using osu.Game.Rulesets;
using osu.Game.Users;
using osuTK;
using osuTK.Graphics;
namespace osu.Game.Overlays.Profile.Header.Components
{
public class ProfileRulesetSelector : RulesetSelector
public class ProfileRulesetSelector : OverlayRulesetSelector
{
private Color4 accentColour = Color4.White;
public readonly Bindable<User> User = new Bindable<User>();
public ProfileRulesetSelector()
{
TabContainer.Masking = false;
TabContainer.Spacing = new Vector2(10, 0);
AutoSizeAxes = Axes.Both;
}
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
accentColour = colours.Seafoam;
foreach (TabItem<RulesetInfo> tabItem in TabContainer)
((ProfileRulesetTabItem)tabItem).AccentColour = accentColour;
}
protected override void LoadComplete()
{
base.LoadComplete();
User.BindValueChanged(u => SetDefaultRuleset(Rulesets.GetRuleset(u.NewValue?.PlayMode ?? "osu")), true);
}
public void SetDefaultRuleset(RulesetInfo ruleset)
{
foreach (TabItem<RulesetInfo> tabItem in TabContainer)
foreach (var tabItem in TabContainer)
((ProfileRulesetTabItem)tabItem).IsDefault = ((ProfileRulesetTabItem)tabItem).Value.ID == ruleset.ID;
}
protected override TabItem<RulesetInfo> CreateTabItem(RulesetInfo value) => new ProfileRulesetTabItem(value)
{
AccentColour = accentColour
};
protected override TabFillFlowContainer CreateTabFlow() => new TabFillFlowContainer
{
Direction = FillDirection.Horizontal,
AutoSizeAxes = Axes.Both,
};
protected override TabItem<RulesetInfo> CreateTabItem(RulesetInfo value) => new ProfileRulesetTabItem(value);
}
}

View File

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

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

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

@ -44,7 +44,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
for (var i = 0; i < beatmap.ControlPointInfo.TimingPoints.Count; i++)
{
var point = beatmap.ControlPointInfo.TimingPoints[i];
var until = beatmap.ControlPointInfo.TimingPoints.Count < i + 1 ? beatmap.ControlPointInfo.TimingPoints[i + 1].Time : working.Value.Track.Length;
var until = i + 1 < beatmap.ControlPointInfo.TimingPoints.Count ? beatmap.ControlPointInfo.TimingPoints[i + 1].Time : working.Value.Track.Length;
int beat = 0;

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

@ -33,6 +33,8 @@ namespace osu.Game.Screens.Select
public override void OnResuming(IScreen last)
{
base.OnResuming(last);
player = null;
if (removeAutoModOnResume)
@ -41,8 +43,6 @@ namespace osu.Game.Screens.Select
ModSelect.DeselectTypes(new[] { autoType }, true);
removeAutoModOnResume = false;
}
base.OnResuming(last);
}
protected override bool OnStart()

View File

@ -75,6 +75,9 @@ namespace osu.Game.Screens.Select
[Resolved(canBeNull: true)]
private NotificationOverlay notificationOverlay { get; set; }
[Resolved]
private Bindable<IReadOnlyList<Mod>> selectedMods { get; set; }
protected override BackgroundScreen CreateBackground() => new BackgroundScreenBeatmap(Beatmap.Value);
protected BeatmapCarousel Carousel { get; private set; }
@ -460,6 +463,8 @@ namespace osu.Game.Screens.Select
this.FadeInFromZero(250);
FilterControl.Activate();
ModSelect.SelectedMods.BindTo(selectedMods);
}
private const double logo_transition = 250;
@ -500,6 +505,12 @@ namespace osu.Game.Screens.Select
public override void OnResuming(IScreen last)
{
base.OnResuming(last);
// required due to https://github.com/ppy/osu-framework/issues/3218
ModSelect.SelectedMods.Disabled = false;
ModSelect.SelectedMods.BindTo(selectedMods);
Carousel.AllowSelection = true;
BeatmapDetails.Leaderboard.RefreshScores();
@ -515,8 +526,6 @@ namespace osu.Game.Screens.Select
music?.Play();
}
base.OnResuming(last);
this.FadeIn(250);
this.ScaleTo(1, 250, Easing.OutSine);
@ -526,6 +535,7 @@ namespace osu.Game.Screens.Select
public override void OnSuspending(IScreen next)
{
ModSelect.SelectedMods.UnbindFrom(selectedMods);
ModSelect.Hide();
BeatmapOptions.Hide();

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

@ -24,7 +24,7 @@
<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.131.0" />
<PackageReference Include="Sentry" Version="1.2.0" />
<PackageReference Include="Sentry" Version="2.0.1" />
<PackageReference Include="SharpCompress" Version="0.24.0" />
<PackageReference Include="NUnit" Version="3.12.0" />
<PackageReference Include="System.ComponentModel.Annotations" Version="4.7.0" />