Merge branch 'master' into hoverable-timestamps

This commit is contained in:
Malox10 2020-02-13 15:21:09 +01:00 committed by GitHub
commit c08398a404
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
97 changed files with 975 additions and 523 deletions

View File

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

View File

@ -2,7 +2,7 @@
<Import Project="..\osu.TestProject.props" /> <Import Project="..\osu.TestProject.props" />
<ItemGroup Label="Package References"> <ItemGroup Label="Package References">
<PackageReference Include="Appveyor.TestLogger" Version="2.0.0" /> <PackageReference Include="Appveyor.TestLogger" Version="2.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.4.0" /> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.5.0" />
<PackageReference Include="NUnit" Version="3.12.0" /> <PackageReference Include="NUnit" Version="3.12.0" />
<PackageReference Include="NUnit3TestAdapter" Version="3.15.1" /> <PackageReference Include="NUnit3TestAdapter" Version="3.15.1" />
<PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" /> <PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" />

View File

@ -10,7 +10,7 @@ namespace osu.Game.Rulesets.Catch.Mods
{ {
public class CatchModDifficultyAdjust : ModDifficultyAdjust public class CatchModDifficultyAdjust : ModDifficultyAdjust
{ {
[SettingSource("Fruit Size", "Override a beatmap's set CS.")] [SettingSource("Circle Size", "Override a beatmap's set CS.", FIRST_SETTING_ORDER - 1)]
public BindableNumber<float> CircleSize { get; } = new BindableFloat public BindableNumber<float> CircleSize { get; } = new BindableFloat
{ {
Precision = 0.1f, Precision = 0.1f,
@ -20,7 +20,7 @@ namespace osu.Game.Rulesets.Catch.Mods
Value = 5, Value = 5,
}; };
[SettingSource("Approach Rate", "Override a beatmap's set AR.")] [SettingSource("Approach Rate", "Override a beatmap's set AR.", LAST_SETTING_ORDER + 1)]
public BindableNumber<float> ApproachRate { get; } = new BindableFloat public BindableNumber<float> ApproachRate { get; } = new BindableFloat
{ {
Precision = 0.1f, Precision = 0.1f,

View File

@ -2,7 +2,7 @@
<Import Project="..\osu.TestProject.props" /> <Import Project="..\osu.TestProject.props" />
<ItemGroup Label="Package References"> <ItemGroup Label="Package References">
<PackageReference Include="Appveyor.TestLogger" Version="2.0.0" /> <PackageReference Include="Appveyor.TestLogger" Version="2.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.4.0" /> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.5.0" />
<PackageReference Include="NUnit" Version="3.12.0" /> <PackageReference Include="NUnit" Version="3.12.0" />
<PackageReference Include="NUnit3TestAdapter" Version="3.15.1" /> <PackageReference Include="NUnit3TestAdapter" Version="3.15.1" />
<PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" /> <PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" />

View File

@ -52,7 +52,7 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints
{ {
base.UpdatePosition(screenSpacePosition); base.UpdatePosition(screenSpacePosition);
if (PlacementBegun) if (PlacementActive)
{ {
var endTime = TimeAt(screenSpacePosition); var endTime = TimeAt(screenSpacePosition);

View File

@ -56,13 +56,13 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints
protected override void OnMouseUp(MouseUpEvent e) protected override void OnMouseUp(MouseUpEvent e)
{ {
EndPlacement(); EndPlacement(true);
base.OnMouseUp(e); base.OnMouseUp(e);
} }
public override void UpdatePosition(Vector2 screenSpacePosition) public override void UpdatePosition(Vector2 screenSpacePosition)
{ {
if (!PlacementBegun) if (!PlacementActive)
Column = ColumnAt(screenSpacePosition); Column = ColumnAt(screenSpacePosition);
if (Column == null) return; if (Column == null) return;

View File

@ -2,7 +2,7 @@
<Import Project="..\osu.TestProject.props" /> <Import Project="..\osu.TestProject.props" />
<ItemGroup Label="Package References"> <ItemGroup Label="Package References">
<PackageReference Include="Appveyor.TestLogger" Version="2.0.0" /> <PackageReference Include="Appveyor.TestLogger" Version="2.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.4.0" /> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.5.0" />
<PackageReference Include="NUnit" Version="3.12.0" /> <PackageReference Include="NUnit" Version="3.12.0" />
<PackageReference Include="NUnit3TestAdapter" Version="3.15.1" /> <PackageReference Include="NUnit3TestAdapter" Version="3.15.1" />
<PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" /> <PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" />

View File

@ -30,12 +30,13 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles
protected override bool OnClick(ClickEvent e) protected override bool OnClick(ClickEvent e)
{ {
EndPlacement(); EndPlacement(true);
return true; return true;
} }
public override void UpdatePosition(Vector2 screenSpacePosition) public override void UpdatePosition(Vector2 screenSpacePosition)
{ {
BeginPlacement();
HitObject.Position = ToLocalSpace(screenSpacePosition); HitObject.Position = ToLocalSpace(screenSpacePosition);
} }
} }

View File

@ -68,6 +68,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
switch (state) switch (state)
{ {
case PlacementState.Initial: case PlacementState.Initial:
BeginPlacement();
HitObject.Position = ToLocalSpace(screenSpacePosition); HitObject.Position = ToLocalSpace(screenSpacePosition);
break; break;
@ -125,14 +126,14 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
private void beginCurve() private void beginCurve()
{ {
BeginPlacement(); BeginPlacement(commitStart: true);
setState(PlacementState.Body); setState(PlacementState.Body);
} }
private void endCurve() private void endCurve()
{ {
updateSlider(); updateSlider();
EndPlacement(); EndPlacement(true);
} }
protected override void Update() protected override void Update()

View File

@ -45,14 +45,14 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners
return false; return false;
HitObject.EndTime = EditorClock.CurrentTime; HitObject.EndTime = EditorClock.CurrentTime;
EndPlacement(); EndPlacement(true);
} }
else else
{ {
if (e.Button != MouseButton.Left) if (e.Button != MouseButton.Left)
return false; return false;
BeginPlacement(); BeginPlacement(commitStart: true);
piece.FadeTo(1f, 150, Easing.OutQuint); piece.FadeTo(1f, 150, Easing.OutQuint);
isPlacingEnd = true; isPlacingEnd = true;

View File

@ -10,7 +10,7 @@ namespace osu.Game.Rulesets.Osu.Mods
{ {
public class OsuModDifficultyAdjust : ModDifficultyAdjust public class OsuModDifficultyAdjust : ModDifficultyAdjust
{ {
[SettingSource("Circle Size", "Override a beatmap's set CS.")] [SettingSource("Circle Size", "Override a beatmap's set CS.", FIRST_SETTING_ORDER - 1)]
public BindableNumber<float> CircleSize { get; } = new BindableFloat public BindableNumber<float> CircleSize { get; } = new BindableFloat
{ {
Precision = 0.1f, Precision = 0.1f,
@ -20,7 +20,7 @@ namespace osu.Game.Rulesets.Osu.Mods
Value = 5, Value = 5,
}; };
[SettingSource("Approach Rate", "Override a beatmap's set AR.")] [SettingSource("Approach Rate", "Override a beatmap's set AR.", LAST_SETTING_ORDER + 1)]
public BindableNumber<float> ApproachRate { get; } = new BindableFloat public BindableNumber<float> ApproachRate { get; } = new BindableFloat
{ {
Precision = 0.1f, Precision = 0.1f,

View File

@ -2,7 +2,7 @@
<Import Project="..\osu.TestProject.props" /> <Import Project="..\osu.TestProject.props" />
<ItemGroup Label="Package References"> <ItemGroup Label="Package References">
<PackageReference Include="Appveyor.TestLogger" Version="2.0.0" /> <PackageReference Include="Appveyor.TestLogger" Version="2.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.4.0" /> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.5.0" />
<PackageReference Include="NUnit" Version="3.12.0" /> <PackageReference Include="NUnit" Version="3.12.0" />
<PackageReference Include="NUnit3TestAdapter" Version="3.15.1" /> <PackageReference Include="NUnit3TestAdapter" Version="3.15.1" />
<PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" /> <PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" />

View File

@ -121,11 +121,14 @@ namespace osu.Game.Tests.Visual.Multiplayer
{ {
room.Playlist.Add(new PlaylistItem room.Playlist.Add(new PlaylistItem
{ {
Ruleset = ruleset, Ruleset = { Value = ruleset },
Beatmap = new BeatmapInfo Beatmap =
{
Value = new BeatmapInfo
{ {
Metadata = new BeatmapMetadata() Metadata = new BeatmapMetadata()
} }
}
}); });
} }

View File

@ -32,11 +32,11 @@ namespace osu.Game.Tests.Visual.Multiplayer
Origin = Anchor.Centre, Origin = Anchor.Centre,
}); });
Room.Playlist.Add(new PlaylistItem { Beatmap = new BeatmapInfo { OnlineBeatmapID = 1763072 } }); Room.Playlist.Add(new PlaylistItem { Beatmap = { Value = new BeatmapInfo { OnlineBeatmapID = 1763072 } } });
Room.Playlist.Add(new PlaylistItem { Beatmap = new BeatmapInfo { OnlineBeatmapID = 2101557 } }); Room.Playlist.Add(new PlaylistItem { Beatmap = { Value = new BeatmapInfo { OnlineBeatmapID = 2101557 } } });
Room.Playlist.Add(new PlaylistItem { Beatmap = new BeatmapInfo { OnlineBeatmapID = 1973466 } }); Room.Playlist.Add(new PlaylistItem { Beatmap = { Value = new BeatmapInfo { OnlineBeatmapID = 1973466 } } });
Room.Playlist.Add(new PlaylistItem { Beatmap = new BeatmapInfo { OnlineBeatmapID = 2109801 } }); Room.Playlist.Add(new PlaylistItem { Beatmap = { Value = new BeatmapInfo { OnlineBeatmapID = 2109801 } } });
Room.Playlist.Add(new PlaylistItem { Beatmap = new BeatmapInfo { OnlineBeatmapID = 1922035 } }); Room.Playlist.Add(new PlaylistItem { Beatmap = { Value = new BeatmapInfo { OnlineBeatmapID = 1922035 } } });
} }
protected override void LoadComplete() protected override void LoadComplete()

View File

@ -23,7 +23,9 @@ namespace osu.Game.Tests.Visual.Multiplayer
{ {
Room.Playlist.Add(new PlaylistItem Room.Playlist.Add(new PlaylistItem
{ {
Beatmap = new BeatmapInfo Beatmap =
{
Value = new BeatmapInfo
{ {
Metadata = new BeatmapMetadata Metadata = new BeatmapMetadata
{ {
@ -33,6 +35,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
}, },
Version = "Version", Version = "Version",
Ruleset = new OsuRuleset().RulesetInfo Ruleset = new OsuRuleset().RulesetInfo
}
}, },
RequiredMods = RequiredMods =
{ {

View File

@ -37,7 +37,9 @@ namespace osu.Game.Tests.Visual.Multiplayer
Room.Playlist.Clear(); Room.Playlist.Clear();
Room.Playlist.Add(new PlaylistItem Room.Playlist.Add(new PlaylistItem
{ {
Beatmap = new BeatmapInfo Beatmap =
{
Value = new BeatmapInfo
{ {
StarDifficulty = 2.4, StarDifficulty = 2.4,
Ruleset = rulesets.GetRuleset(0), Ruleset = rulesets.GetRuleset(0),
@ -48,6 +50,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
AuthorString = @"osu!lazer", AuthorString = @"osu!lazer",
}, },
} }
}
}); });
}); });
@ -60,7 +63,9 @@ namespace osu.Game.Tests.Visual.Multiplayer
Room.Playlist.Clear(); Room.Playlist.Clear();
Room.Playlist.Add(new PlaylistItem Room.Playlist.Add(new PlaylistItem
{ {
Beatmap = new BeatmapInfo Beatmap =
{
Value = new BeatmapInfo
{ {
StarDifficulty = 4.2, StarDifficulty = 4.2,
Ruleset = rulesets.GetRuleset(3), Ruleset = rulesets.GetRuleset(3),
@ -71,6 +76,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
AuthorString = @"Someone", AuthorString = @"Someone",
}, },
} }
}
}); });
}); });
} }

View File

@ -16,7 +16,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
Add(new Participants { RelativeSizeAxes = Axes.Both }); Add(new Participants { RelativeSizeAxes = Axes.Both });
AddStep(@"set max to null", () => Room.MaxParticipants.Value = null); AddStep(@"set max to null", () => Room.MaxParticipants.Value = null);
AddStep(@"set users", () => Room.Participants.Value = new[] AddStep(@"set users", () => Room.Participants.AddRange(new[]
{ {
new User new User
{ {
@ -42,10 +42,10 @@ namespace osu.Game.Tests.Visual.Multiplayer
CoverUrl = @"https://assets.ppy.sh/user-profile-covers/5287410/5cfeaa9dd41cbce038ecdc9d781396ed4b0108089170bf7f50492ef8eadeb368.jpeg", CoverUrl = @"https://assets.ppy.sh/user-profile-covers/5287410/5cfeaa9dd41cbce038ecdc9d781396ed4b0108089170bf7f50492ef8eadeb368.jpeg",
IsSupporter = true, IsSupporter = true,
}, },
}); }));
AddStep(@"set max", () => Room.MaxParticipants.Value = 10); AddStep(@"set max", () => Room.MaxParticipants.Value = 10);
AddStep(@"clear users", () => Room.Participants.Value = System.Array.Empty<User>()); AddStep(@"clear users", () => Room.Participants.Clear());
AddStep(@"set max to null", () => Room.MaxParticipants.Value = null); AddStep(@"set max to null", () => Room.MaxParticipants.Value = null);
} }
} }

View File

@ -56,7 +56,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddStep("set name", () => Room.Name.Value = "Room name"); AddStep("set name", () => Room.Name.Value = "Room name");
AddAssert("button disabled", () => !settings.ApplyButton.Enabled.Value); AddAssert("button disabled", () => !settings.ApplyButton.Enabled.Value);
AddStep("set beatmap", () => Room.Playlist.Add(new PlaylistItem { Beatmap = CreateBeatmap(Ruleset.Value).BeatmapInfo })); AddStep("set beatmap", () => Room.Playlist.Add(new PlaylistItem { Beatmap = { Value = CreateBeatmap(Ruleset.Value).BeatmapInfo } }));
AddAssert("button enabled", () => settings.ApplyButton.Enabled.Value); AddAssert("button enabled", () => settings.ApplyButton.Enabled.Value);
AddStep("clear name", () => Room.Name.Value = ""); AddStep("clear name", () => Room.Name.Value = "");

View File

@ -5,9 +5,11 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Utils; using osu.Framework.Utils;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Overlays;
using osu.Game.Overlays.BeatmapSet; using osu.Game.Overlays.BeatmapSet;
using osu.Game.Screens.Select.Details; using osu.Game.Screens.Select.Details;
@ -22,6 +24,9 @@ namespace osu.Game.Tests.Visual.Online
private RatingsExposingDetails details; private RatingsExposingDetails details;
[Cached]
private OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Blue);
[SetUp] [SetUp]
public void Setup() => Schedule(() => public void Setup() => Schedule(() =>
{ {
@ -55,8 +60,12 @@ namespace osu.Game.Tests.Visual.Online
{ {
Fails = Enumerable.Range(1, 100).Select(_ => RNG.Next(10)).ToArray(), Fails = Enumerable.Range(1, 100).Select(_ => RNG.Next(10)).ToArray(),
Retries = Enumerable.Range(-2, 100).Select(_ => RNG.Next(10)).ToArray(), Retries = Enumerable.Range(-2, 100).Select(_ => RNG.Next(10)).ToArray(),
},
} }
} },
OnlineInfo = new BeatmapSetOnlineInfo
{
Status = BeatmapSetOnlineStatus.Ranked
} }
}; };
} }

View File

@ -5,11 +5,13 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Shapes;
using osu.Framework.Utils; using osu.Framework.Utils;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Overlays;
using osu.Game.Overlays.BeatmapSet; using osu.Game.Overlays.BeatmapSet;
using osu.Game.Screens.Select.Details; using osu.Game.Screens.Select.Details;
using osuTK; using osuTK;
@ -26,6 +28,9 @@ namespace osu.Game.Tests.Visual.Online
private GraphExposingSuccessRate successRate; private GraphExposingSuccessRate successRate;
[Cached]
private OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Blue);
[SetUp] [SetUp]
public void Setup() => Schedule(() => public void Setup() => Schedule(() =>
{ {

View File

@ -7,11 +7,16 @@ using System.Collections.Generic;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Game.Screens.Select.Leaderboards; using osu.Game.Screens.Select.Leaderboards;
using osu.Framework.Allocation;
using osu.Game.Overlays;
namespace osu.Game.Tests.Visual.Online namespace osu.Game.Tests.Visual.Online
{ {
public class TestSceneLeaderboardScopeSelector : OsuTestScene public class TestSceneLeaderboardScopeSelector : OsuTestScene
{ {
[Cached]
private OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Blue);
public override IReadOnlyList<Type> RequiredTypes => new[] public override IReadOnlyList<Type> RequiredTypes => new[]
{ {
typeof(LeaderboardScopeSelector), typeof(LeaderboardScopeSelector),

View File

@ -34,25 +34,7 @@ namespace osu.Game.Tests.Visual.Online
{ {
Current = { BindTarget = scope }, Current = { BindTarget = scope },
Country = { BindTarget = countryBindable }, Country = { BindTarget = countryBindable },
Ruleset = { BindTarget = ruleset }, Ruleset = { BindTarget = ruleset }
Spotlights = new[]
{
new Spotlight
{
Id = 1,
Text = "Spotlight 1"
},
new Spotlight
{
Id = 2,
Text = "Spotlight 2"
},
new Spotlight
{
Id = 3,
Text = "Spotlight 3"
}
}
}); });
var country = new Country var country = new Country

View File

@ -35,6 +35,12 @@ namespace osu.Game.Tests.Visual.Online
Add(selector = new SpotlightSelector()); Add(selector = new SpotlightSelector());
} }
[Test]
public void TestVisibility()
{
AddStep("Toggle Visibility", selector.ToggleVisibility);
}
[Test] [Test]
public void TestLocalSpotlights() public void TestLocalSpotlights()
{ {

View File

@ -0,0 +1,55 @@
// 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.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Overlays;
using osu.Game.Overlays.Rankings;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Catch;
using osu.Game.Rulesets.Mania;
using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Taiko;
namespace osu.Game.Tests.Visual.Online
{
public class TestSceneSpotlightsLayout : OsuTestScene
{
public override IReadOnlyList<Type> RequiredTypes => new[]
{
typeof(SpotlightsLayout),
typeof(SpotlightSelector),
};
protected override bool UseOnlineAPI => true;
[Cached]
private readonly OverlayColourProvider overlayColour = new OverlayColourProvider(OverlayColourScheme.Green);
public TestSceneSpotlightsLayout()
{
var ruleset = new Bindable<RulesetInfo>(new OsuRuleset().RulesetInfo);
Add(new BasicScrollContainer
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.Both,
Width = 0.8f,
Child = new SpotlightsLayout
{
Ruleset = { BindTarget = ruleset }
}
});
AddStep("Osu ruleset", () => ruleset.Value = new OsuRuleset().RulesetInfo);
AddStep("Mania ruleset", () => ruleset.Value = new ManiaRuleset().RulesetInfo);
AddStep("Taiko ruleset", () => ruleset.Value = new TaikoRuleset().RulesetInfo);
AddStep("Catch ruleset", () => ruleset.Value = new CatchRuleset().RulesetInfo);
}
}
}

View File

@ -17,6 +17,7 @@ using osu.Framework.Screens;
using osu.Framework.Testing; using osu.Framework.Testing;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Configuration; using osu.Game.Configuration;
using osu.Game.Graphics.UserInterface;
using osu.Game.Overlays; using osu.Game.Overlays;
using osu.Game.Rulesets; using osu.Game.Rulesets;
using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Mods;
@ -426,6 +427,44 @@ namespace osu.Game.Tests.Visual.SongSelect
AddAssert("start not requested", () => !startRequested); AddAssert("start not requested", () => !startRequested);
} }
[TestCase(false)]
[TestCase(true)]
public void TestExternalBeatmapChangeWhileFiltered(bool differentRuleset)
{
createSongSelect();
addManyTestMaps();
changeRuleset(0);
AddUntilStep("has selection", () => songSelect.Carousel.SelectedBeatmap != null);
AddStep("set filter text", () => songSelect.FilterControl.ChildrenOfType<SearchTextBox>().First().Text = "nonono");
AddUntilStep("dummy selected", () => Beatmap.Value is DummyWorkingBeatmap);
AddUntilStep("has no selection", () => songSelect.Carousel.SelectedBeatmap == null);
BeatmapInfo target = null;
AddStep("select beatmap externally", () =>
{
target = manager.GetAllUsableBeatmapSets().Where(b => b.Beatmaps.Any(bi => bi.RulesetID == (differentRuleset ? 1 : 0)))
.ElementAt(5).Beatmaps.First();
Beatmap.Value = manager.GetWorkingBeatmap(target);
});
AddUntilStep("has selection", () => songSelect.Carousel.SelectedBeatmap != null);
AddUntilStep("carousel has correct", () => songSelect.Carousel.SelectedBeatmap?.OnlineBeatmapID == target.OnlineBeatmapID);
AddUntilStep("game has correct", () => Beatmap.Value.BeatmapInfo.OnlineBeatmapID == target.OnlineBeatmapID);
AddStep("reset filter text", () => songSelect.FilterControl.ChildrenOfType<SearchTextBox>().First().Text = string.Empty);
AddAssert("game still correct", () => Beatmap.Value?.BeatmapInfo.OnlineBeatmapID == target.OnlineBeatmapID);
AddAssert("carousel still correct", () => songSelect.Carousel.SelectedBeatmap.OnlineBeatmapID == target.OnlineBeatmapID);
}
[Test] [Test]
public void TestAutoplayViaCtrlEnter() public void TestAutoplayViaCtrlEnter()
{ {
@ -468,6 +507,7 @@ namespace osu.Game.Tests.Visual.SongSelect
private void importForRuleset(int id) => manager.Import(createTestBeatmapSet(getImportId(), rulesets.AvailableRulesets.Where(r => r.ID == id).ToArray())).Wait(); private void importForRuleset(int id) => manager.Import(createTestBeatmapSet(getImportId(), rulesets.AvailableRulesets.Where(r => r.ID == id).ToArray())).Wait();
private static int importId; private static int importId;
private int getImportId() => ++importId; private int getImportId() => ++importId;
private void checkMusicPlaying(bool playing) => private void checkMusicPlaying(bool playing) =>
@ -551,6 +591,8 @@ namespace osu.Game.Tests.Visual.SongSelect
public new Bindable<RulesetInfo> Ruleset => base.Ruleset; public new Bindable<RulesetInfo> Ruleset => base.Ruleset;
public new FilterControl FilterControl => base.FilterControl;
public WorkingBeatmap CurrentBeatmap => Beatmap.Value; public WorkingBeatmap CurrentBeatmap => Beatmap.Value;
public WorkingBeatmap CurrentBeatmapDetailsBeatmap => BeatmapDetails.Beatmap; public WorkingBeatmap CurrentBeatmapDetailsBeatmap => BeatmapDetails.Beatmap;
public new BeatmapCarousel Carousel => base.Carousel; public new BeatmapCarousel Carousel => base.Carousel;

View File

@ -0,0 +1,40 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using NUnit.Framework;
using osu.Framework.Graphics;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu.Mods;
using osu.Game.Screens.Play.HUD;
namespace osu.Game.Tests.Visual.UserInterface
{
public class TestSceneModDisplay : OsuTestScene
{
[TestCase(ExpansionMode.ExpandOnHover)]
[TestCase(ExpansionMode.AlwaysExpanded)]
[TestCase(ExpansionMode.AlwaysContracted)]
public void TestMode(ExpansionMode mode)
{
AddStep("create mod display", () =>
{
Child = new ModDisplay
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
ExpansionMode = mode,
Current =
{
Value = new Mod[]
{
new OsuModHardRock(),
new OsuModDoubleTime(),
new OsuModDifficultyAdjust(),
new OsuModEasy(),
}
}
};
});
}
}
}

View File

@ -37,9 +37,9 @@ namespace osu.Game.Tests
trackStore = audioManager.GetTrackStore(getZipReader()); trackStore = audioManager.GetTrackStore(getZipReader());
} }
protected override void Dispose(bool isDisposing) ~WaveformTestBeatmap()
{ {
base.Dispose(isDisposing); // Remove the track store from the audio manager
trackStore?.Dispose(); trackStore?.Dispose();
} }

View File

@ -3,7 +3,7 @@
<ItemGroup Label="Package References"> <ItemGroup Label="Package References">
<PackageReference Include="Appveyor.TestLogger" Version="2.0.0" /> <PackageReference Include="Appveyor.TestLogger" Version="2.0.0" />
<PackageReference Include="DeepEqual" Version="2.0.0" /> <PackageReference Include="DeepEqual" Version="2.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.4.0" /> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.5.0" />
<PackageReference Include="NUnit" Version="3.12.0" /> <PackageReference Include="NUnit" Version="3.12.0" />
<PackageReference Include="NUnit3TestAdapter" Version="3.15.1" /> <PackageReference Include="NUnit3TestAdapter" Version="3.15.1" />
<PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" /> <PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" />

View File

@ -5,7 +5,7 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup Label="Package References"> <ItemGroup Label="Package References">
<PackageReference Include="Appveyor.TestLogger" Version="2.0.0" /> <PackageReference Include="Appveyor.TestLogger" Version="2.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.4.0" /> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.5.0" />
<PackageReference Include="NUnit" Version="3.12.0" /> <PackageReference Include="NUnit" Version="3.12.0" />
<PackageReference Include="NUnit3TestAdapter" Version="3.15.1" /> <PackageReference Include="NUnit3TestAdapter" Version="3.15.1" />
</ItemGroup> </ItemGroup>

View File

@ -36,8 +36,9 @@ namespace osu.Game.Beatmaps
using (var stream = new LineBufferedReader(store.GetStream(getPathForFile(BeatmapInfo.Path)))) using (var stream = new LineBufferedReader(store.GetStream(getPathForFile(BeatmapInfo.Path))))
return Decoder.GetDecoder<Beatmap>(stream).Decode(stream); return Decoder.GetDecoder<Beatmap>(stream).Decode(stream);
} }
catch catch (Exception e)
{ {
Logger.Error(e, "Beatmap failed to load");
return null; return null;
} }
} }
@ -59,8 +60,9 @@ namespace osu.Game.Beatmaps
{ {
return textureStore.Get(getPathForFile(Metadata.BackgroundFile)); return textureStore.Get(getPathForFile(Metadata.BackgroundFile));
} }
catch catch (Exception e)
{ {
Logger.Error(e, "Background failed to load");
return null; return null;
} }
} }
@ -74,8 +76,9 @@ namespace osu.Game.Beatmaps
{ {
return new VideoSprite(textureStore.GetStream(getPathForFile(Metadata.VideoFile))); return new VideoSprite(textureStore.GetStream(getPathForFile(Metadata.VideoFile)));
} }
catch catch (Exception e)
{ {
Logger.Error(e, "Video failed to load");
return null; return null;
} }
} }
@ -86,8 +89,9 @@ namespace osu.Game.Beatmaps
{ {
return (trackStore ??= AudioManager.GetTrackStore(store)).Get(getPathForFile(Metadata.AudioFile)); return (trackStore ??= AudioManager.GetTrackStore(store)).Get(getPathForFile(Metadata.AudioFile));
} }
catch catch (Exception e)
{ {
Logger.Error(e, "Track failed to load");
return null; return null;
} }
} }
@ -115,8 +119,9 @@ namespace osu.Game.Beatmaps
var trackData = store.GetStream(getPathForFile(Metadata.AudioFile)); var trackData = store.GetStream(getPathForFile(Metadata.AudioFile));
return trackData == null ? null : new Waveform(trackData); return trackData == null ? null : new Waveform(trackData);
} }
catch catch (Exception e)
{ {
Logger.Error(e, "Waveform failed to load");
return null; return null;
} }
} }

View File

@ -13,6 +13,7 @@ namespace osu.Game.Beatmaps.Drawables
public class BeatmapSetOnlineStatusPill : CircularContainer public class BeatmapSetOnlineStatusPill : CircularContainer
{ {
private readonly OsuSpriteText statusText; private readonly OsuSpriteText statusText;
private readonly Box background;
private BeatmapSetOnlineStatus status; private BeatmapSetOnlineStatus status;
@ -43,6 +44,12 @@ namespace osu.Game.Beatmaps.Drawables
set => statusText.Padding = value; set => statusText.Padding = value;
} }
public Color4 BackgroundColour
{
get => background.Colour;
set => background.Colour = value;
}
public BeatmapSetOnlineStatusPill() public BeatmapSetOnlineStatusPill()
{ {
AutoSizeAxes = Axes.Both; AutoSizeAxes = Axes.Both;
@ -50,7 +57,7 @@ namespace osu.Game.Beatmaps.Drawables
Children = new Drawable[] Children = new Drawable[]
{ {
new Box background = new Box
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
Colour = Color4.Black, Colour = Color4.Black,

View File

@ -64,7 +64,7 @@ namespace osu.Game.Beatmaps.Formats
hitObject.ApplyDefaults(this.beatmap.ControlPointInfo, this.beatmap.BeatmapInfo.BaseDifficulty); hitObject.ApplyDefaults(this.beatmap.ControlPointInfo, this.beatmap.BeatmapInfo.BaseDifficulty);
} }
protected override bool ShouldSkipLine(string line) => base.ShouldSkipLine(line) || line.StartsWith(" ", StringComparison.Ordinal) || line.StartsWith("_", StringComparison.Ordinal); protected override bool ShouldSkipLine(string line) => base.ShouldSkipLine(line) || line.StartsWith(' ') || line.StartsWith('_');
protected override void ParseLine(Beatmap beatmap, Section section, string line) protected override void ParseLine(Beatmap beatmap, Section section, string line)
{ {

View File

@ -33,7 +33,7 @@ namespace osu.Game.Beatmaps.Formats
if (ShouldSkipLine(line)) if (ShouldSkipLine(line))
continue; continue;
if (line.StartsWith(@"[", StringComparison.Ordinal) && line.EndsWith(@"]", StringComparison.Ordinal)) if (line.StartsWith('[') && line.EndsWith(']'))
{ {
if (!Enum.TryParse(line[1..^1], out section)) if (!Enum.TryParse(line[1..^1], out section))
{ {

View File

@ -64,15 +64,16 @@ namespace osu.Game.Beatmaps.Formats
private void handleEvents(string line) private void handleEvents(string line)
{ {
var depth = 0; var depth = 0;
var lineSpan = line.AsSpan();
while (lineSpan.StartsWith(" ", StringComparison.Ordinal) || lineSpan.StartsWith("_", StringComparison.Ordinal)) foreach (char c in line)
{ {
lineSpan = lineSpan.Slice(1); if (c == ' ' || c == '_')
++depth; depth++;
else
break;
} }
line = lineSpan.ToString(); line = line.Substring(depth);
decodeVariables(ref line); decodeVariables(ref line);

View File

@ -17,10 +17,11 @@ using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI;
using osu.Game.Skinning; using osu.Game.Skinning;
using osu.Framework.Graphics.Video; using osu.Framework.Graphics.Video;
using osu.Framework.Logging;
namespace osu.Game.Beatmaps namespace osu.Game.Beatmaps
{ {
public abstract class WorkingBeatmap : IWorkingBeatmap, IDisposable public abstract class WorkingBeatmap : IWorkingBeatmap
{ {
public readonly BeatmapInfo BeatmapInfo; public readonly BeatmapInfo BeatmapInfo;
@ -39,7 +40,7 @@ namespace osu.Game.Beatmaps
BeatmapSetInfo = beatmapInfo.BeatmapSet; BeatmapSetInfo = beatmapInfo.BeatmapSet;
Metadata = beatmapInfo.Metadata ?? BeatmapSetInfo?.Metadata ?? new BeatmapMetadata(); Metadata = beatmapInfo.Metadata ?? BeatmapSetInfo?.Metadata ?? new BeatmapMetadata();
track = new RecyclableLazy<Track>(() => GetTrack() ?? GetVirtualTrack()); track = new RecyclableLazy<Track>(() => GetTrack() ?? GetVirtualTrack(1000));
background = new RecyclableLazy<Texture>(GetBackground, BackgroundStillValid); background = new RecyclableLazy<Texture>(GetBackground, BackgroundStillValid);
waveform = new RecyclableLazy<Waveform>(GetWaveform); waveform = new RecyclableLazy<Waveform>(GetWaveform);
storyboard = new RecyclableLazy<Storyboard>(GetStoryboard); storyboard = new RecyclableLazy<Storyboard>(GetStoryboard);
@ -48,7 +49,7 @@ namespace osu.Game.Beatmaps
total_count.Value++; total_count.Value++;
} }
protected virtual Track GetVirtualTrack() protected virtual Track GetVirtualTrack(double emptyLength = 0)
{ {
const double excess_length = 1000; const double excess_length = 1000;
@ -59,7 +60,7 @@ namespace osu.Game.Beatmaps
switch (lastObject) switch (lastObject)
{ {
case null: case null:
length = excess_length; length = emptyLength;
break; break;
case IHasEndTime endTime: case IHasEndTime endTime:
@ -133,11 +134,29 @@ namespace osu.Game.Beatmaps
return converted; return converted;
} }
public override string ToString() => BeatmapInfo.ToString(); private CancellationTokenSource loadCancellation = new CancellationTokenSource();
public bool BeatmapLoaded => beatmapLoadTask?.IsCompleted ?? false; /// <summary>
/// Beings loading the contents of this <see cref="WorkingBeatmap"/> asynchronously.
/// </summary>
public void BeginAsyncLoad()
{
loadBeatmapAsync();
}
public Task<IBeatmap> LoadBeatmapAsync() => beatmapLoadTask ??= Task.Factory.StartNew(() => /// <summary>
/// Cancels the asynchronous loading of the contents of this <see cref="WorkingBeatmap"/>.
/// </summary>
public void CancelAsyncLoad()
{
loadCancellation?.Cancel();
loadCancellation = new CancellationTokenSource();
if (beatmapLoadTask?.IsCompleted != true)
beatmapLoadTask = null;
}
private Task<IBeatmap> loadBeatmapAsync() => beatmapLoadTask ??= Task.Factory.StartNew(() =>
{ {
// Todo: Handle cancellation during beatmap parsing // Todo: Handle cancellation during beatmap parsing
var b = GetBeatmap() ?? new Beatmap(); var b = GetBeatmap() ?? new Beatmap();
@ -149,7 +168,11 @@ namespace osu.Game.Beatmaps
b.BeatmapInfo = BeatmapInfo; b.BeatmapInfo = BeatmapInfo;
return b; return b;
}, beatmapCancellation.Token, TaskCreationOptions.LongRunning, TaskScheduler.Default); }, loadCancellation.Token, TaskCreationOptions.LongRunning, TaskScheduler.Default);
public override string ToString() => BeatmapInfo.ToString();
public bool BeatmapLoaded => beatmapLoadTask?.IsCompleted ?? false;
public IBeatmap Beatmap public IBeatmap Beatmap
{ {
@ -157,16 +180,25 @@ namespace osu.Game.Beatmaps
{ {
try try
{ {
return LoadBeatmapAsync().Result; return loadBeatmapAsync().Result;
} }
catch (TaskCanceledException) catch (AggregateException ae)
{ {
// This is the exception that is generally expected here, which occurs via natural cancellation of the asynchronous load
if (ae.InnerExceptions.FirstOrDefault() is TaskCanceledException)
return null;
Logger.Error(ae, "Beatmap failed to load");
return null;
}
catch (Exception e)
{
Logger.Error(e, "Beatmap failed to load");
return null; return null;
} }
} }
} }
private readonly CancellationTokenSource beatmapCancellation = new CancellationTokenSource();
protected abstract IBeatmap GetBeatmap(); protected abstract IBeatmap GetBeatmap();
private Task<IBeatmap> beatmapLoadTask; private Task<IBeatmap> beatmapLoadTask;
@ -217,40 +249,11 @@ namespace osu.Game.Beatmaps
/// </summary> /// </summary>
public virtual void RecycleTrack() => track.Recycle(); public virtual void RecycleTrack() => track.Recycle();
#region Disposal
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
private bool isDisposed;
protected virtual void Dispose(bool isDisposing)
{
if (isDisposed)
return;
isDisposed = true;
// recycling logic is not here for the time being, as components which use
// retrieved objects from WorkingBeatmap may not hold a reference to the WorkingBeatmap itself.
// this should be fine as each retrieved component do have their own finalizers.
// cancelling the beatmap load is safe for now since the retrieval is a synchronous
// operation. if we add an async retrieval method this may need to be reconsidered.
beatmapCancellation?.Cancel();
total_count.Value--;
}
~WorkingBeatmap() ~WorkingBeatmap()
{ {
Dispose(false); total_count.Value--;
} }
#endregion
public class RecyclableLazy<T> public class RecyclableLazy<T>
{ {
private Lazy<T> lazy; private Lazy<T> lazy;

View File

@ -3,6 +3,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using System.Reflection; using System.Reflection;
using JetBrains.Annotations; using JetBrains.Annotations;
using osu.Framework.Bindables; using osu.Framework.Bindables;
@ -16,6 +17,10 @@ namespace osu.Game.Configuration
/// An attribute to mark a bindable as being exposed to the user via settings controls. /// An attribute to mark a bindable as being exposed to the user via settings controls.
/// Can be used in conjunction with <see cref="SettingSourceExtensions.CreateSettingsControls"/> to automatically create UI controls. /// Can be used in conjunction with <see cref="SettingSourceExtensions.CreateSettingsControls"/> to automatically create UI controls.
/// </summary> /// </summary>
/// <remarks>
/// All controls with <see cref="OrderPosition"/> set will be placed first in ascending order.
/// All controls with no <see cref="OrderPosition"/> will come afterward in default order.
/// </remarks>
[MeansImplicitUse] [MeansImplicitUse]
[AttributeUsage(AttributeTargets.Property)] [AttributeUsage(AttributeTargets.Property)]
public class SettingSourceAttribute : Attribute public class SettingSourceAttribute : Attribute
@ -24,18 +29,26 @@ namespace osu.Game.Configuration
public string Description { get; } public string Description { get; }
public int? OrderPosition { get; }
public SettingSourceAttribute(string label, string description = null) public SettingSourceAttribute(string label, string description = null)
{ {
Label = label ?? string.Empty; Label = label ?? string.Empty;
Description = description ?? string.Empty; Description = description ?? string.Empty;
} }
public SettingSourceAttribute(string label, string description, int orderPosition)
: this(label, description)
{
OrderPosition = orderPosition;
}
} }
public static class SettingSourceExtensions public static class SettingSourceExtensions
{ {
public static IEnumerable<Drawable> CreateSettingsControls(this object obj) public static IEnumerable<Drawable> CreateSettingsControls(this object obj)
{ {
foreach (var (attr, property) in obj.GetSettingsSourceProperties()) foreach (var (attr, property) in obj.GetOrderedSettingsSourceProperties())
{ {
object value = property.GetValue(obj); object value = property.GetValue(obj);
@ -116,5 +129,15 @@ namespace osu.Game.Configuration
yield return (attr, property); yield return (attr, property);
} }
} }
public static IEnumerable<(SettingSourceAttribute, PropertyInfo)> GetOrderedSettingsSourceProperties(this object obj)
{
var original = obj.GetSettingsSourceProperties();
var orderedRelative = original.Where(attr => attr.Item1.OrderPosition != null).OrderBy(attr => attr.Item1.OrderPosition);
var unordered = original.Except(orderedRelative);
return orderedRelative.Concat(unordered);
}
} }
} }

View File

@ -59,9 +59,9 @@ namespace osu.Game.Graphics.Containers
Track track = null; Track track = null;
IBeatmap beatmap = null; IBeatmap beatmap = null;
double currentTrackTime; double currentTrackTime = 0;
TimingControlPoint timingPoint; TimingControlPoint timingPoint = null;
EffectControlPoint effectPoint; EffectControlPoint effectPoint = null;
if (Beatmap.Value.TrackLoaded && Beatmap.Value.BeatmapLoaded) if (Beatmap.Value.TrackLoaded && Beatmap.Value.BeatmapLoaded)
{ {
@ -69,24 +69,18 @@ namespace osu.Game.Graphics.Containers
beatmap = Beatmap.Value.Beatmap; beatmap = Beatmap.Value.Beatmap;
} }
if (track != null && beatmap != null && track.IsRunning) if (track != null && beatmap != null && track.IsRunning && track.Length > 0)
{ {
currentTrackTime = track.Length > 0 ? track.CurrentTime + EarlyActivationMilliseconds : Clock.CurrentTime; currentTrackTime = track.CurrentTime + EarlyActivationMilliseconds;
timingPoint = beatmap.ControlPointInfo.TimingPointAt(currentTrackTime); timingPoint = beatmap.ControlPointInfo.TimingPointAt(currentTrackTime);
effectPoint = beatmap.ControlPointInfo.EffectPointAt(currentTrackTime); effectPoint = beatmap.ControlPointInfo.EffectPointAt(currentTrackTime);
if (timingPoint.BeatLength == 0)
{
IsBeatSyncedWithTrack = false;
return;
} }
IsBeatSyncedWithTrack = true; IsBeatSyncedWithTrack = timingPoint?.BeatLength > 0;
}
else if (timingPoint == null || !IsBeatSyncedWithTrack)
{ {
IsBeatSyncedWithTrack = false;
currentTrackTime = Clock.CurrentTime; currentTrackTime = Clock.CurrentTime;
timingPoint = defaultTiming; timingPoint = defaultTiming;
effectPoint = defaultEffect; effectPoint = defaultEffect;

View File

@ -49,14 +49,7 @@ namespace osu.Game.Graphics.UserInterface
public GradientLine() public GradientLine()
{ {
RelativeSizeAxes = Axes.X; RelativeSizeAxes = Axes.X;
Size = new Vector2(0.8f, 1.5f); Size = new Vector2(0.8f, 1f);
ColumnDimensions = new[]
{
new Dimension(),
new Dimension(mode: GridSizeMode.Relative, size: 0.4f),
new Dimension(),
};
Content = new[] Content = new[]
{ {
@ -65,16 +58,12 @@ namespace osu.Game.Graphics.UserInterface
new Box new Box
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
Colour = ColourInfo.GradientHorizontal(Color4.Transparent, Color4.White) Colour = ColourInfo.GradientHorizontal(Color4.Transparent, Colour)
}, },
new Box new Box
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
}, Colour = ColourInfo.GradientHorizontal(Colour, Color4.Transparent)
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = ColourInfo.GradientHorizontal(Color4.White, Color4.Transparent)
}, },
} }
}; };

View File

@ -26,6 +26,9 @@ namespace osu.Game.Online.API.Requests.Responses
[JsonProperty(@"end_date")] [JsonProperty(@"end_date")]
public DateTimeOffset EndDate; public DateTimeOffset EndDate;
[JsonProperty(@"participant_count")]
public int? Participants;
public override string ToString() => Name; public override string ToString() => Name;
} }
} }

View File

@ -55,7 +55,7 @@ namespace osu.Game.Online.Leaderboards
private List<ScoreComponentLabel> statisticsLabels; private List<ScoreComponentLabel> statisticsLabels;
[Resolved] [Resolved(CanBeNull = true)]
private DialogOverlay dialogOverlay { get; set; } private DialogOverlay dialogOverlay { get; set; }
public LeaderboardScore(ScoreInfo score, int rank, bool allowHighlight = true) public LeaderboardScore(ScoreInfo score, int rank, bool allowHighlight = true)

View File

@ -1,9 +1,9 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using System.Collections.Generic;
using System.Linq; using System.Linq;
using Newtonsoft.Json; using Newtonsoft.Json;
using osu.Framework.Bindables;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Online.API; using osu.Game.Online.API;
using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.API.Requests.Responses;
@ -24,24 +24,16 @@ namespace osu.Game.Online.Multiplayer
public int RulesetID { get; set; } public int RulesetID { get; set; }
[JsonIgnore] [JsonIgnore]
public BeatmapInfo Beatmap public readonly Bindable<BeatmapInfo> Beatmap = new Bindable<BeatmapInfo>();
{
get => beatmap;
set
{
beatmap = value;
BeatmapID = value?.OnlineBeatmapID ?? 0;
}
}
[JsonIgnore] [JsonIgnore]
public RulesetInfo Ruleset { get; set; } public readonly Bindable<RulesetInfo> Ruleset = new Bindable<RulesetInfo>();
[JsonIgnore] [JsonIgnore]
public readonly List<Mod> AllowedMods = new List<Mod>(); public readonly BindableList<Mod> AllowedMods = new BindableList<Mod>();
[JsonIgnore] [JsonIgnore]
public readonly List<Mod> RequiredMods = new List<Mod>(); public readonly BindableList<Mod> RequiredMods = new BindableList<Mod>();
[JsonProperty("beatmap")] [JsonProperty("beatmap")]
private APIBeatmap apiBeatmap { get; set; } private APIBeatmap apiBeatmap { get; set; }
@ -64,16 +56,20 @@ namespace osu.Game.Online.Multiplayer
set => requiredModsBacking = value; set => requiredModsBacking = value;
} }
private BeatmapInfo beatmap; public PlaylistItem()
{
Beatmap.BindValueChanged(beatmap => BeatmapID = beatmap.NewValue?.OnlineBeatmapID ?? 0);
Ruleset.BindValueChanged(ruleset => RulesetID = ruleset.NewValue?.ID ?? 0);
}
public void MapObjects(BeatmapManager beatmaps, RulesetStore rulesets) public void MapObjects(BeatmapManager beatmaps, RulesetStore rulesets)
{ {
// If we don't have an api beatmap, the request occurred as a result of room creation, so we can query the local beatmap instead // If we don't have an api beatmap, the request occurred as a result of room creation, so we can query the local beatmap instead
// Todo: Is this a bug? Room creation only returns the beatmap ID // Todo: Is this a bug? Room creation only returns the beatmap ID
Beatmap = apiBeatmap == null ? beatmaps.QueryBeatmap(b => b.OnlineBeatmapID == BeatmapID) : apiBeatmap.ToBeatmap(rulesets); Beatmap.Value = apiBeatmap == null ? beatmaps.QueryBeatmap(b => b.OnlineBeatmapID == BeatmapID) : apiBeatmap.ToBeatmap(rulesets);
Ruleset = rulesets.GetRuleset(RulesetID); Ruleset.Value = rulesets.GetRuleset(RulesetID);
Ruleset rulesetInstance = Ruleset.CreateInstance(); Ruleset rulesetInstance = Ruleset.Value.CreateInstance();
if (allowedModsBacking != null) if (allowedModsBacking != null)
{ {

View File

@ -65,7 +65,7 @@ namespace osu.Game.Online.Multiplayer
[Cached] [Cached]
[JsonIgnore] [JsonIgnore]
public Bindable<IEnumerable<User>> Participants { get; private set; } = new Bindable<IEnumerable<User>>(Enumerable.Empty<User>()); public BindableList<User> Participants { get; private set; } = new BindableList<User>();
[Cached] [Cached]
public Bindable<int> ParticipantCount { get; private set; } = new Bindable<int>(); public Bindable<int> ParticipantCount { get; private set; } = new Bindable<int>();
@ -130,7 +130,6 @@ namespace osu.Game.Online.Multiplayer
Type.Value = other.Type.Value; Type.Value = other.Type.Value;
MaxParticipants.Value = other.MaxParticipants.Value; MaxParticipants.Value = other.MaxParticipants.Value;
ParticipantCount.Value = other.ParticipantCount.Value; ParticipantCount.Value = other.ParticipantCount.Value;
Participants.Value = other.Participants.Value.ToArray();
EndDate.Value = other.EndDate.Value; EndDate.Value = other.EndDate.Value;
if (DateTimeOffset.Now >= EndDate.Value) if (DateTimeOffset.Now >= EndDate.Value)
@ -142,6 +141,10 @@ namespace osu.Game.Online.Multiplayer
else if (other.Playlist.Count > 0) else if (other.Playlist.Count > 0)
Playlist.First().ID = other.Playlist.First().ID; Playlist.First().ID = other.Playlist.First().ID;
foreach (var removedItem in Participants.Except(other.Participants).ToArray())
Participants.Remove(removedItem);
Participants.AddRange(other.Participants.Except(Participants).ToArray());
Position = other.Position; Position = other.Position;
} }

View File

@ -401,15 +401,14 @@ namespace osu.Game
if (nextBeatmap?.Track != null) if (nextBeatmap?.Track != null)
nextBeatmap.Track.Completed += currentTrackCompleted; nextBeatmap.Track.Completed += currentTrackCompleted;
using (var oldBeatmap = beatmap.OldValue) var oldBeatmap = beatmap.OldValue;
{
if (oldBeatmap?.Track != null) if (oldBeatmap?.Track != null)
oldBeatmap.Track.Completed -= currentTrackCompleted; oldBeatmap.Track.Completed -= currentTrackCompleted;
}
updateModDefaults(); updateModDefaults();
nextBeatmap?.LoadBeatmapAsync(); oldBeatmap?.CancelAsyncLoad();
nextBeatmap?.BeginAsyncLoad();
} }
private void modsChanged(ValueChangedEvent<IReadOnlyList<Mod>> mods) private void modsChanged(ValueChangedEvent<IReadOnlyList<Mod>> mods)
@ -446,7 +445,7 @@ namespace osu.Game
/// </summary> /// </summary>
/// <param name="action">The action to perform once we are in the correct state.</param> /// <param name="action">The action to perform once we are in the correct state.</param>
/// <param name="validScreens">An optional collection of valid screen types. If any of these screens are already current we can perform the action immediately, else the first valid parent will be made current before performing the action. <see cref="MainMenu"/> is used if not specified.</param> /// <param name="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) public void PerformFromScreen(Action<IScreen> action, IEnumerable<Type> validScreens = null)
{ {
performFromMainMenuTask?.Cancel(); performFromMainMenuTask?.Cancel();

View File

@ -150,7 +150,7 @@ namespace osu.Game.Overlays.BeatmapSet.Buttons
}, },
new OsuSpriteText new OsuSpriteText
{ {
Text = BeatmapSet.Value.OnlineInfo.HasVideo && noVideo ? "without Video" : string.Empty, Text = getVideoSuffixText(),
Font = OsuFont.GetFont(size: 11, weight: FontWeight.Bold) Font = OsuFont.GetFont(size: 11, weight: FontWeight.Bold)
}, },
}; };
@ -163,5 +163,13 @@ namespace osu.Game.Overlays.BeatmapSet.Buttons
private void userChanged(ValueChangedEvent<User> e) => button.Enabled.Value = !(e.NewValue is GuestUser); private void userChanged(ValueChangedEvent<User> e) => button.Enabled.Value = !(e.NewValue is GuestUser);
private void enabledChanged(ValueChangedEvent<bool> e) => this.FadeColour(e.NewValue ? Color4.White : Color4.Gray, 200, Easing.OutQuint); private void enabledChanged(ValueChangedEvent<bool> e) => this.FadeColour(e.NewValue ? Color4.White : Color4.Gray, 200, Easing.OutQuint);
private string getVideoSuffixText()
{
if (!BeatmapSet.Value.OnlineInfo.HasVideo)
return string.Empty;
return noVideo ? "without Video" : "with Video";
}
} }
} }

View File

@ -3,7 +3,6 @@
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Shapes;
@ -14,7 +13,6 @@ using osu.Game.Graphics;
using osu.Game.Graphics.Containers; using osu.Game.Graphics.Containers;
using osu.Game.Overlays.Direct; using osu.Game.Overlays.Direct;
using osuTK; using osuTK;
using osuTK.Graphics;
namespace osu.Game.Overlays.BeatmapSet.Buttons namespace osu.Game.Overlays.BeatmapSet.Buttons
{ {
@ -22,7 +20,7 @@ namespace osu.Game.Overlays.BeatmapSet.Buttons
{ {
private const float transition_duration = 500; private const float transition_duration = 500;
private readonly Box bg, progress; private readonly Box background, progress;
private readonly PlayButton playButton; private readonly PlayButton playButton;
private PreviewTrack preview => playButton.Preview; private PreviewTrack preview => playButton.Preview;
@ -40,10 +38,10 @@ namespace osu.Game.Overlays.BeatmapSet.Buttons
Children = new Drawable[] Children = new Drawable[]
{ {
bg = new Box background = new Box
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
Colour = Color4.Black.Opacity(0.25f), Alpha = 0.5f
}, },
new Container new Container
{ {
@ -71,9 +69,10 @@ namespace osu.Game.Overlays.BeatmapSet.Buttons
} }
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(OsuColour colours) private void load(OsuColour colours, OverlayColourProvider colourProvider)
{ {
progress.Colour = colours.Yellow; progress.Colour = colours.Yellow;
background.Colour = colourProvider.Background6;
} }
protected override void Update() protected override void Update()
@ -91,13 +90,13 @@ namespace osu.Game.Overlays.BeatmapSet.Buttons
protected override bool OnHover(HoverEvent e) protected override bool OnHover(HoverEvent e)
{ {
bg.FadeColour(Color4.Black.Opacity(0.5f), 100); background.FadeTo(0.75f, 80);
return base.OnHover(e); return base.OnHover(e);
} }
protected override void OnHoverLost(HoverLostEvent e) protected override void OnHoverLost(HoverLostEvent e)
{ {
bg.FadeColour(Color4.Black.Opacity(0.25f), 100); background.FadeTo(0.5f, 80);
base.OnHoverLost(e); base.OnHoverLost(e);
} }
} }

View File

@ -2,7 +2,6 @@
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Shapes;
@ -10,7 +9,6 @@ using osu.Game.Beatmaps;
using osu.Game.Overlays.BeatmapSet.Buttons; using osu.Game.Overlays.BeatmapSet.Buttons;
using osu.Game.Screens.Select.Details; using osu.Game.Screens.Select.Details;
using osuTK; using osuTK;
using osuTK.Graphics;
namespace osu.Game.Overlays.BeatmapSet namespace osu.Game.Overlays.BeatmapSet
{ {
@ -21,6 +19,7 @@ namespace osu.Game.Overlays.BeatmapSet
private readonly PreviewButton preview; private readonly PreviewButton preview;
private readonly BasicStats basic; private readonly BasicStats basic;
private readonly AdvancedStats advanced; private readonly AdvancedStats advanced;
private readonly DetailBox ratingBox;
private BeatmapSetInfo beatmapSet; private BeatmapSetInfo beatmapSet;
@ -54,6 +53,7 @@ namespace osu.Game.Overlays.BeatmapSet
private void updateDisplay() private void updateDisplay()
{ {
Ratings.Metrics = BeatmapSet?.Metrics; Ratings.Metrics = BeatmapSet?.Metrics;
ratingBox.Alpha = BeatmapSet?.OnlineInfo?.Status > 0 ? 1 : 0;
} }
public Details() public Details()
@ -86,7 +86,7 @@ namespace osu.Game.Overlays.BeatmapSet
Margin = new MarginPadding { Vertical = 7.5f }, Margin = new MarginPadding { Vertical = 7.5f },
}, },
}, },
new DetailBox ratingBox = new DetailBox
{ {
Child = Ratings = new UserRatings Child = Ratings = new UserRatings
{ {
@ -107,6 +107,8 @@ namespace osu.Game.Overlays.BeatmapSet
private class DetailBox : Container private class DetailBox : Container
{ {
private readonly Container content; private readonly Container content;
private readonly Box background;
protected override Container<Drawable> Content => content; protected override Container<Drawable> Content => content;
public DetailBox() public DetailBox()
@ -116,10 +118,10 @@ namespace osu.Game.Overlays.BeatmapSet
InternalChildren = new Drawable[] InternalChildren = new Drawable[]
{ {
new Box background = new Box
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
Colour = Color4.Black.Opacity(0.5f), Alpha = 0.5f
}, },
content = new Container content = new Container
{ {
@ -129,6 +131,12 @@ namespace osu.Game.Overlays.BeatmapSet
}, },
}; };
} }
[BackgroundDependencyLoader]
private void load(OverlayColourProvider colourProvider)
{
background.Colour = colourProvider.Background6;
}
} }
} }
} }

View File

@ -1,4 +1,4 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using System.Linq; using System.Linq;
@ -30,6 +30,7 @@ namespace osu.Game.Overlays.BeatmapSet
private const float buttons_spacing = 5; private const float buttons_spacing = 5;
private readonly UpdateableBeatmapSetCover cover; private readonly UpdateableBeatmapSetCover cover;
private readonly Box coverGradient;
private readonly OsuSpriteText title, artist; private readonly OsuSpriteText title, artist;
private readonly AuthorInfo author; private readonly AuthorInfo author;
private readonly FillFlowContainer downloadButtonsContainer; private readonly FillFlowContainer downloadButtonsContainer;
@ -93,10 +94,9 @@ namespace osu.Game.Overlays.BeatmapSet
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
Masking = true, Masking = true,
}, },
new Box coverGradient = new Box
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both
Colour = ColourInfo.GradientVertical(Color4.Black.Opacity(0.3f), Color4.Black.Opacity(0.8f)),
}, },
}, },
}, },
@ -106,8 +106,7 @@ namespace osu.Game.Overlays.BeatmapSet
AutoSizeAxes = Axes.Y, AutoSizeAxes = Axes.Y,
Padding = new MarginPadding Padding = new MarginPadding
{ {
Top = 20, Vertical = BeatmapSetOverlay.Y_PADDING,
Bottom = 30,
Left = BeatmapSetOverlay.X_PADDING, Left = BeatmapSetOverlay.X_PADDING,
Right = BeatmapSetOverlay.X_PADDING + BeatmapSetOverlay.RIGHT_WIDTH, Right = BeatmapSetOverlay.X_PADDING + BeatmapSetOverlay.RIGHT_WIDTH,
}, },
@ -130,11 +129,12 @@ namespace osu.Game.Overlays.BeatmapSet
{ {
Direction = FillDirection.Horizontal, Direction = FillDirection.Horizontal,
AutoSizeAxes = Axes.Both, AutoSizeAxes = Axes.Both,
Margin = new MarginPadding { Top = 15 },
Children = new Drawable[] Children = new Drawable[]
{ {
title = new OsuSpriteText title = new OsuSpriteText
{ {
Font = OsuFont.GetFont(size: 37, weight: FontWeight.Bold, italics: true) Font = OsuFont.GetFont(size: 30, weight: FontWeight.SemiBold, italics: true)
}, },
externalLink = new ExternalLinkButton externalLink = new ExternalLinkButton
{ {
@ -144,7 +144,7 @@ namespace osu.Game.Overlays.BeatmapSet
}, },
} }
}, },
artist = new OsuSpriteText { Font = OsuFont.GetFont(size: 25, weight: FontWeight.SemiBold, italics: true) }, artist = new OsuSpriteText { Font = OsuFont.GetFont(size: 20, weight: FontWeight.Medium, italics: true) },
new Container new Container
{ {
RelativeSizeAxes = Axes.X, RelativeSizeAxes = Axes.X,
@ -187,7 +187,7 @@ namespace osu.Game.Overlays.BeatmapSet
Anchor = Anchor.BottomRight, Anchor = Anchor.BottomRight,
Origin = Anchor.BottomRight, Origin = Anchor.BottomRight,
AutoSizeAxes = Axes.Both, AutoSizeAxes = Axes.Both,
Margin = new MarginPadding { Top = BeatmapSetOverlay.TOP_PADDING, Right = BeatmapSetOverlay.X_PADDING }, Margin = new MarginPadding { Top = BeatmapSetOverlay.Y_PADDING, Right = BeatmapSetOverlay.X_PADDING },
Direction = FillDirection.Vertical, Direction = FillDirection.Vertical,
Spacing = new Vector2(10), Spacing = new Vector2(10),
Children = new Drawable[] Children = new Drawable[]
@ -197,7 +197,7 @@ namespace osu.Game.Overlays.BeatmapSet
Anchor = Anchor.TopRight, Anchor = Anchor.TopRight,
Origin = Anchor.TopRight, Origin = Anchor.TopRight,
TextSize = 14, TextSize = 14,
TextPadding = new MarginPadding { Horizontal = 25, Vertical = 8 } TextPadding = new MarginPadding { Horizontal = 35, Vertical = 10 }
}, },
Details = new Details(), Details = new Details(),
}, },
@ -215,8 +215,11 @@ namespace osu.Game.Overlays.BeatmapSet
} }
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(OsuColour colours) private void load(OverlayColourProvider colourProvider)
{ {
coverGradient.Colour = ColourInfo.GradientVertical(colourProvider.Background6.Opacity(0.3f), colourProvider.Background6.Opacity(0.8f));
onlineStatusPill.BackgroundColour = colourProvider.Background6;
State.BindValueChanged(_ => updateDownloadButtons()); State.BindValueChanged(_ => updateDownloadButtons());
BeatmapSet.BindValueChanged(setInfo => BeatmapSet.BindValueChanged(setInfo =>

View File

@ -38,6 +38,8 @@ namespace osu.Game.Overlays.BeatmapSet
public Info() public Info()
{ {
MetadataSection source, tags, genre, language; MetadataSection source, tags, genre, language;
OsuSpriteText unrankedPlaceholder;
RelativeSizeAxes = Axes.X; RelativeSizeAxes = Axes.X;
Height = 220; Height = 220;
Masking = true; Masking = true;
@ -110,6 +112,14 @@ namespace osu.Game.Overlays.BeatmapSet
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding { Top = 20, Horizontal = 15 }, Padding = new MarginPadding { Top = 20, Horizontal = 15 },
}, },
unrankedPlaceholder = new OsuSpriteText
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Alpha = 0,
Text = "Unranked beatmap",
Font = OsuFont.GetFont(size: 12)
},
}, },
}, },
}, },
@ -122,6 +132,9 @@ namespace osu.Game.Overlays.BeatmapSet
tags.Text = b.NewValue?.Metadata.Tags ?? string.Empty; tags.Text = b.NewValue?.Metadata.Tags ?? string.Empty;
genre.Text = b.NewValue?.OnlineInfo?.Genre?.Name ?? string.Empty; genre.Text = b.NewValue?.OnlineInfo?.Genre?.Name ?? string.Empty;
language.Text = b.NewValue?.OnlineInfo?.Language?.Name ?? string.Empty; language.Text = b.NewValue?.OnlineInfo?.Language?.Name ?? string.Empty;
var setHasLeaderboard = b.NewValue?.OnlineInfo?.Status > 0;
successRate.Alpha = setHasLeaderboard ? 1 : 0;
unrankedPlaceholder.Alpha = setHasLeaderboard ? 0 : 1;
}; };
} }

View File

@ -26,10 +26,10 @@ namespace osu.Game.Overlays.BeatmapSet
} }
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(OsuColour colours) private void load(OverlayColourProvider colourProvider)
{ {
AccentColour = colours.Blue; AccentColour = colourProvider.Highlight1;
LineColour = Color4.Gray; LineColour = colourProvider.Background1;
} }
private class ScopeSelectorTabItem : PageTabItem private class ScopeSelectorTabItem : PageTabItem

View File

@ -77,9 +77,9 @@ namespace osu.Game.Overlays.BeatmapSet.Scores
new TableColumn("rank", Anchor.CentreRight, new Dimension(GridSizeMode.AutoSize)), new TableColumn("rank", Anchor.CentreRight, new Dimension(GridSizeMode.AutoSize)),
new TableColumn("", Anchor.Centre, new Dimension(GridSizeMode.Absolute, 70)), // grade new TableColumn("", Anchor.Centre, new Dimension(GridSizeMode.Absolute, 70)), // grade
new TableColumn("score", Anchor.CentreLeft, new Dimension(GridSizeMode.AutoSize)), new TableColumn("score", Anchor.CentreLeft, new Dimension(GridSizeMode.AutoSize)),
new TableColumn("accuracy", Anchor.CentreLeft, new Dimension(GridSizeMode.AutoSize)), new TableColumn("accuracy", Anchor.CentreLeft, new Dimension(GridSizeMode.Distributed, minSize: 60, maxSize: 70)),
new TableColumn("player", Anchor.CentreLeft, new Dimension(GridSizeMode.Distributed, minSize: 150)), new TableColumn("player", Anchor.CentreLeft, new Dimension(GridSizeMode.Distributed, minSize: 150)),
new TableColumn("max combo", Anchor.CentreLeft, new Dimension(GridSizeMode.Distributed, minSize: 70, maxSize: 90)) new TableColumn("max combo", Anchor.CentreLeft, new Dimension(GridSizeMode.Distributed, minSize: 70, maxSize: 110))
}; };
foreach (var statistic in score.SortedStatistics) foreach (var statistic in score.SortedStatistics)

View File

@ -30,7 +30,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores
RelativeSizeAxes = Axes.X; RelativeSizeAxes = Axes.X;
Height = 25; Height = 25;
CornerRadius = 3; CornerRadius = 5;
Masking = true; Masking = true;
InternalChildren = new Drawable[] InternalChildren = new Drawable[]

View File

@ -164,7 +164,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
Masking = true, Masking = true,
CornerRadius = 10, CornerRadius = 5,
Child = loading = new DimmedLoadingLayer(iconScale: 0.8f) Child = loading = new DimmedLoadingLayer(iconScale: 0.8f)
{ {
Alpha = 0, Alpha = 0,

View File

@ -118,27 +118,43 @@ namespace osu.Game.Overlays.BeatmapSet.Scores
{ {
AutoSizeAxes = Axes.Both; AutoSizeAxes = Axes.Both;
InternalChild = new FillFlowContainer InternalChild = new GridContainer
{ {
AutoSizeAxes = Axes.Both, AutoSizeAxes = Axes.Both,
Direction = FillDirection.Vertical, ColumnDimensions = new[]
Spacing = new Vector2(0, 1), {
Children = new[] new Dimension(GridSizeMode.AutoSize, minSize: minWidth ?? 0)
},
RowDimensions = new[]
{
new Dimension(GridSizeMode.AutoSize),
new Dimension(GridSizeMode.Absolute, 4),
new Dimension(GridSizeMode.AutoSize)
},
Content = new[]
{
new Drawable[]
{ {
text = new OsuSpriteText text = new OsuSpriteText
{ {
Font = OsuFont.GetFont(size: 10, weight: FontWeight.Bold), Font = OsuFont.GetFont(size: 10, weight: FontWeight.Bold),
Text = title.ToUpper() Text = title.ToUpper()
}
}, },
new Drawable[]
{
separator = new Box separator = new Box
{ {
RelativeSizeAxes = minWidth == null ? Axes.X : Axes.None, Anchor = Anchor.CentreLeft,
Width = minWidth ?? 1f, RelativeSizeAxes = Axes.X,
Height = 2, Height = 2
Margin = new MarginPadding { Top = 2 } }
}, },
new[]
{
content content
} }
}
}; };
} }

View File

@ -116,6 +116,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores
Anchor = Anchor.CentreLeft, Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft, Origin = Anchor.CentreLeft,
Size = new Vector2(19, 13), Size = new Vector2(19, 13),
Margin = new MarginPadding { Top = 3 }, // makes spacing look more even
ShowPlaceholderOnNull = false, ShowPlaceholderOnNull = false,
}, },
} }

View File

@ -42,7 +42,7 @@ namespace osu.Game.Overlays.BeatmapSet
int playCount = beatmap?.OnlineInfo?.PlayCount ?? 0; int playCount = beatmap?.OnlineInfo?.PlayCount ?? 0;
var rate = playCount != 0 ? (float)passCount / playCount : 0; var rate = playCount != 0 ? (float)passCount / playCount : 0;
successPercent.Text = rate.ToString("0%"); successPercent.Text = rate.ToString("0.#%");
successRate.Length = rate; successRate.Length = rate;
percentContainer.ResizeWidthTo(successRate.Length, 250, Easing.InOutCubic); percentContainer.ResizeWidthTo(successRate.Length, 250, Easing.InOutCubic);
@ -105,10 +105,10 @@ namespace osu.Game.Overlays.BeatmapSet
} }
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(OsuColour colours) private void load(OsuColour colours, OverlayColourProvider colourProvider)
{ {
successRate.AccentColour = colours.Green; successRate.AccentColour = colours.Green;
successRate.BackgroundColour = colours.GrayD; successRate.BackgroundColour = colourProvider.Background6;
updateDisplay(); updateDisplay();
} }

View File

@ -21,7 +21,7 @@ namespace osu.Game.Overlays
public class BeatmapSetOverlay : FullscreenOverlay public class BeatmapSetOverlay : FullscreenOverlay
{ {
public const float X_PADDING = 40; public const float X_PADDING = 40;
public const float TOP_PADDING = 25; public const float Y_PADDING = 25;
public const float RIGHT_WIDTH = 275; public const float RIGHT_WIDTH = 275;
protected readonly Header Header; protected readonly Header Header;

View File

@ -19,7 +19,7 @@ namespace osu.Game.Overlays.Direct
public DownloadProgressBar(BeatmapSetInfo beatmapSet) public DownloadProgressBar(BeatmapSetInfo beatmapSet)
: base(beatmapSet) : base(beatmapSet)
{ {
AddInternal(progressBar = new ProgressBar AddInternal(progressBar = new InteractionDisabledProgressBar
{ {
Height = 0, Height = 0,
Alpha = 0, Alpha = 0,
@ -64,5 +64,11 @@ namespace osu.Game.Overlays.Direct
} }
}, true); }, true);
} }
private class InteractionDisabledProgressBar : ProgressBar
{
public override bool HandlePositionalInput => false;
public override bool HandleNonPositionalInput => false;
}
} }
} }

View File

@ -6,25 +6,14 @@ using osu.Framework.Bindables;
using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterface;
using osu.Game.Rulesets; using osu.Game.Rulesets;
using osu.Game.Users; using osu.Game.Users;
using System.Collections.Generic;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Allocation;
namespace osu.Game.Overlays.Rankings namespace osu.Game.Overlays.Rankings
{ {
public class RankingsOverlayHeader : TabControlOverlayHeader<RankingsScope> public class RankingsOverlayHeader : TabControlOverlayHeader<RankingsScope>
{ {
public readonly Bindable<RulesetInfo> Ruleset = new Bindable<RulesetInfo>(); public readonly Bindable<RulesetInfo> Ruleset = new Bindable<RulesetInfo>();
public readonly Bindable<Spotlight> Spotlight = new Bindable<Spotlight>();
public readonly Bindable<Country> Country = new Bindable<Country>(); public readonly Bindable<Country> Country = new Bindable<Country>();
public IEnumerable<Spotlight> Spotlights
{
get => spotlightsContainer.Spotlights;
set => spotlightsContainer.Spotlights = value;
}
protected override ScreenTitle CreateTitle() => new RankingsTitle protected override ScreenTitle CreateTitle() => new RankingsTitle
{ {
Scope = { BindTarget = Current } Scope = { BindTarget = Current }
@ -35,35 +24,11 @@ namespace osu.Game.Overlays.Rankings
Current = Ruleset Current = Ruleset
}; };
private SpotlightsContainer spotlightsContainer; protected override Drawable CreateContent() => new CountryFilter
protected override Drawable CreateContent() => new FillFlowContainer
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Direction = FillDirection.Vertical,
Children = new Drawable[]
{
new CountryFilter
{ {
Current = Country Current = Country
},
spotlightsContainer = new SpotlightsContainer
{
Spotlight = { BindTarget = Spotlight }
}
}
}; };
protected override void LoadComplete()
{
Current.BindValueChanged(onCurrentChanged, true);
base.LoadComplete();
}
private void onCurrentChanged(ValueChangedEvent<RankingsScope> scope) =>
spotlightsContainer.FadeTo(scope.NewValue == RankingsScope.Spotlights ? 1 : 0, 200, Easing.OutQuint);
private class RankingsTitle : ScreenTitle private class RankingsTitle : ScreenTitle
{ {
public readonly Bindable<RankingsScope> Scope = new Bindable<RankingsScope>(); public readonly Bindable<RankingsScope> Scope = new Bindable<RankingsScope>();
@ -81,48 +46,6 @@ namespace osu.Game.Overlays.Rankings
protected override Drawable CreateIcon() => new ScreenTitleTextureIcon(@"Icons/rankings"); protected override Drawable CreateIcon() => new ScreenTitleTextureIcon(@"Icons/rankings");
} }
private class SpotlightsContainer : CompositeDrawable
{
public readonly Bindable<Spotlight> Spotlight = new Bindable<Spotlight>();
public IEnumerable<Spotlight> Spotlights
{
get => dropdown.Items;
set => dropdown.Items = value;
}
private readonly OsuDropdown<Spotlight> dropdown;
private readonly Box background;
public SpotlightsContainer()
{
Height = 100;
RelativeSizeAxes = Axes.X;
InternalChildren = new Drawable[]
{
background = new Box
{
RelativeSizeAxes = Axes.Both,
},
dropdown = new OsuDropdown<Spotlight>
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
RelativeSizeAxes = Axes.X,
Width = 0.8f,
Current = Spotlight,
Y = 20,
}
};
}
[BackgroundDependencyLoader]
private void load(OverlayColourProvider colourProvider)
{
background.Colour = colourProvider.Dark3;
}
}
} }
public enum RankingsScope public enum RankingsScope

View File

@ -1,18 +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 Newtonsoft.Json;
namespace osu.Game.Overlays.Rankings
{
public class Spotlight
{
[JsonProperty("id")]
public int Id;
[JsonProperty("text")]
public string Text;
public override string ToString() => Text;
}
}

View File

@ -14,11 +14,14 @@ using osuTK;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using osu.Framework.Graphics.UserInterface; using osu.Framework.Graphics.UserInterface;
using osu.Game.Online.API.Requests;
namespace osu.Game.Overlays.Rankings namespace osu.Game.Overlays.Rankings
{ {
public class SpotlightSelector : CompositeDrawable, IHasCurrentValue<APISpotlight> public class SpotlightSelector : VisibilityContainer, IHasCurrentValue<APISpotlight>
{ {
private const int duration = 300;
private readonly Box background; private readonly Box background;
private readonly SpotlightsDropdown dropdown; private readonly SpotlightsDropdown dropdown;
@ -36,15 +39,22 @@ namespace osu.Game.Overlays.Rankings
set => dropdown.Items = value; set => dropdown.Items = value;
} }
protected override bool StartHidden => true;
private readonly InfoColumn startDateColumn; private readonly InfoColumn startDateColumn;
private readonly InfoColumn endDateColumn; private readonly InfoColumn endDateColumn;
private readonly InfoColumn mapCountColumn;
private readonly InfoColumn participantsColumn;
private readonly Container content;
public SpotlightSelector() public SpotlightSelector()
{ {
RelativeSizeAxes = Axes.X; RelativeSizeAxes = Axes.X;
Height = 100; Height = 100;
Add(content = new Container
InternalChildren = new Drawable[] {
RelativeSizeAxes = Axes.Both,
Children = new Drawable[]
{ {
background = new Box background = new Box
{ {
@ -75,11 +85,14 @@ namespace osu.Game.Overlays.Rankings
{ {
startDateColumn = new InfoColumn(@"Start Date"), startDateColumn = new InfoColumn(@"Start Date"),
endDateColumn = new InfoColumn(@"End Date"), endDateColumn = new InfoColumn(@"End Date"),
mapCountColumn = new InfoColumn(@"Map Count"),
participantsColumn = new InfoColumn(@"Participants")
} }
} }
} }
}, }
}; }
});
} }
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
@ -88,18 +101,17 @@ namespace osu.Game.Overlays.Rankings
background.Colour = colourProvider.Dark3; background.Colour = colourProvider.Dark3;
} }
protected override void LoadComplete() public void ShowInfo(GetSpotlightRankingsResponse response)
{ {
base.LoadComplete(); startDateColumn.Value = dateToString(response.Spotlight.StartDate);
endDateColumn.Value = dateToString(response.Spotlight.EndDate);
Current.BindValueChanged(onCurrentChanged); mapCountColumn.Value = response.BeatmapSets.Count.ToString();
participantsColumn.Value = response.Spotlight.Participants?.ToString("N0");
} }
private void onCurrentChanged(ValueChangedEvent<APISpotlight> spotlight) protected override void PopIn() => content.FadeIn(duration, Easing.OutQuint);
{
startDateColumn.Value = dateToString(spotlight.NewValue.StartDate); protected override void PopOut() => content.FadeOut(duration, Easing.OutQuint);
endDateColumn.Value = dateToString(spotlight.NewValue.EndDate);
}
private string dateToString(DateTimeOffset date) => date.ToString("yyyy-MM-dd"); private string dateToString(DateTimeOffset date) => date.ToString("yyyy-MM-dd");

View File

@ -0,0 +1,161 @@
// 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.Bindables;
using osu.Game.Rulesets;
using osu.Framework.Graphics.Containers;
using osu.Game.Online.API.Requests.Responses;
using osuTK;
using osu.Framework.Allocation;
using osu.Game.Online.API;
using osu.Game.Online.API.Requests;
using osu.Game.Overlays.Rankings.Tables;
using System.Linq;
using osu.Game.Overlays.Direct;
using System.Threading;
using osu.Game.Graphics.Containers;
using osu.Game.Graphics.UserInterface;
namespace osu.Game.Overlays.Rankings
{
public class SpotlightsLayout : CompositeDrawable
{
public readonly Bindable<RulesetInfo> Ruleset = new Bindable<RulesetInfo>();
private readonly Bindable<APISpotlight> selectedSpotlight = new Bindable<APISpotlight>();
[Resolved]
private IAPIProvider api { get; set; }
[Resolved]
private RulesetStore rulesets { get; set; }
private CancellationTokenSource cancellationToken;
private GetSpotlightRankingsRequest getRankingsRequest;
private GetSpotlightsRequest spotlightsRequest;
private SpotlightSelector selector;
private Container content;
private DimmedLoadingLayer loading;
[BackgroundDependencyLoader]
private void load()
{
RelativeSizeAxes = Axes.X;
AutoSizeAxes = Axes.Y;
InternalChild = new ReverseChildIDFillFlowContainer<Drawable>
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Direction = FillDirection.Vertical,
Children = new Drawable[]
{
selector = new SpotlightSelector
{
Current = selectedSpotlight,
},
new Container
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Children = new Drawable[]
{
content = new Container
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Margin = new MarginPadding { Vertical = 10 }
},
loading = new DimmedLoadingLayer()
}
}
}
};
}
protected override void LoadComplete()
{
base.LoadComplete();
selector.Show();
selectedSpotlight.BindValueChanged(onSpotlightChanged);
Ruleset.BindValueChanged(onRulesetChanged);
getSpotlights();
}
private void getSpotlights()
{
spotlightsRequest = new GetSpotlightsRequest();
spotlightsRequest.Success += response => selector.Spotlights = response.Spotlights;
api.Queue(spotlightsRequest);
}
private void onRulesetChanged(ValueChangedEvent<RulesetInfo> ruleset)
{
if (!selector.Spotlights.Any())
return;
selectedSpotlight.TriggerChange();
}
private void onSpotlightChanged(ValueChangedEvent<APISpotlight> spotlight)
{
loading.Show();
cancellationToken?.Cancel();
getRankingsRequest?.Cancel();
getRankingsRequest = new GetSpotlightRankingsRequest(Ruleset.Value, spotlight.NewValue.Id);
getRankingsRequest.Success += onSuccess;
api.Queue(getRankingsRequest);
}
private void onSuccess(GetSpotlightRankingsResponse response)
{
LoadComponentAsync(createContent(response), loaded =>
{
selector.ShowInfo(response);
content.Clear();
content.Add(loaded);
loading.Hide();
}, (cancellationToken = new CancellationTokenSource()).Token);
}
private Drawable createContent(GetSpotlightRankingsResponse response) => new FillFlowContainer
{
AutoSizeAxes = Axes.Y,
RelativeSizeAxes = Axes.X,
Direction = FillDirection.Vertical,
Spacing = new Vector2(0, 20),
Children = new Drawable[]
{
new ScoresTable(1, response.Users),
new FillFlowContainer
{
AutoSizeAxes = Axes.Y,
RelativeSizeAxes = Axes.X,
Spacing = new Vector2(10),
Children = response.BeatmapSets.Select(b => new DirectGridPanel(b.ToBeatmapSet(rulesets))
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
}).ToList()
}
}
};
protected override void Dispose(bool isDisposing)
{
base.Dispose(isDisposing);
spotlightsRequest?.Cancel();
getRankingsRequest?.Cancel();
cancellationToken?.Cancel();
}
}
}

View File

@ -24,7 +24,7 @@ namespace osu.Game.Overlays
private readonly Bindable<RulesetInfo> ruleset = new Bindable<RulesetInfo>(); private readonly Bindable<RulesetInfo> ruleset = new Bindable<RulesetInfo>();
private readonly BasicScrollContainer scrollFlow; private readonly BasicScrollContainer scrollFlow;
private readonly Container tableContainer; private readonly Container contentContainer;
private readonly DimmedLoadingLayer loading; private readonly DimmedLoadingLayer loading;
private readonly Box background; private readonly Box background;
@ -69,13 +69,13 @@ namespace osu.Game.Overlays
AutoSizeAxes = Axes.Y, AutoSizeAxes = Axes.Y,
Children = new Drawable[] Children = new Drawable[]
{ {
tableContainer = new Container contentContainer = new Container
{ {
Anchor = Anchor.TopCentre, Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre, Origin = Anchor.TopCentre,
AutoSizeAxes = Axes.Y, AutoSizeAxes = Axes.Y,
RelativeSizeAxes = Axes.X, RelativeSizeAxes = Axes.X,
Margin = new MarginPadding { Vertical = 10 } Margin = new MarginPadding { Bottom = 10 }
}, },
loading = new DimmedLoadingLayer(), loading = new DimmedLoadingLayer(),
} }
@ -112,7 +112,13 @@ namespace osu.Game.Overlays
Scheduler.AddOnce(loadNewContent); Scheduler.AddOnce(loadNewContent);
}, true); }, true);
ruleset.BindValueChanged(_ => Scheduler.AddOnce(loadNewContent), true); ruleset.BindValueChanged(_ =>
{
if (Scope.Value == RankingsScope.Spotlights)
return;
Scheduler.AddOnce(loadNewContent);
}, true);
base.LoadComplete(); base.LoadComplete();
} }
@ -134,17 +140,26 @@ namespace osu.Game.Overlays
cancellationToken?.Cancel(); cancellationToken?.Cancel();
lastRequest?.Cancel(); lastRequest?.Cancel();
if (Scope.Value == RankingsScope.Spotlights)
{
loadContent(new SpotlightsLayout
{
Ruleset = { BindTarget = ruleset }
});
return;
}
var request = createScopedRequest(); var request = createScopedRequest();
lastRequest = request; lastRequest = request;
if (request == null) if (request == null)
{ {
loadTable(null); loadContent(null);
return; return;
} }
request.Success += () => loadTable(createTableFromResponse(request)); request.Success += () => loadContent(createTableFromResponse(request));
request.Failure += _ => loadTable(null); request.Failure += _ => loadContent(null);
api.Queue(request); api.Queue(request);
} }
@ -189,21 +204,21 @@ namespace osu.Game.Overlays
return null; return null;
} }
private void loadTable(Drawable table) private void loadContent(Drawable content)
{ {
scrollFlow.ScrollToStart(); scrollFlow.ScrollToStart();
if (table == null) if (content == null)
{ {
tableContainer.Clear(); contentContainer.Clear();
loading.Hide(); loading.Hide();
return; return;
} }
LoadComponentAsync(table, t => LoadComponentAsync(content, loaded =>
{ {
loading.Hide(); loading.Hide();
tableContainer.Child = table; contentContainer.Child = loaded;
}, (cancellationToken = new CancellationTokenSource()).Token); }, (cancellationToken = new CancellationTokenSource()).Token);
} }
} }

View File

@ -251,15 +251,22 @@ namespace osu.Game.Rulesets.Edit
public void BeginPlacement(HitObject hitObject) public void BeginPlacement(HitObject hitObject)
{ {
EditorBeatmap.PlacementObject.Value = hitObject;
if (distanceSnapGrid != null) if (distanceSnapGrid != null)
hitObject.StartTime = GetSnappedPosition(distanceSnapGrid.ToLocalSpace(inputManager.CurrentState.Mouse.Position), hitObject.StartTime).time; hitObject.StartTime = GetSnappedPosition(distanceSnapGrid.ToLocalSpace(inputManager.CurrentState.Mouse.Position), hitObject.StartTime).time;
} }
public void EndPlacement(HitObject hitObject) public void EndPlacement(HitObject hitObject, bool commit)
{
EditorBeatmap.PlacementObject.Value = null;
if (commit)
{ {
EditorBeatmap.Add(hitObject); EditorBeatmap.Add(hitObject);
adjustableClock.Seek(hitObject.StartTime); adjustableClock.Seek(hitObject.GetEndTime());
}
showGridFor(Enumerable.Empty<HitObject>()); showGridFor(Enumerable.Empty<HitObject>());
} }

View File

@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using System;
using osu.Framework;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Graphics; using osu.Framework.Graphics;
@ -20,17 +18,12 @@ namespace osu.Game.Rulesets.Edit
/// <summary> /// <summary>
/// A blueprint which governs the creation of a new <see cref="HitObject"/> to actualisation. /// A blueprint which governs the creation of a new <see cref="HitObject"/> to actualisation.
/// </summary> /// </summary>
public abstract class PlacementBlueprint : CompositeDrawable, IStateful<PlacementState> public abstract class PlacementBlueprint : CompositeDrawable
{ {
/// <summary> /// <summary>
/// Invoked when <see cref="State"/> has changed. /// Whether the <see cref="HitObject"/> is currently mid-placement, but has not necessarily finished being placed.
/// </summary> /// </summary>
public event Action<PlacementState> StateChanged; public bool PlacementActive { get; private set; }
/// <summary>
/// Whether the <see cref="HitObject"/> is currently being placed, but has not necessarily finished being placed.
/// </summary>
public bool PlacementBegun { get; private set; }
/// <summary> /// <summary>
/// The <see cref="HitObject"/> that is being placed. /// The <see cref="HitObject"/> that is being placed.
@ -53,8 +46,6 @@ namespace osu.Game.Rulesets.Edit
// This is required to allow the blueprint's position to be updated via OnMouseMove/Handle // This is required to allow the blueprint's position to be updated via OnMouseMove/Handle
// on the same frame it is made visible via a PlacementState change. // on the same frame it is made visible via a PlacementState change.
AlwaysPresent = true; AlwaysPresent = true;
Alpha = 0;
} }
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
@ -67,47 +58,29 @@ namespace osu.Game.Rulesets.Edit
ApplyDefaultsToHitObject(); ApplyDefaultsToHitObject();
} }
private PlacementState state;
public PlacementState State
{
get => state;
set
{
if (state == value)
return;
state = value;
if (state == PlacementState.Shown)
Show();
else
Hide();
StateChanged?.Invoke(value);
}
}
/// <summary> /// <summary>
/// Signals that the placement of <see cref="HitObject"/> has started. /// Signals that the placement of <see cref="HitObject"/> has started.
/// </summary> /// </summary>
/// <param name="startTime">The start time of <see cref="HitObject"/> at the placement point. If null, the current clock time is used.</param> /// <param name="startTime">The start time of <see cref="HitObject"/> at the placement point. If null, the current clock time is used.</param>
protected void BeginPlacement(double? startTime = null) /// <param name="commitStart">Whether this call is committing a value for HitObject.StartTime and continuing with further adjustments.</param>
protected void BeginPlacement(double? startTime = null, bool commitStart = false)
{ {
HitObject.StartTime = startTime ?? EditorClock.CurrentTime; HitObject.StartTime = startTime ?? EditorClock.CurrentTime;
placementHandler.BeginPlacement(HitObject); placementHandler.BeginPlacement(HitObject);
PlacementBegun = true; PlacementActive |= commitStart;
} }
/// <summary> /// <summary>
/// Signals that the placement of <see cref="HitObject"/> has finished. /// Signals that the placement of <see cref="HitObject"/> has finished.
/// This will destroy this <see cref="PlacementBlueprint"/>, and add the <see cref="HitObject"/> to the <see cref="Beatmap"/>. /// This will destroy this <see cref="PlacementBlueprint"/>, and add the HitObject.StartTime to the <see cref="Beatmap"/>.
/// </summary> /// </summary>
protected void EndPlacement() /// <param name="commit">Whether the object should be committed.</param>
public void EndPlacement(bool commit)
{ {
if (!PlacementBegun) if (!PlacementActive)
BeginPlacement(); BeginPlacement();
placementHandler.EndPlacement(HitObject); placementHandler.EndPlacement(HitObject, commit);
PlacementActive = false;
} }
/// <summary> /// <summary>
@ -141,10 +114,4 @@ namespace osu.Game.Rulesets.Edit
} }
} }
} }
public enum PlacementState
{
Hidden,
Shown,
}
} }

View File

@ -28,7 +28,11 @@ namespace osu.Game.Rulesets.Mods
public override Type[] IncompatibleMods => new[] { typeof(ModEasy), typeof(ModHardRock) }; public override Type[] IncompatibleMods => new[] { typeof(ModEasy), typeof(ModHardRock) };
[SettingSource("Drain Rate", "Override a beatmap's set HP.")] protected const int FIRST_SETTING_ORDER = 1;
protected const int LAST_SETTING_ORDER = 2;
[SettingSource("HP Drain", "Override a beatmap's set HP.", FIRST_SETTING_ORDER)]
public BindableNumber<float> DrainRate { get; } = new BindableFloat public BindableNumber<float> DrainRate { get; } = new BindableFloat
{ {
Precision = 0.1f, Precision = 0.1f,
@ -38,7 +42,7 @@ namespace osu.Game.Rulesets.Mods
Value = 5, Value = 5,
}; };
[SettingSource("Overall Difficulty", "Override a beatmap's set OD.")] [SettingSource("Accuracy", "Override a beatmap's set OD.", LAST_SETTING_ORDER)]
public BindableNumber<float> OverallDifficulty { get; } = new BindableFloat public BindableNumber<float> OverallDifficulty { get; } = new BindableFloat
{ {
Precision = 0.1f, Precision = 0.1f,

View File

@ -32,7 +32,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
protected DragBox DragBox { get; private set; } protected DragBox DragBox { get; private set; }
private Container<SelectionBlueprint> selectionBlueprints; protected Container<SelectionBlueprint> SelectionBlueprints { get; private set; }
private SelectionHandler selectionHandler; private SelectionHandler selectionHandler;
@ -62,7 +62,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
{ {
DragBox = CreateDragBox(select), DragBox = CreateDragBox(select),
selectionHandler, selectionHandler,
selectionBlueprints = CreateSelectionBlueprintContainer(), SelectionBlueprints = CreateSelectionBlueprintContainer(),
DragBox.CreateProxy().With(p => p.Depth = float.MinValue) DragBox.CreateProxy().With(p => p.Depth = float.MinValue)
}); });
@ -73,7 +73,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
selectedHitObjects.ItemsAdded += objects => selectedHitObjects.ItemsAdded += objects =>
{ {
foreach (var o in objects) foreach (var o in objects)
selectionBlueprints.FirstOrDefault(b => b.HitObject == o)?.Select(); SelectionBlueprints.FirstOrDefault(b => b.HitObject == o)?.Select();
SelectionChanged?.Invoke(selectedHitObjects); SelectionChanged?.Invoke(selectedHitObjects);
}; };
@ -81,7 +81,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
selectedHitObjects.ItemsRemoved += objects => selectedHitObjects.ItemsRemoved += objects =>
{ {
foreach (var o in objects) foreach (var o in objects)
selectionBlueprints.FirstOrDefault(b => b.HitObject == o)?.Deselect(); SelectionBlueprints.FirstOrDefault(b => b.HitObject == o)?.Deselect();
SelectionChanged?.Invoke(selectedHitObjects); SelectionChanged?.Invoke(selectedHitObjects);
}; };
@ -230,7 +230,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
private void removeBlueprintFor(HitObject hitObject) private void removeBlueprintFor(HitObject hitObject)
{ {
var blueprint = selectionBlueprints.SingleOrDefault(m => m.HitObject == hitObject); var blueprint = SelectionBlueprints.SingleOrDefault(m => m.HitObject == hitObject);
if (blueprint == null) if (blueprint == null)
return; return;
@ -239,7 +239,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
blueprint.Selected -= onBlueprintSelected; blueprint.Selected -= onBlueprintSelected;
blueprint.Deselected -= onBlueprintDeselected; blueprint.Deselected -= onBlueprintDeselected;
selectionBlueprints.Remove(blueprint); SelectionBlueprints.Remove(blueprint);
} }
protected virtual void AddBlueprintFor(HitObject hitObject) protected virtual void AddBlueprintFor(HitObject hitObject)
@ -251,7 +251,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
blueprint.Selected += onBlueprintSelected; blueprint.Selected += onBlueprintSelected;
blueprint.Deselected += onBlueprintDeselected; blueprint.Deselected += onBlueprintDeselected;
selectionBlueprints.Add(blueprint); SelectionBlueprints.Add(blueprint);
} }
#endregion #endregion
@ -278,7 +278,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
if (!allowDeselection && selectionHandler.SelectedBlueprints.Any(s => s.IsHovered)) if (!allowDeselection && selectionHandler.SelectedBlueprints.Any(s => s.IsHovered))
return; return;
foreach (SelectionBlueprint blueprint in selectionBlueprints.AliveChildren) foreach (SelectionBlueprint blueprint in SelectionBlueprints.AliveChildren)
{ {
if (blueprint.IsHovered) if (blueprint.IsHovered)
{ {
@ -308,7 +308,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
/// <param name="rect">The rectangle to perform a selection on in screen-space coordinates.</param> /// <param name="rect">The rectangle to perform a selection on in screen-space coordinates.</param>
private void select(RectangleF rect) private void select(RectangleF rect)
{ {
foreach (var blueprint in selectionBlueprints) foreach (var blueprint in SelectionBlueprints)
{ {
if (blueprint.IsAlive && blueprint.IsPresent && rect.Contains(blueprint.SelectionPoint)) if (blueprint.IsAlive && blueprint.IsPresent && rect.Contains(blueprint.SelectionPoint))
blueprint.Select(); blueprint.Select();
@ -322,7 +322,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
/// </summary> /// </summary>
private void selectAll() private void selectAll()
{ {
selectionBlueprints.ToList().ForEach(m => m.Select()); SelectionBlueprints.ToList().ForEach(m => m.Select());
selectionHandler.UpdateVisibility(); selectionHandler.UpdateVisibility();
} }
@ -334,14 +334,14 @@ namespace osu.Game.Screens.Edit.Compose.Components
private void onBlueprintSelected(SelectionBlueprint blueprint) private void onBlueprintSelected(SelectionBlueprint blueprint)
{ {
selectionHandler.HandleSelected(blueprint); selectionHandler.HandleSelected(blueprint);
selectionBlueprints.ChangeChildDepth(blueprint, 1); SelectionBlueprints.ChangeChildDepth(blueprint, 1);
beatmap.SelectedHitObjects.Add(blueprint.HitObject); beatmap.SelectedHitObjects.Add(blueprint.HitObject);
} }
private void onBlueprintDeselected(SelectionBlueprint blueprint) private void onBlueprintDeselected(SelectionBlueprint blueprint)
{ {
selectionHandler.HandleDeselected(blueprint); selectionHandler.HandleDeselected(blueprint);
selectionBlueprints.ChangeChildDepth(blueprint, 0); SelectionBlueprints.ChangeChildDepth(blueprint, 0);
beatmap.SelectedHitObjects.Remove(blueprint.HitObject); beatmap.SelectedHitObjects.Remove(blueprint.HitObject);
} }

View File

@ -62,18 +62,8 @@ namespace osu.Game.Screens.Edit.Compose.Components
/// </summary> /// </summary>
private void refreshTool() private void refreshTool()
{ {
placementBlueprintContainer.Clear(); removePlacement();
currentPlacement = null; createPlacement();
var blueprint = CurrentTool?.CreatePlacementBlueprint();
if (blueprint != null)
{
placementBlueprintContainer.Child = currentPlacement = blueprint;
// Fixes a 1-frame position discrepancy due to the first mouse move event happening in the next frame
updatePlacementPosition(inputManager.CurrentState.Mouse.Position);
}
} }
private void updatePlacementPosition(Vector2 screenSpacePosition) private void updatePlacementPosition(Vector2 screenSpacePosition)
@ -101,18 +91,16 @@ namespace osu.Game.Screens.Edit.Compose.Components
{ {
base.Update(); base.Update();
if (currentPlacement != null)
{
if (composer.CursorInPlacementArea) if (composer.CursorInPlacementArea)
currentPlacement.State = PlacementState.Shown; createPlacement();
else if (currentPlacement?.PlacementBegun == false) else if (currentPlacement?.PlacementActive == false)
currentPlacement.State = PlacementState.Hidden; removePlacement();
}
} }
protected sealed override SelectionBlueprint CreateBlueprintFor(HitObject hitObject) protected sealed override SelectionBlueprint CreateBlueprintFor(HitObject hitObject)
{ {
var drawable = drawableHitObjects.FirstOrDefault(d => d.HitObject == hitObject); var drawable = drawableHitObjects.FirstOrDefault(d => d.HitObject == hitObject);
if (drawable == null) if (drawable == null)
return null; return null;
@ -127,6 +115,30 @@ namespace osu.Game.Screens.Edit.Compose.Components
base.AddBlueprintFor(hitObject); base.AddBlueprintFor(hitObject);
} }
private void createPlacement()
{
if (currentPlacement != null) return;
var blueprint = CurrentTool?.CreatePlacementBlueprint();
if (blueprint != null)
{
placementBlueprintContainer.Child = currentPlacement = blueprint;
// Fixes a 1-frame position discrepancy due to the first mouse move event happening in the next frame
updatePlacementPosition(inputManager.CurrentState.Mouse.Position);
}
}
private void removePlacement()
{
if (currentPlacement == null) return;
currentPlacement.EndPlacement(false);
currentPlacement.Expire();
currentPlacement = null;
}
private HitObjectCompositionTool currentTool; private HitObjectCompositionTool currentTool;
/// <summary> /// <summary>
@ -135,6 +147,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
public HitObjectCompositionTool CurrentTool public HitObjectCompositionTool CurrentTool
{ {
get => currentTool; get => currentTool;
set set
{ {
if (currentTool == value) if (currentTool == value)

View File

@ -3,6 +3,7 @@
using System; using System;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Primitives; using osu.Framework.Graphics.Primitives;
@ -21,8 +22,15 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
[Resolved(CanBeNull = true)] [Resolved(CanBeNull = true)]
private Timeline timeline { get; set; } private Timeline timeline { get; set; }
[Resolved]
private EditorBeatmap beatmap { get; set; }
private DragEvent lastDragEvent; private DragEvent lastDragEvent;
private Bindable<HitObject> placement;
private SelectionBlueprint placementBlueprint;
public TimelineBlueprintContainer() public TimelineBlueprintContainer()
{ {
RelativeSizeAxes = Axes.Both; RelativeSizeAxes = Axes.Both;
@ -43,6 +51,29 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
{ {
base.LoadComplete(); base.LoadComplete();
DragBox.Alpha = 0; DragBox.Alpha = 0;
placement = beatmap.PlacementObject.GetBoundCopy();
placement.ValueChanged += placementChanged;
}
private void placementChanged(ValueChangedEvent<HitObject> obj)
{
if (obj.NewValue == null)
{
if (placementBlueprint != null)
{
SelectionBlueprints.Remove(placementBlueprint);
placementBlueprint = null;
}
}
else
{
placementBlueprint = CreateBlueprintFor(obj.NewValue);
placementBlueprint.Colour = Color4.MediumPurple;
SelectionBlueprints.Add(placementBlueprint);
}
} }
protected override Container<SelectionBlueprint> CreateSelectionBlueprintContainer() => new TimelineSelectionBlueprintContainer { RelativeSizeAxes = Axes.Both }; protected override Container<SelectionBlueprint> CreateSelectionBlueprintContainer() => new TimelineSelectionBlueprintContainer { RelativeSizeAxes = Axes.Both };

View File

@ -6,10 +6,13 @@ using osu.Framework.Bindables;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Sprites;
using osu.Framework.Input.Events;
using osu.Framework.Threading;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterface;
using osuTK; using osuTK;
using osuTK.Graphics; using osuTK.Graphics;
using osuTK.Input;
namespace osu.Game.Screens.Edit.Compose.Components.Timeline namespace osu.Game.Screens.Edit.Compose.Components.Timeline
{ {
@ -52,6 +55,45 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
HoverColour = OsuColour.Gray(0.25f); HoverColour = OsuColour.Gray(0.25f);
FlashColour = OsuColour.Gray(0.5f); FlashColour = OsuColour.Gray(0.5f);
} }
private ScheduledDelegate repeatSchedule;
/// <summary>
/// The initial delay before mouse down repeat begins.
/// </summary>
private const int repeat_initial_delay = 250;
/// <summary>
/// The delay between mouse down repeats after the initial repeat.
/// </summary>
private const int repeat_tick_rate = 70;
protected override bool OnClick(ClickEvent e)
{
// don't actuate a click since we are manually handling repeats.
return true;
}
protected override bool OnMouseDown(MouseDownEvent e)
{
if (e.Button == MouseButton.Left)
{
Action clickAction = () => base.OnClick(new ClickEvent(e.CurrentState, e.Button));
// run once for initial down
clickAction();
Scheduler.Add(repeatSchedule = new ScheduledDelegate(clickAction, Clock.CurrentTime + repeat_initial_delay, repeat_tick_rate));
}
return base.OnMouseDown(e);
}
protected override void OnMouseUp(MouseUpEvent e)
{
repeatSchedule?.Cancel();
base.OnMouseUp(e);
}
} }
} }
} }

View File

@ -17,7 +17,8 @@ namespace osu.Game.Screens.Edit.Compose
/// Notifies that a placement has finished. /// Notifies that a placement has finished.
/// </summary> /// </summary>
/// <param name="hitObject">The <see cref="HitObject"/> that has been placed.</param> /// <param name="hitObject">The <see cref="HitObject"/> that has been placed.</param>
void EndPlacement(HitObject hitObject); /// <param name="commit">Whether the object should be committed.</param>
void EndPlacement(HitObject hitObject, bool commit);
/// <summary> /// <summary>
/// Deletes a <see cref="HitObject"/>. /// Deletes a <see cref="HitObject"/>.

View File

@ -33,7 +33,15 @@ namespace osu.Game.Screens.Edit
/// </summary> /// </summary>
public event Action<HitObject> StartTimeChanged; public event Action<HitObject> StartTimeChanged;
public BindableList<HitObject> SelectedHitObjects { get; } = new BindableList<HitObject>(); /// <summary>
/// All currently selected <see cref="HitObject"/>s.
/// </summary>
public readonly BindableList<HitObject> SelectedHitObjects = new BindableList<HitObject>();
/// <summary>
/// The current placement. Null if there's no active placement.
/// </summary>
public readonly Bindable<HitObject> PlacementObject = new Bindable<HitObject>();
public readonly IBeatmap PlayableBeatmap; public readonly IBeatmap PlayableBeatmap;

View File

@ -95,7 +95,7 @@ namespace osu.Game.Screens.Menu
private void updateAmplitudes() private void updateAmplitudes()
{ {
var track = beatmap.Value.TrackLoaded ? beatmap.Value.Track : null; var track = beatmap.Value.TrackLoaded ? beatmap.Value.Track : null;
var effect = beatmap.Value.BeatmapLoaded ? beatmap.Value.Beatmap.ControlPointInfo.EffectPointAt(track?.CurrentTime ?? Time.Current) : null; var effect = beatmap.Value.BeatmapLoaded ? beatmap.Value.Beatmap?.ControlPointInfo.EffectPointAt(track?.CurrentTime ?? Time.Current) : null;
float[] temporalAmplitudes = track?.CurrentAmplitudes.FrequencyAmplitudes; float[] temporalAmplitudes = track?.CurrentAmplitudes.FrequencyAmplitudes;

View File

@ -141,12 +141,15 @@ namespace osu.Game.Screens.Menu
preloadSongSelect(); preloadSongSelect();
} }
[Resolved]
private OsuGame game { get; set; }
private void confirmAndExit() private void confirmAndExit()
{ {
if (exitConfirmed) return; if (exitConfirmed) return;
exitConfirmed = true; exitConfirmed = true;
this.Exit(); game.PerformFromScreen(menu => menu.Exit());
} }
private void preloadSongSelect() private void preloadSongSelect()

View File

@ -70,7 +70,7 @@ namespace osu.Game.Screens.Multi.Components
{ {
new OsuSpriteText new OsuSpriteText
{ {
Text = new LocalisedString((beatmap.Metadata.ArtistUnicode, beatmap.Metadata.Artist)), Text = new LocalisedString((beatmap.Value.Metadata.ArtistUnicode, beatmap.Value.Metadata.Artist)),
Font = OsuFont.GetFont(size: TextSize), Font = OsuFont.GetFont(size: TextSize),
}, },
new OsuSpriteText new OsuSpriteText
@ -80,10 +80,10 @@ namespace osu.Game.Screens.Multi.Components
}, },
new OsuSpriteText new OsuSpriteText
{ {
Text = new LocalisedString((beatmap.Metadata.TitleUnicode, beatmap.Metadata.Title)), Text = new LocalisedString((beatmap.Value.Metadata.TitleUnicode, beatmap.Value.Metadata.Title)),
Font = OsuFont.GetFont(size: TextSize), Font = OsuFont.GetFont(size: TextSize),
} }
}, LinkAction.OpenBeatmap, beatmap.OnlineBeatmapID.ToString(), "Open beatmap"); }, LinkAction.OpenBeatmap, beatmap.Value.OnlineBeatmapID.ToString(), "Open beatmap");
} }
} }
} }

View File

@ -59,7 +59,7 @@ namespace osu.Game.Screens.Multi.Components
if (beatmap != null) if (beatmap != null)
{ {
beatmapAuthor.AddText("mapped by ", s => s.Colour = OsuColour.Gray(0.8f)); beatmapAuthor.AddText("mapped by ", s => s.Colour = OsuColour.Gray(0.8f));
beatmapAuthor.AddUserLink(beatmap.Metadata.Author); beatmapAuthor.AddUserLink(beatmap.Value.Metadata.Author);
} }
}, true); }, true);
} }

View File

@ -56,7 +56,7 @@ namespace osu.Game.Screens.Multi.Components
if (item?.Beatmap != null) if (item?.Beatmap != null)
{ {
drawableRuleset.FadeIn(transition_duration); drawableRuleset.FadeIn(transition_duration);
drawableRuleset.Child = new DifficultyIcon(item.Beatmap, item.Ruleset) { Size = new Vector2(height) }; drawableRuleset.Child = new DifficultyIcon(item.Beatmap.Value, item.Ruleset.Value) { Size = new Vector2(height) };
} }
else else
drawableRuleset.FadeOut(transition_duration); drawableRuleset.FadeOut(transition_duration);

View File

@ -23,7 +23,7 @@ namespace osu.Game.Screens.Multi.Components
InternalChild = sprite = CreateBackgroundSprite(); InternalChild = sprite = CreateBackgroundSprite();
CurrentItem.BindValueChanged(item => sprite.Beatmap.Value = item.NewValue?.Beatmap, true); CurrentItem.BindValueChanged(item => sprite.Beatmap.Value = item.NewValue?.Beatmap.Value, true);
} }
protected virtual UpdateableBeatmapBackgroundSprite CreateBackgroundSprite() => new UpdateableBeatmapBackgroundSprite(beatmapSetCoverType) { RelativeSizeAxes = Axes.Both }; protected virtual UpdateableBeatmapBackgroundSprite CreateBackgroundSprite() => new UpdateableBeatmapBackgroundSprite(beatmapSetCoverType) { RelativeSizeAxes = Axes.Both };

View File

@ -68,7 +68,7 @@ namespace osu.Game.Screens.Multi.Lounge.Components
{ {
bool matchingFilter = true; bool matchingFilter = true;
matchingFilter &= r.Room.Playlist.Count == 0 || r.Room.Playlist.Any(i => i.Ruleset.Equals(criteria.Ruleset)); matchingFilter &= r.Room.Playlist.Count == 0 || r.Room.Playlist.Any(i => i.Ruleset.Value.Equals(criteria.Ruleset));
if (!string.IsNullOrEmpty(criteria.SearchString)) if (!string.IsNullOrEmpty(criteria.SearchString))
matchingFilter &= r.FilterTerms.Any(term => term.IndexOf(criteria.SearchString, StringComparison.InvariantCultureIgnoreCase) >= 0); matchingFilter &= r.FilterTerms.Any(term => term.IndexOf(criteria.SearchString, StringComparison.InvariantCultureIgnoreCase) >= 0);

View File

@ -2,6 +2,7 @@
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using System; using System;
using System.Linq;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Extensions.Color4Extensions;

View File

@ -89,7 +89,7 @@ namespace osu.Game.Screens.Multi.Match.Components
}, },
}; };
CurrentItem.BindValueChanged(item => readyButton.Beatmap.Value = item.NewValue?.Beatmap, true); CurrentItem.BindValueChanged(item => readyButton.Beatmap.Value = item.NewValue?.Beatmap.Value, true);
hostInfo.Host.BindTo(Host); hostInfo.Host.BindTo(Host);
} }

View File

@ -32,7 +32,7 @@ namespace osu.Game.Screens.Multi.Match.Components
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load() private void load()
{ {
CurrentItem.BindValueChanged(item => loadNewPanel(item.NewValue?.Beatmap), true); CurrentItem.BindValueChanged(item => loadNewPanel(item.NewValue?.Beatmap.Value), true);
} }
private void loadNewPanel(BeatmapInfo beatmap) private void loadNewPanel(BeatmapInfo beatmap)

View File

@ -51,9 +51,9 @@ namespace osu.Game.Screens.Multi.Match.Components
}, },
}; };
Participants.BindValueChanged(participants => Participants.ItemsAdded += users =>
{ {
usersFlow.Children = participants.NewValue.Select(u => usersFlow.AddRange(users.Select(u =>
{ {
var panel = new UserPanel(u) var panel = new UserPanel(u)
{ {
@ -65,8 +65,13 @@ namespace osu.Game.Screens.Multi.Match.Components
panel.OnLoadComplete += d => d.FadeInFromZero(60); panel.OnLoadComplete += d => d.FadeInFromZero(60);
return panel; return panel;
}).ToList(); }).ToList());
}, true); };
Participants.ItemsRemoved += users =>
{
usersFlow.RemoveAll(p => users.Contains(p.User));
};
} }
} }
} }

View File

@ -2,6 +2,7 @@
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using System; using System;
using System.Linq;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Graphics; using osu.Framework.Graphics;
@ -183,13 +184,13 @@ namespace osu.Game.Screens.Multi.Match
private void currentItemChanged(ValueChangedEvent<PlaylistItem> e) private void currentItemChanged(ValueChangedEvent<PlaylistItem> e)
{ {
// Retrieve the corresponding local beatmap, since we can't directly use the playlist's beatmap info // Retrieve the corresponding local beatmap, since we can't directly use the playlist's beatmap info
var localBeatmap = e.NewValue?.Beatmap == null ? null : beatmapManager.QueryBeatmap(b => b.OnlineBeatmapID == e.NewValue.Beatmap.OnlineBeatmapID); var localBeatmap = e.NewValue?.Beatmap == null ? null : beatmapManager.QueryBeatmap(b => b.OnlineBeatmapID == e.NewValue.Beatmap.Value.OnlineBeatmapID);
Beatmap.Value = beatmapManager.GetWorkingBeatmap(localBeatmap); Beatmap.Value = beatmapManager.GetWorkingBeatmap(localBeatmap);
Mods.Value = e.NewValue?.RequiredMods?.ToArray() ?? Array.Empty<Mod>(); Mods.Value = e.NewValue?.RequiredMods?.ToArray() ?? Array.Empty<Mod>();
if (e.NewValue?.Ruleset != null) if (e.NewValue?.Ruleset != null)
Ruleset.Value = e.NewValue.Ruleset; Ruleset.Value = e.NewValue.Ruleset.Value;
previewTrackManager.StopAnyPlaying(this); previewTrackManager.StopAnyPlaying(this);
} }
@ -206,7 +207,7 @@ namespace osu.Game.Screens.Multi.Match
return; return;
// Try to retrieve the corresponding local beatmap // Try to retrieve the corresponding local beatmap
var localBeatmap = beatmapManager.QueryBeatmap(b => b.OnlineBeatmapID == CurrentItem.Value.Beatmap.OnlineBeatmapID); var localBeatmap = beatmapManager.QueryBeatmap(b => b.OnlineBeatmapID == CurrentItem.Value.Beatmap.Value.OnlineBeatmapID);
if (localBeatmap != null) if (localBeatmap != null)
Beatmap.Value = beatmapManager.GetWorkingBeatmap(localBeatmap); Beatmap.Value = beatmapManager.GetWorkingBeatmap(localBeatmap);

View File

@ -2,7 +2,6 @@
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using System; using System;
using System.Collections.Generic;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
@ -35,7 +34,7 @@ namespace osu.Game.Screens.Multi
protected Bindable<PlaylistItem> CurrentItem { get; private set; } protected Bindable<PlaylistItem> CurrentItem { get; private set; }
[Resolved(typeof(Room))] [Resolved(typeof(Room))]
protected Bindable<IEnumerable<User>> Participants { get; private set; } protected BindableList<User> Participants { get; private set; }
[Resolved(typeof(Room))] [Resolved(typeof(Room))]
protected Bindable<int> ParticipantCount { get; private set; } protected Bindable<int> ParticipantCount { get; private set; }

View File

@ -50,10 +50,10 @@ namespace osu.Game.Screens.Multi.Play
bool failed = false; bool failed = false;
// Sanity checks to ensure that TimeshiftPlayer matches the settings for the current PlaylistItem // Sanity checks to ensure that TimeshiftPlayer matches the settings for the current PlaylistItem
if (Beatmap.Value.BeatmapInfo.OnlineBeatmapID != playlistItem.Beatmap.OnlineBeatmapID) if (Beatmap.Value.BeatmapInfo.OnlineBeatmapID != playlistItem.Beatmap.Value.OnlineBeatmapID)
throw new InvalidOperationException("Current Beatmap does not match PlaylistItem's Beatmap"); throw new InvalidOperationException("Current Beatmap does not match PlaylistItem's Beatmap");
if (ruleset.Value.ID != playlistItem.Ruleset.ID) if (ruleset.Value.ID != playlistItem.Ruleset.Value.ID)
throw new InvalidOperationException("Current Ruleset does not match PlaylistItem's Ruleset"); throw new InvalidOperationException("Current Ruleset does not match PlaylistItem's Ruleset");
if (!playlistItem.RequiredMods.All(m => Mods.Value.Any(m.Equals))) if (!playlistItem.RequiredMods.All(m => Mods.Value.Any(m.Equals)))

View File

@ -24,7 +24,7 @@ namespace osu.Game.Screens.Play.HUD
public bool DisplayUnrankedText = true; public bool DisplayUnrankedText = true;
public bool AllowExpand = true; public ExpansionMode ExpansionMode = ExpansionMode.ExpandOnHover;
private readonly Bindable<IReadOnlyList<Mod>> current = new Bindable<IReadOnlyList<Mod>>(); private readonly Bindable<IReadOnlyList<Mod>> current = new Bindable<IReadOnlyList<Mod>>();
@ -110,11 +110,15 @@ namespace osu.Game.Screens.Play.HUD
private void expand() private void expand()
{ {
if (AllowExpand) if (ExpansionMode != ExpansionMode.AlwaysContracted)
IconsContainer.TransformSpacingTo(new Vector2(5, 0), 500, Easing.OutQuint); IconsContainer.TransformSpacingTo(new Vector2(5, 0), 500, Easing.OutQuint);
} }
private void contract() => IconsContainer.TransformSpacingTo(new Vector2(-25, 0), 500, Easing.OutQuint); private void contract()
{
if (ExpansionMode != ExpansionMode.AlwaysExpanded)
IconsContainer.TransformSpacingTo(new Vector2(-25, 0), 500, Easing.OutQuint);
}
protected override bool OnHover(HoverEvent e) protected override bool OnHover(HoverEvent e)
{ {
@ -128,4 +132,22 @@ namespace osu.Game.Screens.Play.HUD
base.OnHoverLost(e); base.OnHoverLost(e);
} }
} }
public enum ExpansionMode
{
/// <summary>
/// The <see cref="ModDisplay"/> will expand only when hovered.
/// </summary>
ExpandOnHover,
/// <summary>
/// The <see cref="ModDisplay"/> will always be expanded.
/// </summary>
AlwaysExpanded,
/// <summary>
/// The <see cref="ModDisplay"/> will always be contracted.
/// </summary>
AlwaysContracted
}
} }

View File

@ -229,6 +229,17 @@ namespace osu.Game.Screens.Select
if (item != null) if (item != null)
{ {
select(item); select(item);
// if we got here and the set is filtered, it means we were bypassing filters.
// in this case, reapplying the filter is necessary to ensure the panel is in the correct place
// (since it is forcefully being included in the carousel).
if (set.Filtered.Value)
{
Debug.Assert(bypassFilters);
applyActiveCriteria(false, true);
}
return true; return true;
} }
} }

View File

@ -62,7 +62,7 @@ namespace osu.Game.Screens.Select.Carousel
/// <summary> /// <summary>
/// All beatmaps which are not filtered and valid for display. /// All beatmaps which are not filtered and valid for display.
/// </summary> /// </summary>
protected IEnumerable<BeatmapInfo> ValidBeatmaps => Beatmaps.Where(b => !b.Filtered.Value).Select(b => b.Beatmap); protected IEnumerable<BeatmapInfo> ValidBeatmaps => Beatmaps.Where(b => !b.Filtered.Value || b.State.Value == CarouselItemState.Selected).Select(b => b.Beatmap);
private int compareUsingAggregateMax(CarouselBeatmapSet other, Func<BeatmapInfo, double> func) private int compareUsingAggregateMax(CarouselBeatmapSet other, Func<BeatmapInfo, double> func)
{ {

View File

@ -16,7 +16,7 @@ namespace osu.Game.Screens.Select.Carousel
/// <summary> /// <summary>
/// This item is not in a hidden state. /// This item is not in a hidden state.
/// </summary> /// </summary>
public bool Visible => State.Value != CarouselItemState.Collapsed && !Filtered.Value; public bool Visible => State.Value == CarouselItemState.Selected || (State.Value != CarouselItemState.Collapsed && !Filtered.Value);
public virtual List<DrawableCarouselItem> Drawables public virtual List<DrawableCarouselItem> Drawables
{ {

View File

@ -91,7 +91,7 @@ namespace osu.Game.Screens.Select
public FooterModDisplay() public FooterModDisplay()
{ {
AllowExpand = false; ExpansionMode = ExpansionMode.AlwaysContracted;
IconsContainer.Margin = new MarginPadding(); IconsContainer.Margin = new MarginPadding();
} }
} }

View File

@ -2,6 +2,7 @@
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using System; using System;
using System.Linq;
using Humanizer; using Humanizer;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Bindables; using osu.Framework.Bindables;
@ -38,8 +39,8 @@ namespace osu.Game.Screens.Select
{ {
var item = new PlaylistItem var item = new PlaylistItem
{ {
Beatmap = Beatmap.Value.BeatmapInfo, Beatmap = { Value = Beatmap.Value.BeatmapInfo },
Ruleset = Ruleset.Value, Ruleset = { Value = Ruleset.Value },
RulesetID = Ruleset.Value.ID ?? 0 RulesetID = Ruleset.Value.ID ?? 0
}; };
@ -60,8 +61,8 @@ namespace osu.Game.Screens.Select
if (CurrentItem.Value != null) if (CurrentItem.Value != null)
{ {
Ruleset.Value = CurrentItem.Value.Ruleset; Ruleset.Value = CurrentItem.Value.Ruleset.Value;
Beatmap.Value = beatmaps.GetWorkingBeatmap(CurrentItem.Value.Beatmap); Beatmap.Value = beatmaps.GetWorkingBeatmap(CurrentItem.Value.Beatmap.Value);
Mods.Value = CurrentItem.Value.RequiredMods?.ToArray() ?? Array.Empty<Mod>(); Mods.Value = CurrentItem.Value.RequiredMods?.ToArray() ?? Array.Empty<Mod>();
} }

View File

@ -376,16 +376,22 @@ namespace osu.Game.Screens.Select
private void workingBeatmapChanged(ValueChangedEvent<WorkingBeatmap> e) private void workingBeatmapChanged(ValueChangedEvent<WorkingBeatmap> e)
{ {
if (e.NewValue is DummyWorkingBeatmap) return; if (e.NewValue is DummyWorkingBeatmap || !this.IsCurrentScreen()) return;
if (this.IsCurrentScreen() && !Carousel.SelectBeatmap(e.NewValue?.BeatmapInfo, false)) if (!Carousel.SelectBeatmap(e.NewValue.BeatmapInfo, false))
{ {
// If selecting new beatmap without bypassing filters failed, there's possibly a ruleset mismatch // A selection may not have been possible with filters applied.
if (e.NewValue?.BeatmapInfo?.Ruleset != null && !e.NewValue.BeatmapInfo.Ruleset.Equals(decoupledRuleset.Value))
// There was possibly a ruleset mismatch. This is a case we can help things along by updating the game-wide ruleset to match.
if (e.NewValue.BeatmapInfo.Ruleset != null && !e.NewValue.BeatmapInfo.Ruleset.Equals(decoupledRuleset.Value))
{ {
Ruleset.Value = e.NewValue.BeatmapInfo.Ruleset; Ruleset.Value = e.NewValue.BeatmapInfo.Ruleset;
Carousel.SelectBeatmap(e.NewValue.BeatmapInfo); transferRulesetValue();
} }
// Even if a ruleset mismatch was not the cause (ie. a text filter is applied),
// we still want to forcefully show the new beatmap, bypassing filters.
Carousel.SelectBeatmap(e.NewValue.BeatmapInfo);
} }
} }

View File

@ -191,9 +191,9 @@ namespace osu.Game.Tests.Visual
track = audio?.Tracks.GetVirtual(length); track = audio?.Tracks.GetVirtual(length);
} }
protected override void Dispose(bool isDisposing) ~ClockBackedTestWorkingBeatmap()
{ {
base.Dispose(isDisposing); // Remove the track store from the audio manager
store?.Dispose(); store?.Dispose();
} }

View File

@ -53,8 +53,9 @@ namespace osu.Game.Tests.Visual
{ {
} }
public void EndPlacement(HitObject hitObject) public void EndPlacement(HitObject hitObject, bool commit)
{ {
if (commit)
AddHitObject(CreateHitObject(hitObject)); AddHitObject(CreateHitObject(hitObject));
Remove(currentBlueprint); Remove(currentBlueprint);

View File

@ -26,11 +26,12 @@ namespace osu.Game.Users
{ {
public class UserPanel : OsuClickableContainer, IHasContextMenu public class UserPanel : OsuClickableContainer, IHasContextMenu
{ {
private readonly User user;
private const float height = 100; private const float height = 100;
private const float content_padding = 10; private const float content_padding = 10;
private const float status_height = 30; private const float status_height = 30;
public readonly User User;
[Resolved(canBeNull: true)] [Resolved(canBeNull: true)]
private OsuColour colours { get; set; } private OsuColour colours { get; set; }
@ -54,7 +55,7 @@ namespace osu.Game.Users
if (user == null) if (user == null)
throw new ArgumentNullException(nameof(user)); throw new ArgumentNullException(nameof(user));
this.user = user; User = user;
Height = height - status_height; Height = height - status_height;
} }
@ -86,7 +87,7 @@ namespace osu.Game.Users
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
Anchor = Anchor.Centre, Anchor = Anchor.Centre,
Origin = Anchor.Centre, Origin = Anchor.Centre,
User = user, User = User,
}, 300, 5000) }, 300, 5000)
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
@ -106,7 +107,7 @@ namespace osu.Game.Users
new UpdateableAvatar new UpdateableAvatar
{ {
Size = new Vector2(height - status_height - content_padding * 2), Size = new Vector2(height - status_height - content_padding * 2),
User = user, User = User,
Masking = true, Masking = true,
CornerRadius = 5, CornerRadius = 5,
OpenOnClick = { Value = false }, OpenOnClick = { Value = false },
@ -125,7 +126,7 @@ namespace osu.Game.Users
{ {
new OsuSpriteText new OsuSpriteText
{ {
Text = user.Username, Text = User.Username,
Font = OsuFont.GetFont(weight: FontWeight.SemiBold, size: 18, italics: true), Font = OsuFont.GetFont(weight: FontWeight.SemiBold, size: 18, italics: true),
}, },
infoContainer = new FillFlowContainer infoContainer = new FillFlowContainer
@ -138,7 +139,7 @@ namespace osu.Game.Users
Spacing = new Vector2(5f, 0f), Spacing = new Vector2(5f, 0f),
Children = new Drawable[] Children = new Drawable[]
{ {
new UpdateableFlag(user.Country) new UpdateableFlag(User.Country)
{ {
Width = 30f, Width = 30f,
RelativeSizeAxes = Axes.Y, RelativeSizeAxes = Axes.Y,
@ -191,12 +192,12 @@ namespace osu.Game.Users
} }
}); });
if (user.IsSupporter) if (User.IsSupporter)
{ {
infoContainer.Add(new SupporterIcon infoContainer.Add(new SupporterIcon
{ {
Height = 20f, Height = 20f,
SupportLevel = user.SupportLevel SupportLevel = User.SupportLevel
}); });
} }
@ -206,7 +207,7 @@ namespace osu.Game.Users
base.Action = ViewProfile = () => base.Action = ViewProfile = () =>
{ {
Action?.Invoke(); Action?.Invoke();
profile?.ShowUser(user); profile?.ShowUser(User);
}; };
} }

View File

@ -23,8 +23,8 @@
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="2.2.6" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="2.2.6" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" /> <PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
<PackageReference Include="ppy.osu.Game.Resources" Version="2019.1230.0" /> <PackageReference Include="ppy.osu.Game.Resources" Version="2019.1230.0" />
<PackageReference Include="ppy.osu.Framework" Version="2020.207.0" /> <PackageReference Include="ppy.osu.Framework" Version="2020.213.0" />
<PackageReference Include="Sentry" Version="2.0.1" /> <PackageReference Include="Sentry" Version="2.0.2" />
<PackageReference Include="SharpCompress" Version="0.24.0" /> <PackageReference Include="SharpCompress" Version="0.24.0" />
<PackageReference Include="NUnit" Version="3.12.0" /> <PackageReference Include="NUnit" Version="3.12.0" />
<PackageReference Include="System.ComponentModel.Annotations" Version="4.7.0" /> <PackageReference Include="System.ComponentModel.Annotations" Version="4.7.0" />

View File

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