mirror of
https://github.com/osukey/osukey.git
synced 2025-05-29 17:37:23 +09:00
Merge remote-tracking branch 'refs/remotes/ppy/master' into profile-sections-update
This commit is contained in:
commit
e5d0f3b657
@ -52,6 +52,6 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2020.904.0" />
|
<PackageReference Include="ppy.osu.Game.Resources" Version="2020.904.0" />
|
||||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2020.907.0" />
|
<PackageReference Include="ppy.osu.Framework.Android" Version="2020.910.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
@ -8,6 +8,5 @@ namespace osu.Game.Rulesets.Catch.Difficulty
|
|||||||
public class CatchDifficultyAttributes : DifficultyAttributes
|
public class CatchDifficultyAttributes : DifficultyAttributes
|
||||||
{
|
{
|
||||||
public double ApproachRate;
|
public double ApproachRate;
|
||||||
public int MaxCombo;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,6 +11,7 @@ using osu.Game.Rulesets.Mania.Beatmaps;
|
|||||||
using osu.Game.Rulesets.Mania.Difficulty.Preprocessing;
|
using osu.Game.Rulesets.Mania.Difficulty.Preprocessing;
|
||||||
using osu.Game.Rulesets.Mania.Difficulty.Skills;
|
using osu.Game.Rulesets.Mania.Difficulty.Skills;
|
||||||
using osu.Game.Rulesets.Mania.Mods;
|
using osu.Game.Rulesets.Mania.Mods;
|
||||||
|
using osu.Game.Rulesets.Mania.Objects;
|
||||||
using osu.Game.Rulesets.Mania.Scoring;
|
using osu.Game.Rulesets.Mania.Scoring;
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
using osu.Game.Rulesets.Scoring;
|
using osu.Game.Rulesets.Scoring;
|
||||||
@ -43,6 +44,7 @@ namespace osu.Game.Rulesets.Mania.Difficulty
|
|||||||
Mods = mods,
|
Mods = mods,
|
||||||
// Todo: This int cast is temporary to achieve 1:1 results with osu!stable, and should be removed in the future
|
// Todo: This int cast is temporary to achieve 1:1 results with osu!stable, and should be removed in the future
|
||||||
GreatHitWindow = (int)(hitWindows.WindowFor(HitResult.Great)) / clockRate,
|
GreatHitWindow = (int)(hitWindows.WindowFor(HitResult.Great)) / clockRate,
|
||||||
|
MaxCombo = beatmap.HitObjects.Sum(h => h is HoldNote ? 2 : 1),
|
||||||
Skills = skills
|
Skills = skills
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Rulesets.Edit;
|
using osu.Game.Rulesets.Edit;
|
||||||
using osu.Game.Rulesets.Edit.Tools;
|
using osu.Game.Rulesets.Edit.Tools;
|
||||||
using osu.Game.Rulesets.Mania.Edit.Blueprints;
|
using osu.Game.Rulesets.Mania.Edit.Blueprints;
|
||||||
@ -14,6 +16,8 @@ namespace osu.Game.Rulesets.Mania.Edit
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override Drawable CreateIcon() => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Sliders);
|
||||||
|
|
||||||
public override PlacementBlueprint CreatePlacementBlueprint() => new HoldNotePlacementBlueprint();
|
public override PlacementBlueprint CreatePlacementBlueprint() => new HoldNotePlacementBlueprint();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Rulesets.Edit;
|
using osu.Game.Rulesets.Edit;
|
||||||
using osu.Game.Rulesets.Edit.Tools;
|
using osu.Game.Rulesets.Edit.Tools;
|
||||||
using osu.Game.Rulesets.Mania.Edit.Blueprints;
|
using osu.Game.Rulesets.Mania.Edit.Blueprints;
|
||||||
@ -15,6 +17,8 @@ namespace osu.Game.Rulesets.Mania.Edit
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override Drawable CreateIcon() => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Circles);
|
||||||
|
|
||||||
public override PlacementBlueprint CreatePlacementBlueprint() => new NotePlacementBlueprint();
|
public override PlacementBlueprint CreatePlacementBlueprint() => new NotePlacementBlueprint();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,6 +11,5 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
|||||||
public double SpeedStrain;
|
public double SpeedStrain;
|
||||||
public double ApproachRate;
|
public double ApproachRate;
|
||||||
public double OverallDifficulty;
|
public double OverallDifficulty;
|
||||||
public int MaxCombo;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Rulesets.Edit;
|
using osu.Game.Rulesets.Edit;
|
||||||
using osu.Game.Rulesets.Edit.Tools;
|
using osu.Game.Rulesets.Edit.Tools;
|
||||||
using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles;
|
using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles;
|
||||||
@ -15,6 +17,8 @@ namespace osu.Game.Rulesets.Osu.Edit
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override Drawable CreateIcon() => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Circles);
|
||||||
|
|
||||||
public override PlacementBlueprint CreatePlacementBlueprint() => new HitCirclePlacementBlueprint();
|
public override PlacementBlueprint CreatePlacementBlueprint() => new HitCirclePlacementBlueprint();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,7 @@ using System;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Caching;
|
using osu.Framework.Caching;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
@ -38,6 +39,13 @@ namespace osu.Game.Rulesets.Osu.Edit
|
|||||||
new SpinnerCompositionTool()
|
new SpinnerCompositionTool()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
private readonly BindableBool distanceSnapToggle = new BindableBool(true) { Description = "Distance Snap" };
|
||||||
|
|
||||||
|
protected override IEnumerable<BindableBool> Toggles => new[]
|
||||||
|
{
|
||||||
|
distanceSnapToggle
|
||||||
|
};
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load()
|
private void load()
|
||||||
{
|
{
|
||||||
@ -45,6 +53,7 @@ namespace osu.Game.Rulesets.Osu.Edit
|
|||||||
|
|
||||||
EditorBeatmap.SelectedHitObjects.CollectionChanged += (_, __) => updateDistanceSnapGrid();
|
EditorBeatmap.SelectedHitObjects.CollectionChanged += (_, __) => updateDistanceSnapGrid();
|
||||||
EditorBeatmap.PlacementObject.ValueChanged += _ => updateDistanceSnapGrid();
|
EditorBeatmap.PlacementObject.ValueChanged += _ => updateDistanceSnapGrid();
|
||||||
|
distanceSnapToggle.ValueChanged += _ => updateDistanceSnapGrid();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override ComposeBlueprintContainer CreateBlueprintContainer(IEnumerable<DrawableHitObject> hitObjects)
|
protected override ComposeBlueprintContainer CreateBlueprintContainer(IEnumerable<DrawableHitObject> hitObjects)
|
||||||
@ -87,6 +96,10 @@ namespace osu.Game.Rulesets.Osu.Edit
|
|||||||
{
|
{
|
||||||
distanceSnapGridContainer.Clear();
|
distanceSnapGridContainer.Clear();
|
||||||
distanceSnapGridCache.Invalidate();
|
distanceSnapGridCache.Invalidate();
|
||||||
|
distanceSnapGrid = null;
|
||||||
|
|
||||||
|
if (!distanceSnapToggle.Value)
|
||||||
|
return;
|
||||||
|
|
||||||
switch (BlueprintContainer.CurrentTool)
|
switch (BlueprintContainer.CurrentTool)
|
||||||
{
|
{
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Rulesets.Edit;
|
using osu.Game.Rulesets.Edit;
|
||||||
using osu.Game.Rulesets.Edit.Tools;
|
using osu.Game.Rulesets.Edit.Tools;
|
||||||
using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders;
|
using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders;
|
||||||
@ -15,6 +17,8 @@ namespace osu.Game.Rulesets.Osu.Edit
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override Drawable CreateIcon() => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Sliders);
|
||||||
|
|
||||||
public override PlacementBlueprint CreatePlacementBlueprint() => new SliderPlacementBlueprint();
|
public override PlacementBlueprint CreatePlacementBlueprint() => new SliderPlacementBlueprint();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Rulesets.Edit;
|
using osu.Game.Rulesets.Edit;
|
||||||
using osu.Game.Rulesets.Edit.Tools;
|
using osu.Game.Rulesets.Edit.Tools;
|
||||||
using osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners;
|
using osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners;
|
||||||
@ -15,6 +17,8 @@ namespace osu.Game.Rulesets.Osu.Edit
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override Drawable CreateIcon() => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Spinners);
|
||||||
|
|
||||||
public override PlacementBlueprint CreatePlacementBlueprint() => new SpinnerPlacementBlueprint();
|
public override PlacementBlueprint CreatePlacementBlueprint() => new SpinnerPlacementBlueprint();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -192,7 +192,7 @@ namespace osu.Game.Rulesets.Osu.Statistics
|
|||||||
|
|
||||||
protected void AddPoint(Vector2 start, Vector2 end, Vector2 hitPoint, float radius)
|
protected void AddPoint(Vector2 start, Vector2 end, Vector2 hitPoint, float radius)
|
||||||
{
|
{
|
||||||
if (pointGrid.Content.Length == 0)
|
if (pointGrid.Content.Count == 0)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
double angle1 = Math.Atan2(end.Y - hitPoint.Y, hitPoint.X - end.X); // Angle between the end point and the hit point.
|
double angle1 = Math.Atan2(end.Y - hitPoint.Y, hitPoint.X - end.X); // Angle between the end point and the hit point.
|
||||||
|
@ -8,6 +8,5 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
|
|||||||
public class TaikoDifficultyAttributes : DifficultyAttributes
|
public class TaikoDifficultyAttributes : DifficultyAttributes
|
||||||
{
|
{
|
||||||
public double GreatHitWindow;
|
public double GreatHitWindow;
|
||||||
public int MaxCombo;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Rulesets.Edit;
|
using osu.Game.Rulesets.Edit;
|
||||||
using osu.Game.Rulesets.Edit.Tools;
|
using osu.Game.Rulesets.Edit.Tools;
|
||||||
using osu.Game.Rulesets.Taiko.Edit.Blueprints;
|
using osu.Game.Rulesets.Taiko.Edit.Blueprints;
|
||||||
@ -15,6 +17,8 @@ namespace osu.Game.Rulesets.Taiko.Edit
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override Drawable CreateIcon() => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Sliders);
|
||||||
|
|
||||||
public override PlacementBlueprint CreatePlacementBlueprint() => new DrumRollPlacementBlueprint();
|
public override PlacementBlueprint CreatePlacementBlueprint() => new DrumRollPlacementBlueprint();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Rulesets.Edit;
|
using osu.Game.Rulesets.Edit;
|
||||||
using osu.Game.Rulesets.Edit.Tools;
|
using osu.Game.Rulesets.Edit.Tools;
|
||||||
using osu.Game.Rulesets.Taiko.Edit.Blueprints;
|
using osu.Game.Rulesets.Taiko.Edit.Blueprints;
|
||||||
@ -15,6 +17,8 @@ namespace osu.Game.Rulesets.Taiko.Edit
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override Drawable CreateIcon() => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Circles);
|
||||||
|
|
||||||
public override PlacementBlueprint CreatePlacementBlueprint() => new HitPlacementBlueprint();
|
public override PlacementBlueprint CreatePlacementBlueprint() => new HitPlacementBlueprint();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Rulesets.Edit;
|
using osu.Game.Rulesets.Edit;
|
||||||
using osu.Game.Rulesets.Edit.Tools;
|
using osu.Game.Rulesets.Edit.Tools;
|
||||||
using osu.Game.Rulesets.Taiko.Edit.Blueprints;
|
using osu.Game.Rulesets.Taiko.Edit.Blueprints;
|
||||||
@ -15,6 +17,8 @@ namespace osu.Game.Rulesets.Taiko.Edit
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override Drawable CreateIcon() => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Spinners);
|
||||||
|
|
||||||
public override PlacementBlueprint CreatePlacementBlueprint() => new SwellPlacementBlueprint();
|
public override PlacementBlueprint CreatePlacementBlueprint() => new SwellPlacementBlueprint();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,8 +15,10 @@ using osu.Framework.Extensions;
|
|||||||
using osu.Framework.Logging;
|
using osu.Framework.Logging;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.IO;
|
using osu.Game.IO;
|
||||||
|
using osu.Game.Rulesets.Osu;
|
||||||
using osu.Game.Rulesets.Osu.Objects;
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
using osu.Game.Tests.Resources;
|
using osu.Game.Tests.Resources;
|
||||||
|
using osu.Game.Users;
|
||||||
using SharpCompress.Archives;
|
using SharpCompress.Archives;
|
||||||
using SharpCompress.Archives.Zip;
|
using SharpCompress.Archives.Zip;
|
||||||
using SharpCompress.Common;
|
using SharpCompress.Common;
|
||||||
@ -32,7 +34,7 @@ namespace osu.Game.Tests.Beatmaps.IO
|
|||||||
public async Task TestImportWhenClosed()
|
public async Task TestImportWhenClosed()
|
||||||
{
|
{
|
||||||
// unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here.
|
// unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here.
|
||||||
using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestImportWhenClosed)))
|
using (HeadlessGameHost host = new CleanRunHeadlessGameHost())
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@ -49,7 +51,7 @@ namespace osu.Game.Tests.Beatmaps.IO
|
|||||||
public async Task TestImportThenDelete()
|
public async Task TestImportThenDelete()
|
||||||
{
|
{
|
||||||
// unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here.
|
// unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here.
|
||||||
using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestImportThenDelete)))
|
using (HeadlessGameHost host = new CleanRunHeadlessGameHost())
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@ -70,7 +72,7 @@ namespace osu.Game.Tests.Beatmaps.IO
|
|||||||
public async Task TestImportThenImport()
|
public async Task TestImportThenImport()
|
||||||
{
|
{
|
||||||
// unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here.
|
// unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here.
|
||||||
using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestImportThenImport)))
|
using (HeadlessGameHost host = new CleanRunHeadlessGameHost())
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@ -96,7 +98,7 @@ namespace osu.Game.Tests.Beatmaps.IO
|
|||||||
[Test]
|
[Test]
|
||||||
public async Task TestImportThenImportWithReZip()
|
public async Task TestImportThenImportWithReZip()
|
||||||
{
|
{
|
||||||
using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestImportThenImportWithReZip)))
|
using (HeadlessGameHost host = new CleanRunHeadlessGameHost())
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@ -154,7 +156,7 @@ namespace osu.Game.Tests.Beatmaps.IO
|
|||||||
[Test]
|
[Test]
|
||||||
public async Task TestImportThenImportWithChangedFile()
|
public async Task TestImportThenImportWithChangedFile()
|
||||||
{
|
{
|
||||||
using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestImportThenImportWithChangedFile)))
|
using (HeadlessGameHost host = new CleanRunHeadlessGameHost())
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@ -205,7 +207,7 @@ namespace osu.Game.Tests.Beatmaps.IO
|
|||||||
[Test]
|
[Test]
|
||||||
public async Task TestImportThenImportWithDifferentFilename()
|
public async Task TestImportThenImportWithDifferentFilename()
|
||||||
{
|
{
|
||||||
using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestImportThenImportWithDifferentFilename)))
|
using (HeadlessGameHost host = new CleanRunHeadlessGameHost())
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@ -257,7 +259,7 @@ namespace osu.Game.Tests.Beatmaps.IO
|
|||||||
public async Task TestImportCorruptThenImport()
|
public async Task TestImportCorruptThenImport()
|
||||||
{
|
{
|
||||||
// unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here.
|
// unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here.
|
||||||
using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestImportCorruptThenImport)))
|
using (HeadlessGameHost host = new CleanRunHeadlessGameHost())
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@ -299,7 +301,7 @@ namespace osu.Game.Tests.Beatmaps.IO
|
|||||||
public async Task TestRollbackOnFailure()
|
public async Task TestRollbackOnFailure()
|
||||||
{
|
{
|
||||||
// unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here.
|
// unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here.
|
||||||
using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestRollbackOnFailure)))
|
using (HeadlessGameHost host = new CleanRunHeadlessGameHost())
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@ -376,7 +378,7 @@ namespace osu.Game.Tests.Beatmaps.IO
|
|||||||
public async Task TestImportThenDeleteThenImport()
|
public async Task TestImportThenDeleteThenImport()
|
||||||
{
|
{
|
||||||
// unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here.
|
// unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here.
|
||||||
using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestImportThenDeleteThenImport)))
|
using (HeadlessGameHost host = new CleanRunHeadlessGameHost())
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@ -404,7 +406,7 @@ namespace osu.Game.Tests.Beatmaps.IO
|
|||||||
public async Task TestImportThenDeleteThenImportWithOnlineIDMismatch(bool set)
|
public async Task TestImportThenDeleteThenImportWithOnlineIDMismatch(bool set)
|
||||||
{
|
{
|
||||||
// unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here.
|
// unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here.
|
||||||
using (HeadlessGameHost host = new CleanRunHeadlessGameHost($"{nameof(TestImportThenDeleteThenImportWithOnlineIDMismatch)}-{set}"))
|
using (HeadlessGameHost host = new CleanRunHeadlessGameHost(set.ToString()))
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@ -438,7 +440,7 @@ namespace osu.Game.Tests.Beatmaps.IO
|
|||||||
public async Task TestImportWithDuplicateBeatmapIDs()
|
public async Task TestImportWithDuplicateBeatmapIDs()
|
||||||
{
|
{
|
||||||
// unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here.
|
// unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here.
|
||||||
using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestImportWithDuplicateBeatmapIDs)))
|
using (HeadlessGameHost host = new CleanRunHeadlessGameHost())
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@ -524,7 +526,7 @@ namespace osu.Game.Tests.Beatmaps.IO
|
|||||||
[Test]
|
[Test]
|
||||||
public async Task TestImportWhenFileOpen()
|
public async Task TestImportWhenFileOpen()
|
||||||
{
|
{
|
||||||
using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestImportWhenFileOpen)))
|
using (HeadlessGameHost host = new CleanRunHeadlessGameHost())
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@ -546,7 +548,7 @@ namespace osu.Game.Tests.Beatmaps.IO
|
|||||||
[Test]
|
[Test]
|
||||||
public async Task TestImportWithDuplicateHashes()
|
public async Task TestImportWithDuplicateHashes()
|
||||||
{
|
{
|
||||||
using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestImportWithDuplicateHashes)))
|
using (HeadlessGameHost host = new CleanRunHeadlessGameHost())
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@ -588,7 +590,7 @@ namespace osu.Game.Tests.Beatmaps.IO
|
|||||||
[Test]
|
[Test]
|
||||||
public async Task TestImportNestedStructure()
|
public async Task TestImportNestedStructure()
|
||||||
{
|
{
|
||||||
using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestImportNestedStructure)))
|
using (HeadlessGameHost host = new CleanRunHeadlessGameHost())
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@ -633,7 +635,7 @@ namespace osu.Game.Tests.Beatmaps.IO
|
|||||||
[Test]
|
[Test]
|
||||||
public async Task TestImportWithIgnoredDirectoryInArchive()
|
public async Task TestImportWithIgnoredDirectoryInArchive()
|
||||||
{
|
{
|
||||||
using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestImportWithIgnoredDirectoryInArchive)))
|
using (HeadlessGameHost host = new CleanRunHeadlessGameHost())
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@ -687,7 +689,7 @@ namespace osu.Game.Tests.Beatmaps.IO
|
|||||||
[Test]
|
[Test]
|
||||||
public async Task TestUpdateBeatmapInfo()
|
public async Task TestUpdateBeatmapInfo()
|
||||||
{
|
{
|
||||||
using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestUpdateBeatmapInfo)))
|
using (HeadlessGameHost host = new CleanRunHeadlessGameHost())
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@ -717,7 +719,7 @@ namespace osu.Game.Tests.Beatmaps.IO
|
|||||||
[Test]
|
[Test]
|
||||||
public async Task TestUpdateBeatmapFile()
|
public async Task TestUpdateBeatmapFile()
|
||||||
{
|
{
|
||||||
using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestUpdateBeatmapFile)))
|
using (HeadlessGameHost host = new CleanRunHeadlessGameHost())
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@ -756,6 +758,63 @@ namespace osu.Game.Tests.Beatmaps.IO
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestCreateNewEmptyBeatmap()
|
||||||
|
{
|
||||||
|
using (HeadlessGameHost host = new CleanRunHeadlessGameHost())
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var osu = loadOsu(host);
|
||||||
|
var manager = osu.Dependencies.Get<BeatmapManager>();
|
||||||
|
|
||||||
|
var working = manager.CreateNew(new OsuRuleset().RulesetInfo, User.SYSTEM_USER);
|
||||||
|
|
||||||
|
manager.Save(working.BeatmapInfo, working.Beatmap);
|
||||||
|
|
||||||
|
var retrievedSet = manager.GetAllUsableBeatmapSets()[0];
|
||||||
|
|
||||||
|
// Check that the new file is referenced correctly by attempting a retrieval
|
||||||
|
Beatmap updatedBeatmap = (Beatmap)manager.GetWorkingBeatmap(retrievedSet.Beatmaps[0]).Beatmap;
|
||||||
|
Assert.That(updatedBeatmap.HitObjects.Count, Is.EqualTo(0));
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
host.Exit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestCreateNewBeatmapWithObject()
|
||||||
|
{
|
||||||
|
using (HeadlessGameHost host = new CleanRunHeadlessGameHost())
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var osu = loadOsu(host);
|
||||||
|
var manager = osu.Dependencies.Get<BeatmapManager>();
|
||||||
|
|
||||||
|
var working = manager.CreateNew(new OsuRuleset().RulesetInfo, User.SYSTEM_USER);
|
||||||
|
|
||||||
|
((Beatmap)working.Beatmap).HitObjects.Add(new HitCircle { StartTime = 5000 });
|
||||||
|
|
||||||
|
manager.Save(working.BeatmapInfo, working.Beatmap);
|
||||||
|
|
||||||
|
var retrievedSet = manager.GetAllUsableBeatmapSets()[0];
|
||||||
|
|
||||||
|
// Check that the new file is referenced correctly by attempting a retrieval
|
||||||
|
Beatmap updatedBeatmap = (Beatmap)manager.GetWorkingBeatmap(retrievedSet.Beatmaps[0]).Beatmap;
|
||||||
|
Assert.That(updatedBeatmap.HitObjects.Count, Is.EqualTo(1));
|
||||||
|
Assert.That(updatedBeatmap.HitObjects[0].StartTime, Is.EqualTo(5000));
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
host.Exit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static async Task<BeatmapSetInfo> LoadOszIntoOsu(OsuGameBase osu, string path = null, bool virtualTrack = false)
|
public static async Task<BeatmapSetInfo> LoadOszIntoOsu(OsuGameBase osu, string path = null, bool virtualTrack = false)
|
||||||
{
|
{
|
||||||
var temp = path ?? TestResources.GetTestBeatmapForImport(virtualTrack);
|
var temp = path ?? TestResources.GetTestBeatmapForImport(virtualTrack);
|
||||||
|
221
osu.Game.Tests/Collections/IO/ImportCollectionsTest.cs
Normal file
221
osu.Game.Tests/Collections/IO/ImportCollectionsTest.cs
Normal file
@ -0,0 +1,221 @@
|
|||||||
|
// 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.IO;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Platform;
|
||||||
|
using osu.Game.Collections;
|
||||||
|
using osu.Game.Tests.Resources;
|
||||||
|
|
||||||
|
namespace osu.Game.Tests.Collections.IO
|
||||||
|
{
|
||||||
|
[TestFixture]
|
||||||
|
public class ImportCollectionsTest
|
||||||
|
{
|
||||||
|
[Test]
|
||||||
|
public async Task TestImportEmptyDatabase()
|
||||||
|
{
|
||||||
|
using (HeadlessGameHost host = new CleanRunHeadlessGameHost())
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var osu = loadOsu(host);
|
||||||
|
|
||||||
|
await osu.CollectionManager.Import(new MemoryStream());
|
||||||
|
|
||||||
|
Assert.That(osu.CollectionManager.Collections.Count, Is.Zero);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
host.Exit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public async Task TestImportWithNoBeatmaps()
|
||||||
|
{
|
||||||
|
using (HeadlessGameHost host = new CleanRunHeadlessGameHost())
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var osu = loadOsu(host);
|
||||||
|
|
||||||
|
await osu.CollectionManager.Import(TestResources.OpenResource("Collections/collections.db"));
|
||||||
|
|
||||||
|
Assert.That(osu.CollectionManager.Collections.Count, Is.EqualTo(2));
|
||||||
|
|
||||||
|
Assert.That(osu.CollectionManager.Collections[0].Name.Value, Is.EqualTo("First"));
|
||||||
|
Assert.That(osu.CollectionManager.Collections[0].Beatmaps.Count, Is.Zero);
|
||||||
|
|
||||||
|
Assert.That(osu.CollectionManager.Collections[1].Name.Value, Is.EqualTo("Second"));
|
||||||
|
Assert.That(osu.CollectionManager.Collections[1].Beatmaps.Count, Is.Zero);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
host.Exit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public async Task TestImportWithBeatmaps()
|
||||||
|
{
|
||||||
|
using (HeadlessGameHost host = new CleanRunHeadlessGameHost())
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var osu = loadOsu(host, true);
|
||||||
|
|
||||||
|
await osu.CollectionManager.Import(TestResources.OpenResource("Collections/collections.db"));
|
||||||
|
|
||||||
|
Assert.That(osu.CollectionManager.Collections.Count, Is.EqualTo(2));
|
||||||
|
|
||||||
|
Assert.That(osu.CollectionManager.Collections[0].Name.Value, Is.EqualTo("First"));
|
||||||
|
Assert.That(osu.CollectionManager.Collections[0].Beatmaps.Count, Is.EqualTo(1));
|
||||||
|
|
||||||
|
Assert.That(osu.CollectionManager.Collections[1].Name.Value, Is.EqualTo("Second"));
|
||||||
|
Assert.That(osu.CollectionManager.Collections[1].Beatmaps.Count, Is.EqualTo(12));
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
host.Exit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public async Task TestImportMalformedDatabase()
|
||||||
|
{
|
||||||
|
bool exceptionThrown = false;
|
||||||
|
UnhandledExceptionEventHandler setException = (_, __) => exceptionThrown = true;
|
||||||
|
|
||||||
|
using (HeadlessGameHost host = new CleanRunHeadlessGameHost())
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
AppDomain.CurrentDomain.UnhandledException += setException;
|
||||||
|
|
||||||
|
var osu = loadOsu(host, true);
|
||||||
|
|
||||||
|
using (var ms = new MemoryStream())
|
||||||
|
{
|
||||||
|
using (var bw = new BinaryWriter(ms, Encoding.UTF8, true))
|
||||||
|
{
|
||||||
|
for (int i = 0; i < 10000; i++)
|
||||||
|
bw.Write((byte)i);
|
||||||
|
}
|
||||||
|
|
||||||
|
ms.Seek(0, SeekOrigin.Begin);
|
||||||
|
|
||||||
|
await osu.CollectionManager.Import(ms);
|
||||||
|
}
|
||||||
|
|
||||||
|
Assert.That(host.UpdateThread.Running, Is.True);
|
||||||
|
Assert.That(exceptionThrown, Is.False);
|
||||||
|
Assert.That(osu.CollectionManager.Collections.Count, Is.EqualTo(0));
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
host.Exit();
|
||||||
|
AppDomain.CurrentDomain.UnhandledException -= setException;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public async Task TestSaveAndReload()
|
||||||
|
{
|
||||||
|
using (HeadlessGameHost host = new CleanRunHeadlessGameHost())
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var osu = loadOsu(host, true);
|
||||||
|
|
||||||
|
await osu.CollectionManager.Import(TestResources.OpenResource("Collections/collections.db"));
|
||||||
|
|
||||||
|
// Move first beatmap from second collection into the first.
|
||||||
|
osu.CollectionManager.Collections[0].Beatmaps.Add(osu.CollectionManager.Collections[1].Beatmaps[0]);
|
||||||
|
osu.CollectionManager.Collections[1].Beatmaps.RemoveAt(0);
|
||||||
|
|
||||||
|
// Rename the second collecction.
|
||||||
|
osu.CollectionManager.Collections[1].Name.Value = "Another";
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
host.Exit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
using (HeadlessGameHost host = new HeadlessGameHost("TestSaveAndReload"))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var osu = loadOsu(host, true);
|
||||||
|
|
||||||
|
Assert.That(osu.CollectionManager.Collections.Count, Is.EqualTo(2));
|
||||||
|
|
||||||
|
Assert.That(osu.CollectionManager.Collections[0].Name.Value, Is.EqualTo("First"));
|
||||||
|
Assert.That(osu.CollectionManager.Collections[0].Beatmaps.Count, Is.EqualTo(2));
|
||||||
|
|
||||||
|
Assert.That(osu.CollectionManager.Collections[1].Name.Value, Is.EqualTo("Another"));
|
||||||
|
Assert.That(osu.CollectionManager.Collections[1].Beatmaps.Count, Is.EqualTo(11));
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
host.Exit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private TestOsuGameBase loadOsu(GameHost host, bool withBeatmap = false)
|
||||||
|
{
|
||||||
|
var osu = new TestOsuGameBase(withBeatmap);
|
||||||
|
|
||||||
|
#pragma warning disable 4014
|
||||||
|
Task.Run(() => host.Run(osu));
|
||||||
|
#pragma warning restore 4014
|
||||||
|
|
||||||
|
waitForOrAssert(() => osu.IsLoaded, @"osu! failed to start in a reasonable amount of time");
|
||||||
|
|
||||||
|
return osu;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void waitForOrAssert(Func<bool> result, string failureMessage, int timeout = 60000)
|
||||||
|
{
|
||||||
|
Task task = Task.Run(() =>
|
||||||
|
{
|
||||||
|
while (!result()) Thread.Sleep(200);
|
||||||
|
});
|
||||||
|
|
||||||
|
Assert.IsTrue(task.Wait(timeout), failureMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
private class TestOsuGameBase : OsuGameBase
|
||||||
|
{
|
||||||
|
public CollectionManager CollectionManager { get; private set; }
|
||||||
|
|
||||||
|
private readonly bool withBeatmap;
|
||||||
|
|
||||||
|
public TestOsuGameBase(bool withBeatmap)
|
||||||
|
{
|
||||||
|
this.withBeatmap = withBeatmap;
|
||||||
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load()
|
||||||
|
{
|
||||||
|
// Beatmap must be imported before the collection manager is loaded.
|
||||||
|
if (withBeatmap)
|
||||||
|
BeatmapManager.Import(TestResources.GetTestBeatmapForImport()).Wait();
|
||||||
|
|
||||||
|
AddInternal(CollectionManager = new CollectionManager(Storage));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -28,6 +28,20 @@ namespace osu.Game.Tests.Gameplay
|
|||||||
Assert.That(scoreProcessor.TotalScore.Value, Is.EqualTo(0.0));
|
Assert.That(scoreProcessor.TotalScore.Value, Is.EqualTo(0.0));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestOnlyBonusScore()
|
||||||
|
{
|
||||||
|
var beatmap = new Beatmap<TestBonusHitObject> { HitObjects = { new TestBonusHitObject() } };
|
||||||
|
|
||||||
|
var scoreProcessor = new ScoreProcessor();
|
||||||
|
scoreProcessor.ApplyBeatmap(beatmap);
|
||||||
|
|
||||||
|
// Apply a judgement
|
||||||
|
scoreProcessor.ApplyResult(new JudgementResult(new TestBonusHitObject(), new TestBonusJudgement()) { Type = HitResult.Perfect });
|
||||||
|
|
||||||
|
Assert.That(scoreProcessor.TotalScore.Value, Is.EqualTo(100));
|
||||||
|
}
|
||||||
|
|
||||||
private class TestHitObject : HitObject
|
private class TestHitObject : HitObject
|
||||||
{
|
{
|
||||||
public override Judgement CreateJudgement() => new TestJudgement();
|
public override Judgement CreateJudgement() => new TestJudgement();
|
||||||
@ -37,5 +51,17 @@ namespace osu.Game.Tests.Gameplay
|
|||||||
{
|
{
|
||||||
protected override int NumericResultFor(HitResult result) => 100;
|
protected override int NumericResultFor(HitResult result) => 100;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private class TestBonusHitObject : HitObject
|
||||||
|
{
|
||||||
|
public override Judgement CreateJudgement() => new TestBonusJudgement();
|
||||||
|
}
|
||||||
|
|
||||||
|
private class TestBonusJudgement : Judgement
|
||||||
|
{
|
||||||
|
public override bool AffectsCombo => false;
|
||||||
|
|
||||||
|
protected override int NumericResultFor(HitResult result) => 100;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
BIN
osu.Game.Tests/Resources/Collections/collections.db
Normal file
BIN
osu.Game.Tests/Resources/Collections/collections.db
Normal file
Binary file not shown.
@ -27,7 +27,7 @@ namespace osu.Game.Tests.Scores.IO
|
|||||||
[Test]
|
[Test]
|
||||||
public async Task TestBasicImport()
|
public async Task TestBasicImport()
|
||||||
{
|
{
|
||||||
using (HeadlessGameHost host = new CleanRunHeadlessGameHost("TestBasicImport"))
|
using (HeadlessGameHost host = new CleanRunHeadlessGameHost())
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@ -66,7 +66,7 @@ namespace osu.Game.Tests.Scores.IO
|
|||||||
[Test]
|
[Test]
|
||||||
public async Task TestImportMods()
|
public async Task TestImportMods()
|
||||||
{
|
{
|
||||||
using (HeadlessGameHost host = new CleanRunHeadlessGameHost("TestImportMods"))
|
using (HeadlessGameHost host = new CleanRunHeadlessGameHost())
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@ -92,7 +92,7 @@ namespace osu.Game.Tests.Scores.IO
|
|||||||
[Test]
|
[Test]
|
||||||
public async Task TestImportStatistics()
|
public async Task TestImportStatistics()
|
||||||
{
|
{
|
||||||
using (HeadlessGameHost host = new CleanRunHeadlessGameHost("TestImportStatistics"))
|
using (HeadlessGameHost host = new CleanRunHeadlessGameHost())
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@ -122,7 +122,7 @@ namespace osu.Game.Tests.Scores.IO
|
|||||||
[Test]
|
[Test]
|
||||||
public async Task TestImportWithDeletedBeatmapSet()
|
public async Task TestImportWithDeletedBeatmapSet()
|
||||||
{
|
{
|
||||||
using (HeadlessGameHost host = new CleanRunHeadlessGameHost("TestImportWithDeletedBeatmapSet"))
|
using (HeadlessGameHost host = new CleanRunHeadlessGameHost())
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@ -159,7 +159,7 @@ namespace osu.Game.Tests.Scores.IO
|
|||||||
[Test]
|
[Test]
|
||||||
public async Task TestOnlineScoreIsAvailableLocally()
|
public async Task TestOnlineScoreIsAvailableLocally()
|
||||||
{
|
{
|
||||||
using (HeadlessGameHost host = new CleanRunHeadlessGameHost("TestOnlineScoreIsAvailableLocally"))
|
using (HeadlessGameHost host = new CleanRunHeadlessGameHost())
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
@ -0,0 +1,244 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System.Linq;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.UserInterface;
|
||||||
|
using osu.Framework.Platform;
|
||||||
|
using osu.Framework.Testing;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Collections;
|
||||||
|
using osu.Game.Overlays;
|
||||||
|
using osu.Game.Overlays.Dialog;
|
||||||
|
using osu.Game.Rulesets;
|
||||||
|
using osu.Game.Tests.Resources;
|
||||||
|
using osuTK;
|
||||||
|
using osuTK.Input;
|
||||||
|
|
||||||
|
namespace osu.Game.Tests.Visual.Collections
|
||||||
|
{
|
||||||
|
public class TestSceneManageCollectionsDialog : OsuManualInputManagerTestScene
|
||||||
|
{
|
||||||
|
protected override Container<Drawable> Content => content;
|
||||||
|
|
||||||
|
private readonly Container content;
|
||||||
|
private readonly DialogOverlay dialogOverlay;
|
||||||
|
private readonly CollectionManager manager;
|
||||||
|
|
||||||
|
private RulesetStore rulesets;
|
||||||
|
private BeatmapManager beatmapManager;
|
||||||
|
|
||||||
|
private ManageCollectionsDialog dialog;
|
||||||
|
|
||||||
|
public TestSceneManageCollectionsDialog()
|
||||||
|
{
|
||||||
|
base.Content.AddRange(new Drawable[]
|
||||||
|
{
|
||||||
|
manager = new CollectionManager(LocalStorage),
|
||||||
|
content = new Container { RelativeSizeAxes = Axes.Both },
|
||||||
|
dialogOverlay = new DialogOverlay()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(GameHost host)
|
||||||
|
{
|
||||||
|
Dependencies.Cache(rulesets = new RulesetStore(ContextFactory));
|
||||||
|
Dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, Audio, host, Beatmap.Default));
|
||||||
|
|
||||||
|
beatmapManager.Import(TestResources.GetTestBeatmapForImport()).Wait();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent)
|
||||||
|
{
|
||||||
|
var dependencies = new DependencyContainer(base.CreateChildDependencies(parent));
|
||||||
|
dependencies.Cache(manager);
|
||||||
|
dependencies.Cache(dialogOverlay);
|
||||||
|
return dependencies;
|
||||||
|
}
|
||||||
|
|
||||||
|
[SetUp]
|
||||||
|
public void SetUp() => Schedule(() =>
|
||||||
|
{
|
||||||
|
manager.Collections.Clear();
|
||||||
|
Child = dialog = new ManageCollectionsDialog();
|
||||||
|
});
|
||||||
|
|
||||||
|
[SetUpSteps]
|
||||||
|
public void SetUpSteps()
|
||||||
|
{
|
||||||
|
AddStep("show dialog", () => dialog.Show());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestHideDialog()
|
||||||
|
{
|
||||||
|
AddWaitStep("wait for animation", 3);
|
||||||
|
AddStep("hide dialog", () => dialog.Hide());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestLastItemIsPlaceholder()
|
||||||
|
{
|
||||||
|
AddAssert("last item is placeholder", () => !manager.Collections.Contains(dialog.ChildrenOfType<DrawableCollectionListItem>().Last().Model));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestAddCollectionExternal()
|
||||||
|
{
|
||||||
|
AddStep("add collection", () => manager.Collections.Add(new BeatmapCollection { Name = { Value = "First collection" } }));
|
||||||
|
assertCollectionCount(1);
|
||||||
|
assertCollectionName(0, "First collection");
|
||||||
|
|
||||||
|
AddStep("add another collection", () => manager.Collections.Add(new BeatmapCollection { Name = { Value = "Second collection" } }));
|
||||||
|
assertCollectionCount(2);
|
||||||
|
assertCollectionName(1, "Second collection");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestFocusPlaceholderDoesNotCreateCollection()
|
||||||
|
{
|
||||||
|
AddStep("focus placeholder", () =>
|
||||||
|
{
|
||||||
|
InputManager.MoveMouseTo(dialog.ChildrenOfType<DrawableCollectionListItem>().Last());
|
||||||
|
InputManager.Click(MouseButton.Left);
|
||||||
|
});
|
||||||
|
|
||||||
|
assertCollectionCount(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestAddCollectionViaPlaceholder()
|
||||||
|
{
|
||||||
|
DrawableCollectionListItem placeholderItem = null;
|
||||||
|
|
||||||
|
AddStep("focus placeholder", () =>
|
||||||
|
{
|
||||||
|
InputManager.MoveMouseTo(placeholderItem = dialog.ChildrenOfType<DrawableCollectionListItem>().Last());
|
||||||
|
InputManager.Click(MouseButton.Left);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Done directly via the collection since InputManager methods cannot add text to textbox...
|
||||||
|
AddStep("change collection name", () => placeholderItem.Model.Name.Value = "a");
|
||||||
|
assertCollectionCount(1);
|
||||||
|
AddAssert("collection now exists", () => manager.Collections.Contains(placeholderItem.Model));
|
||||||
|
|
||||||
|
AddAssert("last item is placeholder", () => !manager.Collections.Contains(dialog.ChildrenOfType<DrawableCollectionListItem>().Last().Model));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestRemoveCollectionExternal()
|
||||||
|
{
|
||||||
|
AddStep("add two collections", () => manager.Collections.AddRange(new[]
|
||||||
|
{
|
||||||
|
new BeatmapCollection { Name = { Value = "1" } },
|
||||||
|
new BeatmapCollection { Name = { Value = "2" } },
|
||||||
|
}));
|
||||||
|
|
||||||
|
AddStep("remove first collection", () => manager.Collections.RemoveAt(0));
|
||||||
|
assertCollectionCount(1);
|
||||||
|
assertCollectionName(0, "2");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestRemoveCollectionViaButton()
|
||||||
|
{
|
||||||
|
AddStep("add two collections", () => manager.Collections.AddRange(new[]
|
||||||
|
{
|
||||||
|
new BeatmapCollection { Name = { Value = "1" } },
|
||||||
|
new BeatmapCollection { Name = { Value = "2" }, Beatmaps = { beatmapManager.GetAllUsableBeatmapSets().First().Beatmaps[0] } },
|
||||||
|
}));
|
||||||
|
|
||||||
|
assertCollectionCount(2);
|
||||||
|
|
||||||
|
AddStep("click first delete button", () =>
|
||||||
|
{
|
||||||
|
InputManager.MoveMouseTo(dialog.ChildrenOfType<DrawableCollectionListItem.DeleteButton>().First(), new Vector2(5, 0));
|
||||||
|
InputManager.Click(MouseButton.Left);
|
||||||
|
});
|
||||||
|
|
||||||
|
AddAssert("dialog not displayed", () => dialogOverlay.CurrentDialog == null);
|
||||||
|
assertCollectionCount(1);
|
||||||
|
assertCollectionName(0, "2");
|
||||||
|
|
||||||
|
AddStep("click first delete button", () =>
|
||||||
|
{
|
||||||
|
InputManager.MoveMouseTo(dialog.ChildrenOfType<DrawableCollectionListItem.DeleteButton>().First(), new Vector2(5, 0));
|
||||||
|
InputManager.Click(MouseButton.Left);
|
||||||
|
});
|
||||||
|
|
||||||
|
AddAssert("dialog displayed", () => dialogOverlay.CurrentDialog is DeleteCollectionDialog);
|
||||||
|
AddStep("click confirmation", () =>
|
||||||
|
{
|
||||||
|
InputManager.MoveMouseTo(dialogOverlay.CurrentDialog.ChildrenOfType<PopupDialogButton>().First());
|
||||||
|
InputManager.Click(MouseButton.Left);
|
||||||
|
});
|
||||||
|
|
||||||
|
assertCollectionCount(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestCollectionNotRemovedWhenDialogCancelled()
|
||||||
|
{
|
||||||
|
AddStep("add two collections", () => manager.Collections.AddRange(new[]
|
||||||
|
{
|
||||||
|
new BeatmapCollection { Name = { Value = "1" }, Beatmaps = { beatmapManager.GetAllUsableBeatmapSets().First().Beatmaps[0] } },
|
||||||
|
}));
|
||||||
|
|
||||||
|
assertCollectionCount(1);
|
||||||
|
|
||||||
|
AddStep("click first delete button", () =>
|
||||||
|
{
|
||||||
|
InputManager.MoveMouseTo(dialog.ChildrenOfType<DrawableCollectionListItem.DeleteButton>().First(), new Vector2(5, 0));
|
||||||
|
InputManager.Click(MouseButton.Left);
|
||||||
|
});
|
||||||
|
|
||||||
|
AddAssert("dialog displayed", () => dialogOverlay.CurrentDialog is DeleteCollectionDialog);
|
||||||
|
AddStep("click cancellation", () =>
|
||||||
|
{
|
||||||
|
InputManager.MoveMouseTo(dialogOverlay.CurrentDialog.ChildrenOfType<PopupDialogButton>().Last());
|
||||||
|
InputManager.Click(MouseButton.Left);
|
||||||
|
});
|
||||||
|
|
||||||
|
assertCollectionCount(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestCollectionRenamedExternal()
|
||||||
|
{
|
||||||
|
AddStep("add two collections", () => manager.Collections.AddRange(new[]
|
||||||
|
{
|
||||||
|
new BeatmapCollection { Name = { Value = "1" } },
|
||||||
|
new BeatmapCollection { Name = { Value = "2" } },
|
||||||
|
}));
|
||||||
|
|
||||||
|
AddStep("change first collection name", () => manager.Collections[0].Name.Value = "First");
|
||||||
|
|
||||||
|
assertCollectionName(0, "First");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestCollectionRenamedOnTextChange()
|
||||||
|
{
|
||||||
|
AddStep("add two collections", () => manager.Collections.AddRange(new[]
|
||||||
|
{
|
||||||
|
new BeatmapCollection { Name = { Value = "1" } },
|
||||||
|
new BeatmapCollection { Name = { Value = "2" } },
|
||||||
|
}));
|
||||||
|
|
||||||
|
assertCollectionCount(2);
|
||||||
|
|
||||||
|
AddStep("change first collection name", () => dialog.ChildrenOfType<TextBox>().First().Text = "First");
|
||||||
|
AddAssert("collection has new name", () => manager.Collections[0].Name.Value == "First");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertCollectionCount(int count)
|
||||||
|
=> AddUntilStep($"{count} collections shown", () => dialog.ChildrenOfType<DrawableCollectionListItem>().Count(i => i.IsCreated.Value) == count);
|
||||||
|
|
||||||
|
private void assertCollectionName(int index, string name)
|
||||||
|
=> AddUntilStep($"item {index + 1} has correct name", () => dialog.ChildrenOfType<DrawableCollectionListItem>().ElementAt(index).ChildrenOfType<TextBox>().First().Text == name);
|
||||||
|
}
|
||||||
|
}
|
@ -18,6 +18,8 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
|
|
||||||
protected override Ruleset CreateEditorRuleset() => new OsuRuleset();
|
protected override Ruleset CreateEditorRuleset() => new OsuRuleset();
|
||||||
|
|
||||||
|
protected new TestEditor Editor => (TestEditor)base.Editor;
|
||||||
|
|
||||||
public override void SetUpSteps()
|
public override void SetUpSteps()
|
||||||
{
|
{
|
||||||
base.SetUpSteps();
|
base.SetUpSteps();
|
||||||
@ -35,6 +37,7 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
addUndoSteps();
|
addUndoSteps();
|
||||||
|
|
||||||
AddAssert("no change occurred", () => hitObjectCount == editorBeatmap.HitObjects.Count);
|
AddAssert("no change occurred", () => hitObjectCount == editorBeatmap.HitObjects.Count);
|
||||||
|
AddAssert("no unsaved changes", () => !Editor.HasUnsavedChanges);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@ -47,6 +50,7 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
addRedoSteps();
|
addRedoSteps();
|
||||||
|
|
||||||
AddAssert("no change occurred", () => hitObjectCount == editorBeatmap.HitObjects.Count);
|
AddAssert("no change occurred", () => hitObjectCount == editorBeatmap.HitObjects.Count);
|
||||||
|
AddAssert("no unsaved changes", () => !Editor.HasUnsavedChanges);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@ -64,9 +68,11 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
|
|
||||||
AddStep("add hitobject", () => editorBeatmap.Add(expectedObject = new HitCircle { StartTime = 1000 }));
|
AddStep("add hitobject", () => editorBeatmap.Add(expectedObject = new HitCircle { StartTime = 1000 }));
|
||||||
AddAssert("hitobject added", () => addedObject == expectedObject);
|
AddAssert("hitobject added", () => addedObject == expectedObject);
|
||||||
|
AddAssert("unsaved changes", () => Editor.HasUnsavedChanges);
|
||||||
|
|
||||||
addUndoSteps();
|
addUndoSteps();
|
||||||
AddAssert("hitobject removed", () => removedObject == expectedObject);
|
AddAssert("hitobject removed", () => removedObject == expectedObject);
|
||||||
|
AddAssert("no unsaved changes", () => !Editor.HasUnsavedChanges);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@ -94,6 +100,17 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
addRedoSteps();
|
addRedoSteps();
|
||||||
AddAssert("hitobject added", () => addedObject.StartTime == expectedObject.StartTime); // Can't compare via equality (new hitobject instance)
|
AddAssert("hitobject added", () => addedObject.StartTime == expectedObject.StartTime); // Can't compare via equality (new hitobject instance)
|
||||||
AddAssert("no hitobject removed", () => removedObject == null);
|
AddAssert("no hitobject removed", () => removedObject == null);
|
||||||
|
AddAssert("unsaved changes", () => Editor.HasUnsavedChanges);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestAddObjectThenSaveHasNoUnsavedChanges()
|
||||||
|
{
|
||||||
|
AddStep("add hitobject", () => editorBeatmap.Add(new HitCircle { StartTime = 1000 }));
|
||||||
|
|
||||||
|
AddAssert("unsaved changes", () => Editor.HasUnsavedChanges);
|
||||||
|
AddStep("save changes", () => Editor.Save());
|
||||||
|
AddAssert("no unsaved changes", () => !Editor.HasUnsavedChanges);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@ -120,6 +137,7 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
addUndoSteps();
|
addUndoSteps();
|
||||||
AddAssert("hitobject added", () => addedObject.StartTime == expectedObject.StartTime); // Can't compare via equality (new hitobject instance)
|
AddAssert("hitobject added", () => addedObject.StartTime == expectedObject.StartTime); // Can't compare via equality (new hitobject instance)
|
||||||
AddAssert("no hitobject removed", () => removedObject == null);
|
AddAssert("no hitobject removed", () => removedObject == null);
|
||||||
|
AddAssert("unsaved changes", () => Editor.HasUnsavedChanges); // 2 steps performed, 1 undone
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@ -148,19 +166,24 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
addRedoSteps();
|
addRedoSteps();
|
||||||
AddAssert("hitobject removed", () => removedObject.StartTime == expectedObject.StartTime); // Can't compare via equality (new hitobject instance after undo)
|
AddAssert("hitobject removed", () => removedObject.StartTime == expectedObject.StartTime); // Can't compare via equality (new hitobject instance after undo)
|
||||||
AddAssert("no hitobject added", () => addedObject == null);
|
AddAssert("no hitobject added", () => addedObject == null);
|
||||||
|
AddAssert("no changes", () => !Editor.HasUnsavedChanges); // end result is empty beatmap, matching original state
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addUndoSteps() => AddStep("undo", () => ((TestEditor)Editor).Undo());
|
private void addUndoSteps() => AddStep("undo", () => Editor.Undo());
|
||||||
|
|
||||||
private void addRedoSteps() => AddStep("redo", () => ((TestEditor)Editor).Redo());
|
private void addRedoSteps() => AddStep("redo", () => Editor.Redo());
|
||||||
|
|
||||||
protected override Editor CreateEditor() => new TestEditor();
|
protected override Editor CreateEditor() => new TestEditor();
|
||||||
|
|
||||||
private class TestEditor : Editor
|
protected class TestEditor : Editor
|
||||||
{
|
{
|
||||||
public new void Undo() => base.Undo();
|
public new void Undo() => base.Undo();
|
||||||
|
|
||||||
public new void Redo() => base.Redo();
|
public new void Redo() => base.Redo();
|
||||||
|
|
||||||
|
public new void Save() => base.Save();
|
||||||
|
|
||||||
|
public new bool HasUnsavedChanges => base.HasUnsavedChanges;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Sprites;
|
||||||
using osu.Game.Screens.Edit.Components.RadioButtons;
|
using osu.Game.Screens.Edit.Components.RadioButtons;
|
||||||
|
|
||||||
namespace osu.Game.Tests.Visual.Editing
|
namespace osu.Game.Tests.Visual.Editing
|
||||||
@ -22,7 +23,7 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
{
|
{
|
||||||
new RadioButton("Item 1", () => { }),
|
new RadioButton("Item 1", () => { }),
|
||||||
new RadioButton("Item 2", () => { }),
|
new RadioButton("Item 2", () => { }),
|
||||||
new RadioButton("Item 3", () => { }),
|
new RadioButton("Item 3", () => { }, () => new SpriteIcon { Icon = FontAwesome.Regular.Angry }),
|
||||||
new RadioButton("Item 4", () => { }),
|
new RadioButton("Item 4", () => { }),
|
||||||
new RadioButton("Item 5", () => { })
|
new RadioButton("Item 5", () => { })
|
||||||
}
|
}
|
||||||
|
32
osu.Game.Tests/Visual/Editing/TestSceneSetupScreen.cs
Normal file
32
osu.Game.Tests/Visual/Editing/TestSceneSetupScreen.cs
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using NUnit.Framework;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Game.Rulesets.Edit;
|
||||||
|
using osu.Game.Rulesets.Osu.Beatmaps;
|
||||||
|
using osu.Game.Screens.Edit;
|
||||||
|
using osu.Game.Screens.Edit.Setup;
|
||||||
|
|
||||||
|
namespace osu.Game.Tests.Visual.Editing
|
||||||
|
{
|
||||||
|
[TestFixture]
|
||||||
|
public class TestSceneSetupScreen : EditorClockTestScene
|
||||||
|
{
|
||||||
|
[Cached(typeof(EditorBeatmap))]
|
||||||
|
[Cached(typeof(IBeatSnapProvider))]
|
||||||
|
private readonly EditorBeatmap editorBeatmap;
|
||||||
|
|
||||||
|
public TestSceneSetupScreen()
|
||||||
|
{
|
||||||
|
editorBeatmap = new EditorBeatmap(new OsuBeatmap());
|
||||||
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load()
|
||||||
|
{
|
||||||
|
Beatmap.Value = CreateWorkingBeatmap(editorBeatmap.PlayableBeatmap);
|
||||||
|
Child = new SetupScreen();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -48,7 +48,10 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
|
|
||||||
private class ExampleContainer : PlayerSettingsGroup
|
private class ExampleContainer : PlayerSettingsGroup
|
||||||
{
|
{
|
||||||
protected override string Title => @"example";
|
public ExampleContainer()
|
||||||
|
: base("example")
|
||||||
|
{
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
83
osu.Game.Tests/Visual/Menus/TestSceneMusicActionHandling.cs
Normal file
83
osu.Game.Tests/Visual/Menus/TestSceneMusicActionHandling.cs
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using osu.Framework.Testing;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Input.Bindings;
|
||||||
|
using osu.Game.Overlays;
|
||||||
|
using osu.Game.Tests.Resources;
|
||||||
|
using osu.Game.Tests.Visual.Navigation;
|
||||||
|
|
||||||
|
namespace osu.Game.Tests.Visual.Menus
|
||||||
|
{
|
||||||
|
public class TestSceneMusicActionHandling : OsuGameTestScene
|
||||||
|
{
|
||||||
|
private GlobalActionContainer globalActionContainer => Game.ChildrenOfType<GlobalActionContainer>().First();
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestMusicPlayAction()
|
||||||
|
{
|
||||||
|
AddStep("ensure playing something", () => Game.MusicController.EnsurePlayingSomething());
|
||||||
|
AddStep("toggle playback", () => globalActionContainer.TriggerPressed(GlobalAction.MusicPlay));
|
||||||
|
AddAssert("music paused", () => !Game.MusicController.IsPlaying && Game.MusicController.IsUserPaused);
|
||||||
|
AddStep("toggle playback", () => globalActionContainer.TriggerPressed(GlobalAction.MusicPlay));
|
||||||
|
AddAssert("music resumed", () => Game.MusicController.IsPlaying && !Game.MusicController.IsUserPaused);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestMusicNavigationActions()
|
||||||
|
{
|
||||||
|
int importId = 0;
|
||||||
|
Queue<(WorkingBeatmap working, TrackChangeDirection changeDirection)> trackChangeQueue = null;
|
||||||
|
|
||||||
|
// ensure we have at least two beatmaps available to identify the direction the music controller navigated to.
|
||||||
|
AddRepeatStep("import beatmap", () => Game.BeatmapManager.Import(new BeatmapSetInfo
|
||||||
|
{
|
||||||
|
Beatmaps = new List<BeatmapInfo>
|
||||||
|
{
|
||||||
|
new BeatmapInfo
|
||||||
|
{
|
||||||
|
BaseDifficulty = new BeatmapDifficulty(),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Metadata = new BeatmapMetadata
|
||||||
|
{
|
||||||
|
Artist = $"a test map {importId++}",
|
||||||
|
Title = "title",
|
||||||
|
}
|
||||||
|
}).Wait(), 5);
|
||||||
|
|
||||||
|
AddStep("import beatmap with track", () =>
|
||||||
|
{
|
||||||
|
var setWithTrack = Game.BeatmapManager.Import(TestResources.GetTestBeatmapForImport()).Result;
|
||||||
|
Beatmap.Value = Game.BeatmapManager.GetWorkingBeatmap(setWithTrack.Beatmaps.First());
|
||||||
|
});
|
||||||
|
|
||||||
|
AddStep("bind to track change", () =>
|
||||||
|
{
|
||||||
|
trackChangeQueue = new Queue<(WorkingBeatmap, TrackChangeDirection)>();
|
||||||
|
Game.MusicController.TrackChanged += (working, changeDirection) => trackChangeQueue.Enqueue((working, changeDirection));
|
||||||
|
});
|
||||||
|
|
||||||
|
AddStep("seek track to 6 second", () => Game.MusicController.SeekTo(6000));
|
||||||
|
AddUntilStep("wait for current time to update", () => Game.MusicController.CurrentTrack.CurrentTime > 5000);
|
||||||
|
|
||||||
|
AddStep("press previous", () => globalActionContainer.TriggerPressed(GlobalAction.MusicPrev));
|
||||||
|
AddAssert("no track change", () => trackChangeQueue.Count == 0);
|
||||||
|
AddUntilStep("track restarted", () => Game.MusicController.CurrentTrack.CurrentTime < 5000);
|
||||||
|
|
||||||
|
AddStep("press previous", () => globalActionContainer.TriggerPressed(GlobalAction.MusicPrev));
|
||||||
|
AddAssert("track changed to previous", () =>
|
||||||
|
trackChangeQueue.Count == 1 &&
|
||||||
|
trackChangeQueue.Dequeue().changeDirection == TrackChangeDirection.Prev);
|
||||||
|
|
||||||
|
AddStep("press next", () => globalActionContainer.TriggerPressed(GlobalAction.MusicNext));
|
||||||
|
AddAssert("track changed to next", () =>
|
||||||
|
trackChangeQueue.Count == 1 &&
|
||||||
|
trackChangeQueue.Dequeue().changeDirection == TrackChangeDirection.Next);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
237
osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs
Normal file
237
osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs
Normal file
@ -0,0 +1,237 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Sprites;
|
||||||
|
using osu.Framework.Graphics.UserInterface;
|
||||||
|
using osu.Framework.Platform;
|
||||||
|
using osu.Framework.Testing;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Collections;
|
||||||
|
using osu.Game.Graphics.UserInterface;
|
||||||
|
using osu.Game.Rulesets;
|
||||||
|
using osu.Game.Screens.Select;
|
||||||
|
using osu.Game.Tests.Resources;
|
||||||
|
using osuTK.Input;
|
||||||
|
|
||||||
|
namespace osu.Game.Tests.Visual.SongSelect
|
||||||
|
{
|
||||||
|
public class TestSceneFilterControl : OsuManualInputManagerTestScene
|
||||||
|
{
|
||||||
|
protected override Container<Drawable> Content => content;
|
||||||
|
private readonly Container content;
|
||||||
|
|
||||||
|
private readonly CollectionManager collectionManager;
|
||||||
|
|
||||||
|
private RulesetStore rulesets;
|
||||||
|
private BeatmapManager beatmapManager;
|
||||||
|
|
||||||
|
private FilterControl control;
|
||||||
|
|
||||||
|
public TestSceneFilterControl()
|
||||||
|
{
|
||||||
|
base.Content.AddRange(new Drawable[]
|
||||||
|
{
|
||||||
|
collectionManager = new CollectionManager(LocalStorage),
|
||||||
|
content = new Container { RelativeSizeAxes = Axes.Both }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(GameHost host)
|
||||||
|
{
|
||||||
|
Dependencies.Cache(rulesets = new RulesetStore(ContextFactory));
|
||||||
|
Dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, Audio, host, Beatmap.Default));
|
||||||
|
|
||||||
|
beatmapManager.Import(TestResources.GetTestBeatmapForImport()).Wait();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent)
|
||||||
|
{
|
||||||
|
var dependencies = new DependencyContainer(base.CreateChildDependencies(parent));
|
||||||
|
dependencies.Cache(collectionManager);
|
||||||
|
return dependencies;
|
||||||
|
}
|
||||||
|
|
||||||
|
[SetUp]
|
||||||
|
public void SetUp() => Schedule(() =>
|
||||||
|
{
|
||||||
|
collectionManager.Collections.Clear();
|
||||||
|
|
||||||
|
Child = control = new FilterControl
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
Height = FilterControl.HEIGHT,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestEmptyCollectionFilterContainsAllBeatmaps()
|
||||||
|
{
|
||||||
|
assertCollectionDropdownContains("All beatmaps");
|
||||||
|
assertCollectionHeaderDisplays("All beatmaps");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestCollectionAddedToDropdown()
|
||||||
|
{
|
||||||
|
AddStep("add collection", () => collectionManager.Collections.Add(new BeatmapCollection { Name = { Value = "1" } }));
|
||||||
|
AddStep("add collection", () => collectionManager.Collections.Add(new BeatmapCollection { Name = { Value = "2" } }));
|
||||||
|
assertCollectionDropdownContains("1");
|
||||||
|
assertCollectionDropdownContains("2");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestCollectionRemovedFromDropdown()
|
||||||
|
{
|
||||||
|
AddStep("add collection", () => collectionManager.Collections.Add(new BeatmapCollection { Name = { Value = "1" } }));
|
||||||
|
AddStep("add collection", () => collectionManager.Collections.Add(new BeatmapCollection { Name = { Value = "2" } }));
|
||||||
|
AddStep("remove collection", () => collectionManager.Collections.RemoveAt(0));
|
||||||
|
|
||||||
|
assertCollectionDropdownContains("1", false);
|
||||||
|
assertCollectionDropdownContains("2");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestCollectionRenamed()
|
||||||
|
{
|
||||||
|
AddStep("add collection", () => collectionManager.Collections.Add(new BeatmapCollection { Name = { Value = "1" } }));
|
||||||
|
AddStep("select collection", () =>
|
||||||
|
{
|
||||||
|
var dropdown = control.ChildrenOfType<CollectionFilterDropdown>().Single();
|
||||||
|
dropdown.Current.Value = dropdown.ItemSource.ElementAt(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
addExpandHeaderStep();
|
||||||
|
|
||||||
|
AddStep("change name", () => collectionManager.Collections[0].Name.Value = "First");
|
||||||
|
|
||||||
|
assertCollectionDropdownContains("First");
|
||||||
|
assertCollectionHeaderDisplays("First");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestAllBeatmapFilterDoesNotHaveAddButton()
|
||||||
|
{
|
||||||
|
addExpandHeaderStep();
|
||||||
|
AddStep("hover all beatmaps", () => InputManager.MoveMouseTo(getAddOrRemoveButton(0)));
|
||||||
|
AddAssert("'All beatmaps' filter does not have add button", () => !getAddOrRemoveButton(0).IsPresent);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestCollectionFilterHasAddButton()
|
||||||
|
{
|
||||||
|
addExpandHeaderStep();
|
||||||
|
AddStep("add collection", () => collectionManager.Collections.Add(new BeatmapCollection { Name = { Value = "1" } }));
|
||||||
|
AddStep("hover collection", () => InputManager.MoveMouseTo(getAddOrRemoveButton(1)));
|
||||||
|
AddAssert("collection has add button", () => getAddOrRemoveButton(1).IsPresent);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestButtonDisabledAndEnabledWithBeatmapChanges()
|
||||||
|
{
|
||||||
|
addExpandHeaderStep();
|
||||||
|
|
||||||
|
AddStep("add collection", () => collectionManager.Collections.Add(new BeatmapCollection { Name = { Value = "1" } }));
|
||||||
|
|
||||||
|
AddStep("select available beatmap", () => Beatmap.Value = beatmapManager.GetWorkingBeatmap(beatmapManager.GetAllUsableBeatmapSets().First().Beatmaps[0]));
|
||||||
|
AddAssert("button enabled", () => getAddOrRemoveButton(1).Enabled.Value);
|
||||||
|
|
||||||
|
AddStep("set dummy beatmap", () => Beatmap.SetDefault());
|
||||||
|
AddAssert("button disabled", () => !getAddOrRemoveButton(1).Enabled.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestButtonChangesWhenAddedAndRemovedFromCollection()
|
||||||
|
{
|
||||||
|
addExpandHeaderStep();
|
||||||
|
|
||||||
|
AddStep("select available beatmap", () => Beatmap.Value = beatmapManager.GetWorkingBeatmap(beatmapManager.GetAllUsableBeatmapSets().First().Beatmaps[0]));
|
||||||
|
|
||||||
|
AddStep("add collection", () => collectionManager.Collections.Add(new BeatmapCollection { Name = { Value = "1" } }));
|
||||||
|
AddAssert("button is plus", () => getAddOrRemoveButton(1).Icon.Equals(FontAwesome.Solid.PlusSquare));
|
||||||
|
|
||||||
|
AddStep("add beatmap to collection", () => collectionManager.Collections[0].Beatmaps.Add(Beatmap.Value.BeatmapInfo));
|
||||||
|
AddAssert("button is minus", () => getAddOrRemoveButton(1).Icon.Equals(FontAwesome.Solid.MinusSquare));
|
||||||
|
|
||||||
|
AddStep("remove beatmap from collection", () => collectionManager.Collections[0].Beatmaps.Clear());
|
||||||
|
AddAssert("button is plus", () => getAddOrRemoveButton(1).Icon.Equals(FontAwesome.Solid.PlusSquare));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestButtonAddsAndRemovesBeatmap()
|
||||||
|
{
|
||||||
|
addExpandHeaderStep();
|
||||||
|
|
||||||
|
AddStep("select available beatmap", () => Beatmap.Value = beatmapManager.GetWorkingBeatmap(beatmapManager.GetAllUsableBeatmapSets().First().Beatmaps[0]));
|
||||||
|
|
||||||
|
AddStep("add collection", () => collectionManager.Collections.Add(new BeatmapCollection { Name = { Value = "1" } }));
|
||||||
|
AddAssert("button is plus", () => getAddOrRemoveButton(1).Icon.Equals(FontAwesome.Solid.PlusSquare));
|
||||||
|
|
||||||
|
addClickAddOrRemoveButtonStep(1);
|
||||||
|
AddAssert("collection contains beatmap", () => collectionManager.Collections[0].Beatmaps.Contains(Beatmap.Value.BeatmapInfo));
|
||||||
|
AddAssert("button is minus", () => getAddOrRemoveButton(1).Icon.Equals(FontAwesome.Solid.MinusSquare));
|
||||||
|
|
||||||
|
addClickAddOrRemoveButtonStep(1);
|
||||||
|
AddAssert("collection does not contain beatmap", () => !collectionManager.Collections[0].Beatmaps.Contains(Beatmap.Value.BeatmapInfo));
|
||||||
|
AddAssert("button is plus", () => getAddOrRemoveButton(1).Icon.Equals(FontAwesome.Solid.PlusSquare));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestManageCollectionsFilterIsNotSelected()
|
||||||
|
{
|
||||||
|
addExpandHeaderStep();
|
||||||
|
|
||||||
|
AddStep("add collection", () => collectionManager.Collections.Add(new BeatmapCollection { Name = { Value = "1" } }));
|
||||||
|
AddStep("select collection", () =>
|
||||||
|
{
|
||||||
|
InputManager.MoveMouseTo(getCollectionDropdownItems().ElementAt(1));
|
||||||
|
InputManager.Click(MouseButton.Left);
|
||||||
|
});
|
||||||
|
|
||||||
|
addExpandHeaderStep();
|
||||||
|
|
||||||
|
AddStep("click manage collections filter", () =>
|
||||||
|
{
|
||||||
|
InputManager.MoveMouseTo(getCollectionDropdownItems().Last());
|
||||||
|
InputManager.Click(MouseButton.Left);
|
||||||
|
});
|
||||||
|
|
||||||
|
AddAssert("collection filter still selected", () => control.CreateCriteria().Collection?.Name.Value == "1");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertCollectionHeaderDisplays(string collectionName, bool shouldDisplay = true)
|
||||||
|
=> AddAssert($"collection dropdown header displays '{collectionName}'",
|
||||||
|
() => shouldDisplay == (control.ChildrenOfType<CollectionFilterDropdown.CollectionDropdownHeader>().Single().ChildrenOfType<SpriteText>().First().Text == collectionName));
|
||||||
|
|
||||||
|
private void assertCollectionDropdownContains(string collectionName, bool shouldContain = true) =>
|
||||||
|
AddAssert($"collection dropdown {(shouldContain ? "contains" : "does not contain")} '{collectionName}'",
|
||||||
|
// A bit of a roundabout way of going about this, see: https://github.com/ppy/osu-framework/issues/3871 + https://github.com/ppy/osu-framework/issues/3872
|
||||||
|
() => shouldContain == (getCollectionDropdownItems().Any(i => i.ChildrenOfType<FillFlowContainer>().OfType<IHasText>().First().Text == collectionName)));
|
||||||
|
|
||||||
|
private IconButton getAddOrRemoveButton(int index)
|
||||||
|
=> getCollectionDropdownItems().ElementAt(index).ChildrenOfType<IconButton>().Single();
|
||||||
|
|
||||||
|
private void addExpandHeaderStep() => AddStep("expand header", () =>
|
||||||
|
{
|
||||||
|
InputManager.MoveMouseTo(control.ChildrenOfType<CollectionFilterDropdown.CollectionDropdownHeader>().Single());
|
||||||
|
InputManager.Click(MouseButton.Left);
|
||||||
|
});
|
||||||
|
|
||||||
|
private void addClickAddOrRemoveButtonStep(int index) => AddStep("click add or remove button", () =>
|
||||||
|
{
|
||||||
|
InputManager.MoveMouseTo(getAddOrRemoveButton(index));
|
||||||
|
InputManager.Click(MouseButton.Left);
|
||||||
|
});
|
||||||
|
|
||||||
|
private IEnumerable<Dropdown<CollectionMenuItem>.DropdownMenu.DrawableDropdownMenuItem> getCollectionDropdownItems()
|
||||||
|
=> control.ChildrenOfType<CollectionFilterDropdown>().Single().ChildrenOfType<Dropdown<CollectionMenuItem>.DropdownMenu.DrawableDropdownMenuItem>();
|
||||||
|
}
|
||||||
|
}
|
@ -1,18 +1,11 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Audio;
|
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Platform;
|
|
||||||
using osu.Game.Beatmaps;
|
|
||||||
using osu.Game.Overlays;
|
using osu.Game.Overlays;
|
||||||
using osu.Game.Rulesets;
|
|
||||||
using osu.Game.Rulesets.Osu;
|
using osu.Game.Rulesets.Osu;
|
||||||
using osu.Game.Tests.Resources;
|
|
||||||
|
|
||||||
namespace osu.Game.Tests.Visual.UserInterface
|
namespace osu.Game.Tests.Visual.UserInterface
|
||||||
{
|
{
|
||||||
@ -24,14 +17,9 @@ namespace osu.Game.Tests.Visual.UserInterface
|
|||||||
|
|
||||||
private NowPlayingOverlay nowPlayingOverlay;
|
private NowPlayingOverlay nowPlayingOverlay;
|
||||||
|
|
||||||
private RulesetStore rulesets;
|
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(AudioManager audio, GameHost host)
|
private void load()
|
||||||
{
|
{
|
||||||
Dependencies.Cache(rulesets = new RulesetStore(ContextFactory));
|
|
||||||
Dependencies.Cache(manager = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, audio, host, Beatmap.Default));
|
|
||||||
|
|
||||||
Beatmap.Value = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo);
|
Beatmap.Value = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo);
|
||||||
|
|
||||||
nowPlayingOverlay = new NowPlayingOverlay
|
nowPlayingOverlay = new NowPlayingOverlay
|
||||||
@ -51,49 +39,5 @@ namespace osu.Game.Tests.Visual.UserInterface
|
|||||||
AddToggleStep(@"toggle beatmap lock", state => Beatmap.Disabled = state);
|
AddToggleStep(@"toggle beatmap lock", state => Beatmap.Disabled = state);
|
||||||
AddStep(@"hide", () => nowPlayingOverlay.Hide());
|
AddStep(@"hide", () => nowPlayingOverlay.Hide());
|
||||||
}
|
}
|
||||||
|
|
||||||
private BeatmapManager manager { get; set; }
|
|
||||||
|
|
||||||
private int importId;
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void TestPrevTrackBehavior()
|
|
||||||
{
|
|
||||||
// ensure we have at least two beatmaps available.
|
|
||||||
AddRepeatStep("import beatmap", () => manager.Import(new BeatmapSetInfo
|
|
||||||
{
|
|
||||||
Beatmaps = new List<BeatmapInfo>
|
|
||||||
{
|
|
||||||
new BeatmapInfo
|
|
||||||
{
|
|
||||||
BaseDifficulty = new BeatmapDifficulty(),
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Metadata = new BeatmapMetadata
|
|
||||||
{
|
|
||||||
Artist = $"a test map {importId++}",
|
|
||||||
Title = "title",
|
|
||||||
}
|
|
||||||
}).Wait(), 5);
|
|
||||||
|
|
||||||
WorkingBeatmap currentBeatmap = null;
|
|
||||||
|
|
||||||
AddStep("import beatmap with track", () =>
|
|
||||||
{
|
|
||||||
var setWithTrack = manager.Import(TestResources.GetTestBeatmapForImport()).Result;
|
|
||||||
Beatmap.Value = currentBeatmap = manager.GetWorkingBeatmap(setWithTrack.Beatmaps.First());
|
|
||||||
});
|
|
||||||
|
|
||||||
AddStep(@"Seek track to 6 second", () => musicController.SeekTo(6000));
|
|
||||||
AddUntilStep(@"Wait for current time to update", () => musicController.CurrentTrack.CurrentTime > 5000);
|
|
||||||
|
|
||||||
AddStep(@"Set previous", () => musicController.PreviousTrack());
|
|
||||||
|
|
||||||
AddAssert(@"Check beatmap didn't change", () => currentBeatmap == Beatmap.Value);
|
|
||||||
AddUntilStep("Wait for current time to update", () => musicController.CurrentTrack.CurrentTime < 5000);
|
|
||||||
|
|
||||||
AddStep(@"Set previous", () => musicController.PreviousTrack());
|
|
||||||
AddAssert(@"Check beatmap did change", () => currentBeatmap != Beatmap.Value);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -20,8 +20,6 @@ namespace osu.Game.Tournament.Screens.Ladder.Components
|
|||||||
{
|
{
|
||||||
private const int padding = 10;
|
private const int padding = 10;
|
||||||
|
|
||||||
protected override string Title => @"ladder";
|
|
||||||
|
|
||||||
private SettingsDropdown<TournamentRound> roundDropdown;
|
private SettingsDropdown<TournamentRound> roundDropdown;
|
||||||
private PlayerCheckbox losersCheckbox;
|
private PlayerCheckbox losersCheckbox;
|
||||||
private DateTextBox dateTimeBox;
|
private DateTextBox dateTimeBox;
|
||||||
@ -34,6 +32,11 @@ namespace osu.Game.Tournament.Screens.Ladder.Components
|
|||||||
[Resolved]
|
[Resolved]
|
||||||
private LadderInfo ladderInfo { get; set; }
|
private LadderInfo ladderInfo { get; set; }
|
||||||
|
|
||||||
|
public LadderEditorSettings()
|
||||||
|
: base("ladder")
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load()
|
private void load()
|
||||||
{
|
{
|
||||||
|
@ -207,11 +207,11 @@ namespace osu.Game.Beatmaps
|
|||||||
var calculator = ruleset.CreateDifficultyCalculator(beatmapManager.GetWorkingBeatmap(beatmapInfo));
|
var calculator = ruleset.CreateDifficultyCalculator(beatmapManager.GetWorkingBeatmap(beatmapInfo));
|
||||||
var attributes = calculator.Calculate(key.Mods);
|
var attributes = calculator.Calculate(key.Mods);
|
||||||
|
|
||||||
return difficultyCache[key] = new StarDifficulty(attributes.StarRating);
|
return difficultyCache[key] = new StarDifficulty(attributes.StarRating, attributes.MaxCombo);
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
return difficultyCache[key] = new StarDifficulty(0);
|
return difficultyCache[key] = new StarDifficulty();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -233,7 +233,7 @@ namespace osu.Game.Beatmaps
|
|||||||
if (beatmapInfo.ID == 0 || rulesetInfo.ID == null)
|
if (beatmapInfo.ID == 0 || rulesetInfo.ID == null)
|
||||||
{
|
{
|
||||||
// If not, fall back to the existing star difficulty (e.g. from an online source).
|
// If not, fall back to the existing star difficulty (e.g. from an online source).
|
||||||
existingDifficulty = new StarDifficulty(beatmapInfo.StarDifficulty);
|
existingDifficulty = new StarDifficulty(beatmapInfo.StarDifficulty, beatmapInfo.MaxCombo ?? 0);
|
||||||
key = default;
|
key = default;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@ -298,10 +298,12 @@ namespace osu.Game.Beatmaps
|
|||||||
public readonly struct StarDifficulty
|
public readonly struct StarDifficulty
|
||||||
{
|
{
|
||||||
public readonly double Stars;
|
public readonly double Stars;
|
||||||
|
public readonly int MaxCombo;
|
||||||
|
|
||||||
public StarDifficulty(double stars)
|
public StarDifficulty(double stars, int maxCombo)
|
||||||
{
|
{
|
||||||
Stars = stars;
|
Stars = stars;
|
||||||
|
MaxCombo = maxCombo;
|
||||||
|
|
||||||
// Todo: Add more members (BeatmapInfo.DifficultyRating? Attributes? Etc...)
|
// Todo: Add more members (BeatmapInfo.DifficultyRating? Attributes? Etc...)
|
||||||
}
|
}
|
||||||
|
@ -27,6 +27,7 @@ using osu.Game.Online.API;
|
|||||||
using osu.Game.Online.API.Requests;
|
using osu.Game.Online.API.Requests;
|
||||||
using osu.Game.Rulesets;
|
using osu.Game.Rulesets;
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
|
using osu.Game.Users;
|
||||||
using osu.Game.Skinning;
|
using osu.Game.Skinning;
|
||||||
using Decoder = osu.Game.Beatmaps.Formats.Decoder;
|
using Decoder = osu.Game.Beatmaps.Formats.Decoder;
|
||||||
|
|
||||||
@ -65,12 +66,14 @@ namespace osu.Game.Beatmaps
|
|||||||
private readonly RulesetStore rulesets;
|
private readonly RulesetStore rulesets;
|
||||||
private readonly BeatmapStore beatmaps;
|
private readonly BeatmapStore beatmaps;
|
||||||
private readonly AudioManager audioManager;
|
private readonly AudioManager audioManager;
|
||||||
private readonly BeatmapOnlineLookupQueue onlineLookupQueue;
|
|
||||||
private readonly TextureStore textureStore;
|
private readonly TextureStore textureStore;
|
||||||
private readonly ITrackStore trackStore;
|
private readonly ITrackStore trackStore;
|
||||||
|
|
||||||
|
[CanBeNull]
|
||||||
|
private readonly BeatmapOnlineLookupQueue onlineLookupQueue;
|
||||||
|
|
||||||
public BeatmapManager(Storage storage, IDatabaseContextFactory contextFactory, RulesetStore rulesets, IAPIProvider api, [NotNull] AudioManager audioManager, GameHost host = null,
|
public BeatmapManager(Storage storage, IDatabaseContextFactory contextFactory, RulesetStore rulesets, IAPIProvider api, [NotNull] AudioManager audioManager, GameHost host = null,
|
||||||
WorkingBeatmap defaultBeatmap = null)
|
WorkingBeatmap defaultBeatmap = null, bool performOnlineLookups = false)
|
||||||
: base(storage, contextFactory, api, new BeatmapStore(contextFactory), host)
|
: base(storage, contextFactory, api, new BeatmapStore(contextFactory), host)
|
||||||
{
|
{
|
||||||
this.rulesets = rulesets;
|
this.rulesets = rulesets;
|
||||||
@ -84,7 +87,8 @@ namespace osu.Game.Beatmaps
|
|||||||
beatmaps.ItemRemoved += removeWorkingCache;
|
beatmaps.ItemRemoved += removeWorkingCache;
|
||||||
beatmaps.ItemUpdated += removeWorkingCache;
|
beatmaps.ItemUpdated += removeWorkingCache;
|
||||||
|
|
||||||
onlineLookupQueue = new BeatmapOnlineLookupQueue(api, storage);
|
if (performOnlineLookups)
|
||||||
|
onlineLookupQueue = new BeatmapOnlineLookupQueue(api, storage);
|
||||||
|
|
||||||
textureStore = new LargeTextureStore(host?.CreateTextureLoaderStore(Files.Store));
|
textureStore = new LargeTextureStore(host?.CreateTextureLoaderStore(Files.Store));
|
||||||
trackStore = audioManager.GetTrackStore(Files.Store);
|
trackStore = audioManager.GetTrackStore(Files.Store);
|
||||||
@ -95,6 +99,34 @@ namespace osu.Game.Beatmaps
|
|||||||
|
|
||||||
protected override bool ShouldDeleteArchive(string path) => Path.GetExtension(path)?.ToLowerInvariant() == ".osz";
|
protected override bool ShouldDeleteArchive(string path) => Path.GetExtension(path)?.ToLowerInvariant() == ".osz";
|
||||||
|
|
||||||
|
public WorkingBeatmap CreateNew(RulesetInfo ruleset, User user)
|
||||||
|
{
|
||||||
|
var metadata = new BeatmapMetadata
|
||||||
|
{
|
||||||
|
Artist = "artist",
|
||||||
|
Title = "title",
|
||||||
|
Author = user,
|
||||||
|
};
|
||||||
|
|
||||||
|
var set = new BeatmapSetInfo
|
||||||
|
{
|
||||||
|
Metadata = metadata,
|
||||||
|
Beatmaps = new List<BeatmapInfo>
|
||||||
|
{
|
||||||
|
new BeatmapInfo
|
||||||
|
{
|
||||||
|
BaseDifficulty = new BeatmapDifficulty(),
|
||||||
|
Ruleset = ruleset,
|
||||||
|
Metadata = metadata,
|
||||||
|
Version = "difficulty"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var working = Import(set).Result;
|
||||||
|
return GetWorkingBeatmap(working.Beatmaps.First());
|
||||||
|
}
|
||||||
|
|
||||||
protected override async Task Populate(BeatmapSetInfo beatmapSet, ArchiveReader archive, CancellationToken cancellationToken = default)
|
protected override async Task Populate(BeatmapSetInfo beatmapSet, ArchiveReader archive, CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
if (archive != null)
|
if (archive != null)
|
||||||
@ -113,7 +145,8 @@ namespace osu.Game.Beatmaps
|
|||||||
|
|
||||||
bool hadOnlineBeatmapIDs = beatmapSet.Beatmaps.Any(b => b.OnlineBeatmapID > 0);
|
bool hadOnlineBeatmapIDs = beatmapSet.Beatmaps.Any(b => b.OnlineBeatmapID > 0);
|
||||||
|
|
||||||
await onlineLookupQueue.UpdateAsync(beatmapSet, cancellationToken);
|
if (onlineLookupQueue != null)
|
||||||
|
await onlineLookupQueue.UpdateAsync(beatmapSet, cancellationToken);
|
||||||
|
|
||||||
// ensure at least one beatmap was able to retrieve or keep an online ID, else drop the set ID.
|
// ensure at least one beatmap was able to retrieve or keep an online ID, else drop the set ID.
|
||||||
if (hadOnlineBeatmapIDs && !beatmapSet.Beatmaps.Any(b => b.OnlineBeatmapID > 0))
|
if (hadOnlineBeatmapIDs && !beatmapSet.Beatmaps.Any(b => b.OnlineBeatmapID > 0))
|
||||||
@ -202,7 +235,7 @@ namespace osu.Game.Beatmaps
|
|||||||
/// <param name="beatmapSkin">The beatmap <see cref="ISkin"/> content to write, null if to be omitted.</param>
|
/// <param name="beatmapSkin">The beatmap <see cref="ISkin"/> content to write, null if to be omitted.</param>
|
||||||
public void Save(BeatmapInfo info, IBeatmap beatmapContent, ISkin beatmapSkin = null)
|
public void Save(BeatmapInfo info, IBeatmap beatmapContent, ISkin beatmapSkin = null)
|
||||||
{
|
{
|
||||||
var setInfo = QueryBeatmapSet(s => s.Beatmaps.Any(b => b.ID == info.ID));
|
var setInfo = info.BeatmapSet;
|
||||||
|
|
||||||
using (var stream = new MemoryStream())
|
using (var stream = new MemoryStream())
|
||||||
{
|
{
|
||||||
@ -214,10 +247,20 @@ namespace osu.Game.Beatmaps
|
|||||||
using (ContextFactory.GetForWrite())
|
using (ContextFactory.GetForWrite())
|
||||||
{
|
{
|
||||||
var beatmapInfo = setInfo.Beatmaps.Single(b => b.ID == info.ID);
|
var beatmapInfo = setInfo.Beatmaps.Single(b => b.ID == info.ID);
|
||||||
|
var metadata = beatmapInfo.Metadata ?? setInfo.Metadata;
|
||||||
|
|
||||||
|
// grab the original file (or create a new one if not found).
|
||||||
|
var fileInfo = setInfo.Files.SingleOrDefault(f => string.Equals(f.Filename, beatmapInfo.Path, StringComparison.OrdinalIgnoreCase)) ?? new BeatmapSetFileInfo();
|
||||||
|
|
||||||
|
// metadata may have changed; update the path with the standard format.
|
||||||
|
beatmapInfo.Path = $"{metadata.Artist} - {metadata.Title} ({metadata.Author}) [{beatmapInfo.Version}].osu";
|
||||||
beatmapInfo.MD5Hash = stream.ComputeMD5Hash();
|
beatmapInfo.MD5Hash = stream.ComputeMD5Hash();
|
||||||
|
|
||||||
|
// update existing or populate new file's filename.
|
||||||
|
fileInfo.Filename = beatmapInfo.Path;
|
||||||
|
|
||||||
stream.Seek(0, SeekOrigin.Begin);
|
stream.Seek(0, SeekOrigin.Begin);
|
||||||
UpdateFile(setInfo, setInfo.Files.Single(f => string.Equals(f.Filename, info.Path, StringComparison.OrdinalIgnoreCase)), stream);
|
UpdateFile(setInfo, fileInfo, stream);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -33,6 +33,9 @@ namespace osu.Game.Beatmaps
|
|||||||
|
|
||||||
protected override IBeatmap GetBeatmap()
|
protected override IBeatmap GetBeatmap()
|
||||||
{
|
{
|
||||||
|
if (BeatmapInfo.Path == null)
|
||||||
|
return new Beatmap { BeatmapInfo = BeatmapInfo };
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
using (var stream = new LineBufferedReader(store.GetStream(getPathForFile(BeatmapInfo.Path))))
|
using (var stream = new LineBufferedReader(store.GetStream(getPathForFile(BeatmapInfo.Path))))
|
||||||
@ -67,6 +70,9 @@ namespace osu.Game.Beatmaps
|
|||||||
|
|
||||||
protected override Track GetBeatmapTrack()
|
protected override Track GetBeatmapTrack()
|
||||||
{
|
{
|
||||||
|
if (Metadata?.AudioFile == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
return trackStore.Get(getPathForFile(Metadata.AudioFile));
|
return trackStore.Get(getPathForFile(Metadata.AudioFile));
|
||||||
@ -80,6 +86,9 @@ namespace osu.Game.Beatmaps
|
|||||||
|
|
||||||
protected override Waveform GetWaveform()
|
protected override Waveform GetWaveform()
|
||||||
{
|
{
|
||||||
|
if (Metadata?.AudioFile == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var trackData = store.GetStream(getPathForFile(Metadata.AudioFile));
|
var trackData = store.GetStream(getPathForFile(Metadata.AudioFile));
|
||||||
|
@ -55,7 +55,7 @@ namespace osu.Game.Beatmaps
|
|||||||
{
|
{
|
||||||
const double excess_length = 1000;
|
const double excess_length = 1000;
|
||||||
|
|
||||||
var lastObject = Beatmap.HitObjects.LastOrDefault();
|
var lastObject = Beatmap?.HitObjects.LastOrDefault();
|
||||||
|
|
||||||
double length;
|
double length;
|
||||||
|
|
||||||
|
47
osu.Game/Collections/BeatmapCollection.cs
Normal file
47
osu.Game/Collections/BeatmapCollection.cs
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
// 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 osu.Framework.Bindables;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
|
||||||
|
namespace osu.Game.Collections
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A collection of beatmaps grouped by a name.
|
||||||
|
/// </summary>
|
||||||
|
public class BeatmapCollection
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Invoked whenever any change occurs on this <see cref="BeatmapCollection"/>.
|
||||||
|
/// </summary>
|
||||||
|
public event Action Changed;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The collection's name.
|
||||||
|
/// </summary>
|
||||||
|
public readonly Bindable<string> Name = new Bindable<string>();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The beatmaps contained by the collection.
|
||||||
|
/// </summary>
|
||||||
|
public readonly BindableList<BeatmapInfo> Beatmaps = new BindableList<BeatmapInfo>();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The date when this collection was last modified.
|
||||||
|
/// </summary>
|
||||||
|
public DateTimeOffset LastModifyDate { get; private set; } = DateTimeOffset.UtcNow;
|
||||||
|
|
||||||
|
public BeatmapCollection()
|
||||||
|
{
|
||||||
|
Beatmaps.CollectionChanged += (_, __) => onChange();
|
||||||
|
Name.ValueChanged += _ => onChange();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onChange()
|
||||||
|
{
|
||||||
|
LastModifyDate = DateTimeOffset.Now;
|
||||||
|
Changed?.Invoke();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
303
osu.Game/Collections/CollectionManager.cs
Normal file
303
osu.Game/Collections/CollectionManager.cs
Normal file
@ -0,0 +1,303 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Collections.Specialized;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using osu.Framework;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Logging;
|
||||||
|
using osu.Framework.Platform;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.IO.Legacy;
|
||||||
|
using osu.Game.Overlays.Notifications;
|
||||||
|
|
||||||
|
namespace osu.Game.Collections
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Handles user-defined collections of beatmaps.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// This is currently reading and writing from the osu-stable file format. This is a temporary arrangement until we refactor the
|
||||||
|
/// database backing the game. Going forward writing should be done in a similar way to other model stores.
|
||||||
|
/// </remarks>
|
||||||
|
public class CollectionManager : Component
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Database version in stable-compatible YYYYMMDD format.
|
||||||
|
/// </summary>
|
||||||
|
private const int database_version = 30000000;
|
||||||
|
|
||||||
|
private const string database_name = "collection.db";
|
||||||
|
|
||||||
|
public readonly BindableList<BeatmapCollection> Collections = new BindableList<BeatmapCollection>();
|
||||||
|
|
||||||
|
public bool SupportsImportFromStable => RuntimeInfo.IsDesktop;
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private GameHost host { get; set; }
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private BeatmapManager beatmaps { get; set; }
|
||||||
|
|
||||||
|
private readonly Storage storage;
|
||||||
|
|
||||||
|
public CollectionManager(Storage storage)
|
||||||
|
{
|
||||||
|
this.storage = storage;
|
||||||
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load()
|
||||||
|
{
|
||||||
|
Collections.CollectionChanged += collectionsChanged;
|
||||||
|
|
||||||
|
if (storage.Exists(database_name))
|
||||||
|
{
|
||||||
|
using (var stream = storage.GetStream(database_name))
|
||||||
|
importCollections(readCollections(stream));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void collectionsChanged(object sender, NotifyCollectionChangedEventArgs e)
|
||||||
|
{
|
||||||
|
switch (e.Action)
|
||||||
|
{
|
||||||
|
case NotifyCollectionChangedAction.Add:
|
||||||
|
foreach (var c in e.NewItems.Cast<BeatmapCollection>())
|
||||||
|
c.Changed += backgroundSave;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case NotifyCollectionChangedAction.Remove:
|
||||||
|
foreach (var c in e.OldItems.Cast<BeatmapCollection>())
|
||||||
|
c.Changed -= backgroundSave;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case NotifyCollectionChangedAction.Replace:
|
||||||
|
foreach (var c in e.OldItems.Cast<BeatmapCollection>())
|
||||||
|
c.Changed -= backgroundSave;
|
||||||
|
|
||||||
|
foreach (var c in e.NewItems.Cast<BeatmapCollection>())
|
||||||
|
c.Changed += backgroundSave;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
backgroundSave();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Set an endpoint for notifications to be posted to.
|
||||||
|
/// </summary>
|
||||||
|
public Action<Notification> PostNotification { protected get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Set a storage with access to an osu-stable install for import purposes.
|
||||||
|
/// </summary>
|
||||||
|
public Func<Storage> GetStableStorage { private get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This is a temporary method and will likely be replaced by a full-fledged (and more correctly placed) migration process in the future.
|
||||||
|
/// </summary>
|
||||||
|
public Task ImportFromStableAsync()
|
||||||
|
{
|
||||||
|
var stable = GetStableStorage?.Invoke();
|
||||||
|
|
||||||
|
if (stable == null)
|
||||||
|
{
|
||||||
|
Logger.Log("No osu!stable installation available!", LoggingTarget.Information, LogLevel.Error);
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!stable.Exists(database_name))
|
||||||
|
{
|
||||||
|
// This handles situations like when the user does not have a collections.db file
|
||||||
|
Logger.Log($"No {database_name} available in osu!stable installation", LoggingTarget.Information, LogLevel.Error);
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Task.Run(async () =>
|
||||||
|
{
|
||||||
|
using (var stream = stable.GetStream(database_name))
|
||||||
|
await Import(stream);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task Import(Stream stream)
|
||||||
|
{
|
||||||
|
var notification = new ProgressNotification
|
||||||
|
{
|
||||||
|
State = ProgressNotificationState.Active,
|
||||||
|
Text = "Collections import is initialising..."
|
||||||
|
};
|
||||||
|
|
||||||
|
PostNotification?.Invoke(notification);
|
||||||
|
|
||||||
|
var collection = readCollections(stream, notification);
|
||||||
|
bool importCompleted = false;
|
||||||
|
|
||||||
|
Schedule(() =>
|
||||||
|
{
|
||||||
|
importCollections(collection);
|
||||||
|
importCompleted = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
while (!IsDisposed && !importCompleted)
|
||||||
|
await Task.Delay(10);
|
||||||
|
|
||||||
|
notification.CompletionText = $"Imported {collection.Count} collections";
|
||||||
|
notification.State = ProgressNotificationState.Completed;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void importCollections(List<BeatmapCollection> newCollections)
|
||||||
|
{
|
||||||
|
foreach (var newCol in newCollections)
|
||||||
|
{
|
||||||
|
var existing = Collections.FirstOrDefault(c => c.Name == newCol.Name);
|
||||||
|
if (existing == null)
|
||||||
|
Collections.Add(existing = new BeatmapCollection { Name = { Value = newCol.Name.Value } });
|
||||||
|
|
||||||
|
foreach (var newBeatmap in newCol.Beatmaps)
|
||||||
|
{
|
||||||
|
if (!existing.Beatmaps.Contains(newBeatmap))
|
||||||
|
existing.Beatmaps.Add(newBeatmap);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<BeatmapCollection> readCollections(Stream stream, ProgressNotification notification = null)
|
||||||
|
{
|
||||||
|
if (notification != null)
|
||||||
|
{
|
||||||
|
notification.Text = "Reading collections...";
|
||||||
|
notification.Progress = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
var result = new List<BeatmapCollection>();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using (var sr = new SerializationReader(stream))
|
||||||
|
{
|
||||||
|
sr.ReadInt32(); // Version
|
||||||
|
|
||||||
|
int collectionCount = sr.ReadInt32();
|
||||||
|
result.Capacity = collectionCount;
|
||||||
|
|
||||||
|
for (int i = 0; i < collectionCount; i++)
|
||||||
|
{
|
||||||
|
if (notification?.CancellationToken.IsCancellationRequested == true)
|
||||||
|
return result;
|
||||||
|
|
||||||
|
var collection = new BeatmapCollection { Name = { Value = sr.ReadString() } };
|
||||||
|
int mapCount = sr.ReadInt32();
|
||||||
|
|
||||||
|
for (int j = 0; j < mapCount; j++)
|
||||||
|
{
|
||||||
|
if (notification?.CancellationToken.IsCancellationRequested == true)
|
||||||
|
return result;
|
||||||
|
|
||||||
|
string checksum = sr.ReadString();
|
||||||
|
|
||||||
|
var beatmap = beatmaps.QueryBeatmap(b => b.MD5Hash == checksum);
|
||||||
|
if (beatmap != null)
|
||||||
|
collection.Beatmaps.Add(beatmap);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (notification != null)
|
||||||
|
{
|
||||||
|
notification.Text = $"Imported {i + 1} of {collectionCount} collections";
|
||||||
|
notification.Progress = (float)(i + 1) / collectionCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
result.Add(collection);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Logger.Error(e, "Failed to read collection database.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void DeleteAll()
|
||||||
|
{
|
||||||
|
Collections.Clear();
|
||||||
|
PostNotification?.Invoke(new SimpleNotification { Text = "Deleted all collections!" });
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly object saveLock = new object();
|
||||||
|
private int lastSave;
|
||||||
|
private int saveFailures;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Perform a save with debounce.
|
||||||
|
/// </summary>
|
||||||
|
private void backgroundSave()
|
||||||
|
{
|
||||||
|
var current = Interlocked.Increment(ref lastSave);
|
||||||
|
Task.Delay(100).ContinueWith(task =>
|
||||||
|
{
|
||||||
|
if (current != lastSave)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!save())
|
||||||
|
backgroundSave();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool save()
|
||||||
|
{
|
||||||
|
lock (saveLock)
|
||||||
|
{
|
||||||
|
Interlocked.Increment(ref lastSave);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// This is NOT thread-safe!!
|
||||||
|
|
||||||
|
using (var sw = new SerializationWriter(storage.GetStream(database_name, FileAccess.Write)))
|
||||||
|
{
|
||||||
|
sw.Write(database_version);
|
||||||
|
sw.Write(Collections.Count);
|
||||||
|
|
||||||
|
foreach (var c in Collections)
|
||||||
|
{
|
||||||
|
sw.Write(c.Name.Value);
|
||||||
|
sw.Write(c.Beatmaps.Count);
|
||||||
|
|
||||||
|
foreach (var b in c.Beatmaps)
|
||||||
|
sw.Write(b.MD5Hash);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (saveFailures < 10)
|
||||||
|
saveFailures = 0;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
// Since this code is not thread-safe, we may run into random exceptions (such as collection enumeration or out of range indexing).
|
||||||
|
// Failures are thus only alerted if they exceed a threshold (once) to indicate "actual" errors having occurred.
|
||||||
|
if (++saveFailures == 10)
|
||||||
|
Logger.Error(e, "Failed to save collection database!");
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Dispose(bool isDisposing)
|
||||||
|
{
|
||||||
|
base.Dispose(isDisposing);
|
||||||
|
save();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
34
osu.Game/Collections/DeleteCollectionDialog.cs
Normal file
34
osu.Game/Collections/DeleteCollectionDialog.cs
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
// 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 Humanizer;
|
||||||
|
using osu.Framework.Graphics.Sprites;
|
||||||
|
using osu.Game.Overlays.Dialog;
|
||||||
|
|
||||||
|
namespace osu.Game.Collections
|
||||||
|
{
|
||||||
|
public class DeleteCollectionDialog : PopupDialog
|
||||||
|
{
|
||||||
|
public DeleteCollectionDialog(BeatmapCollection collection, Action deleteAction)
|
||||||
|
{
|
||||||
|
HeaderText = "Confirm deletion of";
|
||||||
|
BodyText = $"{collection.Name.Value} ({"beatmap".ToQuantity(collection.Beatmaps.Count)})";
|
||||||
|
|
||||||
|
Icon = FontAwesome.Regular.TrashAlt;
|
||||||
|
|
||||||
|
Buttons = new PopupDialogButton[]
|
||||||
|
{
|
||||||
|
new PopupDialogOkButton
|
||||||
|
{
|
||||||
|
Text = @"Yes. Go for it.",
|
||||||
|
Action = deleteAction
|
||||||
|
},
|
||||||
|
new PopupDialogCancelButton
|
||||||
|
{
|
||||||
|
Text = @"No! Abort mission!",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
122
osu.Game/Collections/DrawableCollectionList.cs
Normal file
122
osu.Game/Collections/DrawableCollectionList.cs
Normal file
@ -0,0 +1,122 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System.Linq;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Game.Graphics.Containers;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
|
namespace osu.Game.Collections
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Visualises a list of <see cref="BeatmapCollection"/>s.
|
||||||
|
/// </summary>
|
||||||
|
public class DrawableCollectionList : OsuRearrangeableListContainer<BeatmapCollection>
|
||||||
|
{
|
||||||
|
private Scroll scroll;
|
||||||
|
|
||||||
|
protected override ScrollContainer<Drawable> CreateScrollContainer() => scroll = new Scroll();
|
||||||
|
|
||||||
|
protected override FillFlowContainer<RearrangeableListItem<BeatmapCollection>> CreateListFillFlowContainer() => new Flow
|
||||||
|
{
|
||||||
|
DragActive = { BindTarget = DragActive }
|
||||||
|
};
|
||||||
|
|
||||||
|
protected override OsuRearrangeableListItem<BeatmapCollection> CreateOsuDrawable(BeatmapCollection item)
|
||||||
|
{
|
||||||
|
if (item == scroll.PlaceholderItem.Model)
|
||||||
|
return scroll.ReplacePlaceholder();
|
||||||
|
|
||||||
|
return new DrawableCollectionListItem(item, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The scroll container for this <see cref="DrawableCollectionList"/>.
|
||||||
|
/// Contains the main flow of <see cref="DrawableCollectionListItem"/> and attaches a placeholder item to the end of the list.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Use <see cref="ReplacePlaceholder"/> to transfer the placeholder into the main list.
|
||||||
|
/// </remarks>
|
||||||
|
private class Scroll : OsuScrollContainer
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The currently-displayed placeholder item.
|
||||||
|
/// </summary>
|
||||||
|
public DrawableCollectionListItem PlaceholderItem { get; private set; }
|
||||||
|
|
||||||
|
protected override Container<Drawable> Content => content;
|
||||||
|
private readonly Container content;
|
||||||
|
|
||||||
|
private readonly Container<DrawableCollectionListItem> placeholderContainer;
|
||||||
|
|
||||||
|
public Scroll()
|
||||||
|
{
|
||||||
|
ScrollbarVisible = false;
|
||||||
|
Padding = new MarginPadding(10);
|
||||||
|
|
||||||
|
base.Content.Add(new FillFlowContainer
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
AutoSizeAxes = Axes.Y,
|
||||||
|
LayoutDuration = 200,
|
||||||
|
LayoutEasing = Easing.OutQuint,
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
content = new Container { RelativeSizeAxes = Axes.X },
|
||||||
|
placeholderContainer = new Container<DrawableCollectionListItem>
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
AutoSizeAxes = Axes.Y
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
ReplacePlaceholder();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Update()
|
||||||
|
{
|
||||||
|
base.Update();
|
||||||
|
|
||||||
|
// AutoSizeAxes cannot be used as the height should represent the post-layout-transform height at all times, so that the placeholder doesn't bounce around.
|
||||||
|
content.Height = ((Flow)Child).Children.Sum(c => c.DrawHeight + 5);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Replaces the current <see cref="PlaceholderItem"/> with a new one, and returns the previous.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>The current <see cref="PlaceholderItem"/>.</returns>
|
||||||
|
public DrawableCollectionListItem ReplacePlaceholder()
|
||||||
|
{
|
||||||
|
var previous = PlaceholderItem;
|
||||||
|
|
||||||
|
placeholderContainer.Clear(false);
|
||||||
|
placeholderContainer.Add(PlaceholderItem = new DrawableCollectionListItem(new BeatmapCollection(), false));
|
||||||
|
|
||||||
|
return previous;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The flow of <see cref="DrawableCollectionListItem"/>. Disables layout easing unless a drag is in progress.
|
||||||
|
/// </summary>
|
||||||
|
private class Flow : FillFlowContainer<RearrangeableListItem<BeatmapCollection>>
|
||||||
|
{
|
||||||
|
public readonly IBindable<bool> DragActive = new Bindable<bool>();
|
||||||
|
|
||||||
|
public Flow()
|
||||||
|
{
|
||||||
|
Spacing = new Vector2(0, 5);
|
||||||
|
LayoutEasing = Easing.OutQuint;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
DragActive.BindValueChanged(active => LayoutDuration = active.NewValue ? 200 : 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
237
osu.Game/Collections/DrawableCollectionListItem.cs
Normal file
237
osu.Game/Collections/DrawableCollectionListItem.cs
Normal file
@ -0,0 +1,237 @@
|
|||||||
|
// 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 osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Extensions.Color4Extensions;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Shapes;
|
||||||
|
using osu.Framework.Graphics.Sprites;
|
||||||
|
using osu.Framework.Input.Events;
|
||||||
|
using osu.Game.Graphics;
|
||||||
|
using osu.Game.Graphics.Containers;
|
||||||
|
using osu.Game.Graphics.UserInterface;
|
||||||
|
using osu.Game.Overlays;
|
||||||
|
using osuTK;
|
||||||
|
using osuTK.Graphics;
|
||||||
|
|
||||||
|
namespace osu.Game.Collections
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Visualises a <see cref="BeatmapCollection"/> inside a <see cref="DrawableCollectionList"/>.
|
||||||
|
/// </summary>
|
||||||
|
public class DrawableCollectionListItem : OsuRearrangeableListItem<BeatmapCollection>
|
||||||
|
{
|
||||||
|
private const float item_height = 35;
|
||||||
|
private const float button_width = item_height * 0.75f;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether the <see cref="BeatmapCollection"/> currently exists inside the <see cref="CollectionManager"/>.
|
||||||
|
/// </summary>
|
||||||
|
public IBindable<bool> IsCreated => isCreated;
|
||||||
|
|
||||||
|
private readonly Bindable<bool> isCreated = new Bindable<bool>();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new <see cref="DrawableCollectionListItem"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="item">The <see cref="BeatmapCollection"/>.</param>
|
||||||
|
/// <param name="isCreated">Whether <paramref name="item"/> currently exists inside the <see cref="CollectionManager"/>.</param>
|
||||||
|
public DrawableCollectionListItem(BeatmapCollection item, bool isCreated)
|
||||||
|
: base(item)
|
||||||
|
{
|
||||||
|
this.isCreated.Value = isCreated;
|
||||||
|
|
||||||
|
ShowDragHandle.BindTo(this.isCreated);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override Drawable CreateContent() => new ItemContent(Model)
|
||||||
|
{
|
||||||
|
IsCreated = { BindTarget = isCreated }
|
||||||
|
};
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The main content of the <see cref="DrawableCollectionListItem"/>.
|
||||||
|
/// </summary>
|
||||||
|
private class ItemContent : CircularContainer
|
||||||
|
{
|
||||||
|
public readonly Bindable<bool> IsCreated = new Bindable<bool>();
|
||||||
|
|
||||||
|
private readonly IBindable<string> collectionName;
|
||||||
|
private readonly BeatmapCollection collection;
|
||||||
|
|
||||||
|
[Resolved(CanBeNull = true)]
|
||||||
|
private CollectionManager collectionManager { get; set; }
|
||||||
|
|
||||||
|
private Container textBoxPaddingContainer;
|
||||||
|
private ItemTextBox textBox;
|
||||||
|
|
||||||
|
public ItemContent(BeatmapCollection collection)
|
||||||
|
{
|
||||||
|
this.collection = collection;
|
||||||
|
|
||||||
|
RelativeSizeAxes = Axes.X;
|
||||||
|
Height = item_height;
|
||||||
|
Masking = true;
|
||||||
|
|
||||||
|
collectionName = collection.Name.GetBoundCopy();
|
||||||
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(OsuColour colours)
|
||||||
|
{
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
new DeleteButton(collection)
|
||||||
|
{
|
||||||
|
Anchor = Anchor.CentreRight,
|
||||||
|
Origin = Anchor.CentreRight,
|
||||||
|
IsCreated = { BindTarget = IsCreated },
|
||||||
|
IsTextBoxHovered = v => textBox.ReceivePositionalInputAt(v)
|
||||||
|
},
|
||||||
|
textBoxPaddingContainer = new Container
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Padding = new MarginPadding { Right = button_width },
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
textBox = new ItemTextBox
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Size = Vector2.One,
|
||||||
|
CornerRadius = item_height / 2,
|
||||||
|
Current = collection.Name,
|
||||||
|
PlaceholderText = IsCreated.Value ? string.Empty : "Create a new collection"
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
|
||||||
|
collectionName.BindValueChanged(_ => createNewCollection(), true);
|
||||||
|
IsCreated.BindValueChanged(created => textBoxPaddingContainer.Padding = new MarginPadding { Right = created.NewValue ? button_width : 0 }, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void createNewCollection()
|
||||||
|
{
|
||||||
|
if (IsCreated.Value)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(collectionName.Value))
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Add the new collection and disable our placeholder. If all text is removed, the placeholder should not show back again.
|
||||||
|
collectionManager?.Collections.Add(collection);
|
||||||
|
textBox.PlaceholderText = string.Empty;
|
||||||
|
|
||||||
|
// When this item changes from placeholder to non-placeholder (via changing containers), its textbox will lose focus, so it needs to be re-focused.
|
||||||
|
Schedule(() => GetContainingInputManager().ChangeFocus(textBox));
|
||||||
|
|
||||||
|
IsCreated.Value = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class ItemTextBox : OsuTextBox
|
||||||
|
{
|
||||||
|
protected override float LeftRightPadding => item_height / 2;
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(OsuColour colours)
|
||||||
|
{
|
||||||
|
BackgroundUnfocused = colours.GreySeafoamDarker.Darken(0.5f);
|
||||||
|
BackgroundFocused = colours.GreySeafoam;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class DeleteButton : CompositeDrawable
|
||||||
|
{
|
||||||
|
public readonly IBindable<bool> IsCreated = new Bindable<bool>();
|
||||||
|
|
||||||
|
public Func<Vector2, bool> IsTextBoxHovered;
|
||||||
|
|
||||||
|
[Resolved(CanBeNull = true)]
|
||||||
|
private DialogOverlay dialogOverlay { get; set; }
|
||||||
|
|
||||||
|
[Resolved(CanBeNull = true)]
|
||||||
|
private CollectionManager collectionManager { get; set; }
|
||||||
|
|
||||||
|
private readonly BeatmapCollection collection;
|
||||||
|
|
||||||
|
private Drawable fadeContainer;
|
||||||
|
private Drawable background;
|
||||||
|
|
||||||
|
public DeleteButton(BeatmapCollection collection)
|
||||||
|
{
|
||||||
|
this.collection = collection;
|
||||||
|
RelativeSizeAxes = Axes.Y;
|
||||||
|
|
||||||
|
Width = button_width + item_height / 2; // add corner radius to cover with fill
|
||||||
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(OsuColour colours)
|
||||||
|
{
|
||||||
|
InternalChild = fadeContainer = new Container
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Alpha = 0.1f,
|
||||||
|
Children = new[]
|
||||||
|
{
|
||||||
|
background = new Box
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Colour = colours.Red
|
||||||
|
},
|
||||||
|
new SpriteIcon
|
||||||
|
{
|
||||||
|
Anchor = Anchor.CentreRight,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
X = -button_width * 0.6f,
|
||||||
|
Size = new Vector2(10),
|
||||||
|
Icon = FontAwesome.Solid.Trash
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
IsCreated.BindValueChanged(created => Alpha = created.NewValue ? 1 : 0, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => base.ReceivePositionalInputAt(screenSpacePos) && !IsTextBoxHovered(screenSpacePos);
|
||||||
|
|
||||||
|
protected override bool OnHover(HoverEvent e)
|
||||||
|
{
|
||||||
|
fadeContainer.FadeTo(1f, 100, Easing.Out);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnHoverLost(HoverLostEvent e)
|
||||||
|
{
|
||||||
|
fadeContainer.FadeTo(0.1f, 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override bool OnClick(ClickEvent e)
|
||||||
|
{
|
||||||
|
background.FlashColour(Color4.White, 150);
|
||||||
|
|
||||||
|
if (collection.Beatmaps.Count == 0)
|
||||||
|
deleteCollection();
|
||||||
|
else
|
||||||
|
dialogOverlay?.Push(new DeleteCollectionDialog(collection, deleteCollection));
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void deleteCollection() => collectionManager?.Collections.Remove(collection);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
134
osu.Game/Collections/ManageCollectionsDialog.cs
Normal file
134
osu.Game/Collections/ManageCollectionsDialog.cs
Normal file
@ -0,0 +1,134 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Shapes;
|
||||||
|
using osu.Framework.Graphics.Sprites;
|
||||||
|
using osu.Game.Graphics;
|
||||||
|
using osu.Game.Graphics.Containers;
|
||||||
|
using osu.Game.Graphics.Sprites;
|
||||||
|
using osu.Game.Graphics.UserInterface;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
|
namespace osu.Game.Collections
|
||||||
|
{
|
||||||
|
public class ManageCollectionsDialog : OsuFocusedOverlayContainer
|
||||||
|
{
|
||||||
|
private const double enter_duration = 500;
|
||||||
|
private const double exit_duration = 200;
|
||||||
|
|
||||||
|
[Resolved(CanBeNull = true)]
|
||||||
|
private CollectionManager collectionManager { get; set; }
|
||||||
|
|
||||||
|
public ManageCollectionsDialog()
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre;
|
||||||
|
Origin = Anchor.Centre;
|
||||||
|
|
||||||
|
RelativeSizeAxes = Axes.Both;
|
||||||
|
Size = new Vector2(0.5f, 0.8f);
|
||||||
|
|
||||||
|
Masking = true;
|
||||||
|
CornerRadius = 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(OsuColour colours)
|
||||||
|
{
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
new Box
|
||||||
|
{
|
||||||
|
Colour = colours.GreySeafoamDark,
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
},
|
||||||
|
new Container
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Child = new GridContainer
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
RowDimensions = new[]
|
||||||
|
{
|
||||||
|
new Dimension(GridSizeMode.AutoSize),
|
||||||
|
},
|
||||||
|
Content = new[]
|
||||||
|
{
|
||||||
|
new Drawable[]
|
||||||
|
{
|
||||||
|
new Container
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
AutoSizeAxes = Axes.Y,
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
new OsuSpriteText
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
Text = "Manage collections",
|
||||||
|
Font = OsuFont.GetFont(size: 30),
|
||||||
|
Padding = new MarginPadding { Vertical = 10 },
|
||||||
|
},
|
||||||
|
new IconButton
|
||||||
|
{
|
||||||
|
Anchor = Anchor.CentreRight,
|
||||||
|
Origin = Anchor.CentreRight,
|
||||||
|
Icon = FontAwesome.Solid.Times,
|
||||||
|
Colour = colours.GreySeafoamDarker,
|
||||||
|
Scale = new Vector2(0.8f),
|
||||||
|
X = -10,
|
||||||
|
Action = () => State.Value = Visibility.Hidden
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
new Drawable[]
|
||||||
|
{
|
||||||
|
new Container
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
new Box
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Colour = colours.GreySeafoamDarker
|
||||||
|
},
|
||||||
|
new DrawableCollectionList
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Items = { BindTarget = collectionManager?.Collections ?? new BindableList<BeatmapCollection>() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void PopIn()
|
||||||
|
{
|
||||||
|
base.PopIn();
|
||||||
|
|
||||||
|
this.FadeIn(enter_duration, Easing.OutQuint);
|
||||||
|
this.ScaleTo(0.9f).Then().ScaleTo(1f, enter_duration, Easing.OutQuint);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void PopOut()
|
||||||
|
{
|
||||||
|
base.PopOut();
|
||||||
|
|
||||||
|
this.FadeOut(exit_duration, Easing.OutQuint);
|
||||||
|
this.ScaleTo(0.9f, exit_duration);
|
||||||
|
|
||||||
|
// Ensure that textboxes commit
|
||||||
|
GetContainingInputManager()?.TriggerFocusContention(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -397,15 +397,24 @@ namespace osu.Game.Database
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Update an existing file, or create a new entry if not already part of the <paramref name="model"/>'s files.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="model">The item to operate on.</param>
|
||||||
|
/// <param name="file">The file model to be updated or added.</param>
|
||||||
|
/// <param name="contents">The new file contents.</param>
|
||||||
public void UpdateFile(TModel model, TFileModel file, Stream contents)
|
public void UpdateFile(TModel model, TFileModel file, Stream contents)
|
||||||
{
|
{
|
||||||
using (var usage = ContextFactory.GetForWrite())
|
using (var usage = ContextFactory.GetForWrite())
|
||||||
{
|
{
|
||||||
// Dereference the existing file info, since the file model will be removed.
|
// Dereference the existing file info, since the file model will be removed.
|
||||||
Files.Dereference(file.FileInfo);
|
if (file.FileInfo != null)
|
||||||
|
{
|
||||||
|
Files.Dereference(file.FileInfo);
|
||||||
|
|
||||||
// Remove the file model.
|
// Remove the file model.
|
||||||
usage.Context.Set<TFileModel>().Remove(file);
|
usage.Context.Set<TFileModel>().Remove(file);
|
||||||
|
}
|
||||||
|
|
||||||
// Add the new file info and containing file model.
|
// Add the new file info and containing file model.
|
||||||
model.Files.Remove(file);
|
model.Files.Remove(file);
|
||||||
|
@ -103,6 +103,8 @@ namespace osu.Game.Graphics.Containers
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private bool playedPopInSound;
|
||||||
|
|
||||||
protected override void UpdateState(ValueChangedEvent<Visibility> state)
|
protected override void UpdateState(ValueChangedEvent<Visibility> state)
|
||||||
{
|
{
|
||||||
switch (state.NewValue)
|
switch (state.NewValue)
|
||||||
@ -110,16 +112,24 @@ namespace osu.Game.Graphics.Containers
|
|||||||
case Visibility.Visible:
|
case Visibility.Visible:
|
||||||
if (OverlayActivationMode.Value == OverlayActivation.Disabled)
|
if (OverlayActivationMode.Value == OverlayActivation.Disabled)
|
||||||
{
|
{
|
||||||
|
// todo: visual/audible feedback that this operation could not complete.
|
||||||
State.Value = Visibility.Hidden;
|
State.Value = Visibility.Hidden;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
samplePopIn?.Play();
|
samplePopIn?.Play();
|
||||||
|
playedPopInSound = true;
|
||||||
|
|
||||||
if (BlockScreenWideMouse && DimMainContent) game?.AddBlockingOverlay(this);
|
if (BlockScreenWideMouse && DimMainContent) game?.AddBlockingOverlay(this);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Visibility.Hidden:
|
case Visibility.Hidden:
|
||||||
samplePopOut?.Play();
|
if (playedPopInSound)
|
||||||
|
{
|
||||||
|
samplePopOut?.Play();
|
||||||
|
playedPopInSound = false;
|
||||||
|
}
|
||||||
|
|
||||||
if (BlockScreenWideMouse) game?.RemoveBlockingOverlay(this);
|
if (BlockScreenWideMouse) game?.RemoveBlockingOverlay(this);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -12,13 +12,13 @@ namespace osu.Game.Graphics.Containers
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Whether any item is currently being dragged. Used to hide other items' drag handles.
|
/// Whether any item is currently being dragged. Used to hide other items' drag handles.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private readonly BindableBool playlistDragActive = new BindableBool();
|
protected readonly BindableBool DragActive = new BindableBool();
|
||||||
|
|
||||||
protected override ScrollContainer<Drawable> CreateScrollContainer() => new OsuScrollContainer();
|
protected override ScrollContainer<Drawable> CreateScrollContainer() => new OsuScrollContainer();
|
||||||
|
|
||||||
protected sealed override RearrangeableListItem<TModel> CreateDrawable(TModel item) => CreateOsuDrawable(item).With(d =>
|
protected sealed override RearrangeableListItem<TModel> CreateDrawable(TModel item) => CreateOsuDrawable(item).With(d =>
|
||||||
{
|
{
|
||||||
d.PlaylistDragActive.BindTo(playlistDragActive);
|
d.DragActive.BindTo(DragActive);
|
||||||
});
|
});
|
||||||
|
|
||||||
protected abstract OsuRearrangeableListItem<TModel> CreateOsuDrawable(TModel item);
|
protected abstract OsuRearrangeableListItem<TModel> CreateOsuDrawable(TModel item);
|
||||||
|
@ -19,7 +19,7 @@ namespace osu.Game.Graphics.Containers
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Whether any item is currently being dragged. Used to hide other items' drag handles.
|
/// Whether any item is currently being dragged. Used to hide other items' drag handles.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public readonly BindableBool PlaylistDragActive = new BindableBool();
|
public readonly BindableBool DragActive = new BindableBool();
|
||||||
|
|
||||||
private Color4 handleColour = Color4.White;
|
private Color4 handleColour = Color4.White;
|
||||||
|
|
||||||
@ -44,8 +44,9 @@ namespace osu.Game.Graphics.Containers
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Whether the drag handle should be shown.
|
/// Whether the drag handle should be shown.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
protected virtual bool ShowDragHandle => true;
|
protected readonly Bindable<bool> ShowDragHandle = new Bindable<bool>();
|
||||||
|
|
||||||
|
private Container handleContainer;
|
||||||
private PlaylistItemHandle handle;
|
private PlaylistItemHandle handle;
|
||||||
|
|
||||||
protected OsuRearrangeableListItem(TModel item)
|
protected OsuRearrangeableListItem(TModel item)
|
||||||
@ -58,8 +59,6 @@ namespace osu.Game.Graphics.Containers
|
|||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load()
|
private void load()
|
||||||
{
|
{
|
||||||
Container handleContainer;
|
|
||||||
|
|
||||||
InternalChild = new GridContainer
|
InternalChild = new GridContainer
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.X,
|
RelativeSizeAxes = Axes.X,
|
||||||
@ -88,9 +87,12 @@ namespace osu.Game.Graphics.Containers
|
|||||||
ColumnDimensions = new[] { new Dimension(GridSizeMode.AutoSize) },
|
ColumnDimensions = new[] { new Dimension(GridSizeMode.AutoSize) },
|
||||||
RowDimensions = new[] { new Dimension(GridSizeMode.AutoSize) }
|
RowDimensions = new[] { new Dimension(GridSizeMode.AutoSize) }
|
||||||
};
|
};
|
||||||
|
}
|
||||||
|
|
||||||
if (!ShowDragHandle)
|
protected override void LoadComplete()
|
||||||
handleContainer.Alpha = 0;
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
ShowDragHandle.BindValueChanged(show => handleContainer.Alpha = show.NewValue ? 1 : 0, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override bool OnDragStart(DragStartEvent e)
|
protected override bool OnDragStart(DragStartEvent e)
|
||||||
@ -98,13 +100,13 @@ namespace osu.Game.Graphics.Containers
|
|||||||
if (!base.OnDragStart(e))
|
if (!base.OnDragStart(e))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
PlaylistDragActive.Value = true;
|
DragActive.Value = true;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnDragEnd(DragEndEvent e)
|
protected override void OnDragEnd(DragEndEvent e)
|
||||||
{
|
{
|
||||||
PlaylistDragActive.Value = false;
|
DragActive.Value = false;
|
||||||
base.OnDragEnd(e);
|
base.OnDragEnd(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -112,7 +114,7 @@ namespace osu.Game.Graphics.Containers
|
|||||||
|
|
||||||
protected override bool OnHover(HoverEvent e)
|
protected override bool OnHover(HoverEvent e)
|
||||||
{
|
{
|
||||||
handle.UpdateHoverState(IsDragged || !PlaylistDragActive.Value);
|
handle.UpdateHoverState(IsDragged || !DragActive.Value);
|
||||||
return base.OnHover(e);
|
return base.OnHover(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Colour;
|
using osu.Framework.Graphics.Colour;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
@ -55,6 +56,12 @@ namespace osu.Game.Graphics.Sprites
|
|||||||
set => spriteText.UseFullGlyphHeight = blurredText.UseFullGlyphHeight = value;
|
set => spriteText.UseFullGlyphHeight = blurredText.UseFullGlyphHeight = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Bindable<string> Current
|
||||||
|
{
|
||||||
|
get => spriteText.Current;
|
||||||
|
set => spriteText.Current = value;
|
||||||
|
}
|
||||||
|
|
||||||
public GlowingSpriteText()
|
public GlowingSpriteText()
|
||||||
{
|
{
|
||||||
AutoSizeAxes = Axes.Both;
|
AutoSizeAxes = Axes.Both;
|
||||||
|
@ -24,7 +24,7 @@ namespace osu.Game.Graphics.UserInterface
|
|||||||
set
|
set
|
||||||
{
|
{
|
||||||
iconColour = value;
|
iconColour = value;
|
||||||
icon.Colour = value;
|
icon.FadeColour(value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -26,6 +26,8 @@ namespace osu.Game.Graphics.UserInterface
|
|||||||
};
|
};
|
||||||
|
|
||||||
ItemsContainer.Padding = new MarginPadding { Vertical = DrawableOsuMenuItem.MARGIN_VERTICAL };
|
ItemsContainer.Padding = new MarginPadding { Vertical = DrawableOsuMenuItem.MARGIN_VERTICAL };
|
||||||
|
|
||||||
|
MaxHeight = 250;
|
||||||
}
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
|
@ -17,6 +17,8 @@ namespace osu.Game.Graphics.UserInterface
|
|||||||
{
|
{
|
||||||
public class OsuDropdown<T> : Dropdown<T>, IHasAccentColour
|
public class OsuDropdown<T> : Dropdown<T>, IHasAccentColour
|
||||||
{
|
{
|
||||||
|
private const float corner_radius = 4;
|
||||||
|
|
||||||
private Color4 accentColour;
|
private Color4 accentColour;
|
||||||
|
|
||||||
public Color4 AccentColour
|
public Color4 AccentColour
|
||||||
@ -57,9 +59,11 @@ namespace osu.Game.Graphics.UserInterface
|
|||||||
// todo: this uses the same styling as OsuMenu. hopefully we can just use OsuMenu in the future with some refactoring
|
// todo: this uses the same styling as OsuMenu. hopefully we can just use OsuMenu in the future with some refactoring
|
||||||
public OsuDropdownMenu()
|
public OsuDropdownMenu()
|
||||||
{
|
{
|
||||||
CornerRadius = 4;
|
CornerRadius = corner_radius;
|
||||||
BackgroundColour = Color4.Black.Opacity(0.5f);
|
BackgroundColour = Color4.Black.Opacity(0.5f);
|
||||||
|
|
||||||
|
MaskingContainer.CornerRadius = corner_radius;
|
||||||
|
|
||||||
// todo: this uses the same styling as OsuMenu. hopefully we can just use OsuMenu in the future with some refactoring
|
// todo: this uses the same styling as OsuMenu. hopefully we can just use OsuMenu in the future with some refactoring
|
||||||
ItemsContainer.Padding = new MarginPadding(5);
|
ItemsContainer.Padding = new MarginPadding(5);
|
||||||
}
|
}
|
||||||
@ -138,7 +142,7 @@ namespace osu.Game.Graphics.UserInterface
|
|||||||
Foreground.Padding = new MarginPadding(2);
|
Foreground.Padding = new MarginPadding(2);
|
||||||
|
|
||||||
Masking = true;
|
Masking = true;
|
||||||
CornerRadius = 6;
|
CornerRadius = corner_radius;
|
||||||
}
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
@ -237,7 +241,7 @@ namespace osu.Game.Graphics.UserInterface
|
|||||||
|
|
||||||
AutoSizeAxes = Axes.None;
|
AutoSizeAxes = Axes.None;
|
||||||
Margin = new MarginPadding { Bottom = 4 };
|
Margin = new MarginPadding { Bottom = 4 };
|
||||||
CornerRadius = 4;
|
CornerRadius = corner_radius;
|
||||||
Height = 40;
|
Height = 40;
|
||||||
|
|
||||||
Foreground.Children = new Drawable[]
|
Foreground.Children = new Drawable[]
|
||||||
|
@ -9,16 +9,20 @@ using System;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Graphics.UserInterface;
|
||||||
|
|
||||||
namespace osu.Game.Graphics.UserInterface
|
namespace osu.Game.Graphics.UserInterface
|
||||||
{
|
{
|
||||||
public abstract class RollingCounter<T> : Container
|
public abstract class RollingCounter<T> : Container, IHasCurrentValue<T>
|
||||||
where T : struct, IEquatable<T>
|
where T : struct, IEquatable<T>
|
||||||
{
|
{
|
||||||
/// <summary>
|
private readonly BindableWithCurrent<T> current = new BindableWithCurrent<T>();
|
||||||
/// The current value.
|
|
||||||
/// </summary>
|
public Bindable<T> Current
|
||||||
public Bindable<T> Current = new Bindable<T>();
|
{
|
||||||
|
get => current.Current;
|
||||||
|
set => current.Current = value;
|
||||||
|
}
|
||||||
|
|
||||||
private SpriteText displayedCountSpriteText;
|
private SpriteText displayedCountSpriteText;
|
||||||
|
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Graphics.UserInterface;
|
using osu.Framework.Graphics.UserInterface;
|
||||||
using osu.Game.Graphics.UserInterface;
|
using osu.Game.Graphics.UserInterface;
|
||||||
|
|
||||||
@ -32,6 +33,11 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
|||||||
set => Component.Text = value;
|
set => Component.Text = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Container TabbableContentContainer
|
||||||
|
{
|
||||||
|
set => Component.TabbableContentContainer = value;
|
||||||
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(OsuColour colours)
|
private void load(OsuColour colours)
|
||||||
{
|
{
|
||||||
|
@ -25,7 +25,13 @@ namespace osu.Game.IO
|
|||||||
this.subPath = subPath;
|
this.subPath = subPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected virtual string MutatePath(string path) => !string.IsNullOrEmpty(subPath) ? Path.Combine(subPath, path) : path;
|
protected virtual string MutatePath(string path)
|
||||||
|
{
|
||||||
|
if (path == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
return !string.IsNullOrEmpty(subPath) ? Path.Combine(subPath, path) : path;
|
||||||
|
}
|
||||||
|
|
||||||
protected virtual void ChangeTargetStorage(Storage newStorage)
|
protected virtual void ChangeTargetStorage(Storage newStorage)
|
||||||
{
|
{
|
||||||
|
@ -56,13 +56,14 @@ namespace osu.Game.Online.Leaderboards
|
|||||||
scrollFlow?.FadeOut(fade_duration, Easing.OutQuint).Expire();
|
scrollFlow?.FadeOut(fade_duration, Easing.OutQuint).Expire();
|
||||||
scrollFlow = null;
|
scrollFlow = null;
|
||||||
|
|
||||||
loading.Hide();
|
|
||||||
|
|
||||||
showScoresDelegate?.Cancel();
|
showScoresDelegate?.Cancel();
|
||||||
showScoresCancellationSource?.Cancel();
|
showScoresCancellationSource?.Cancel();
|
||||||
|
|
||||||
if (scores == null || !scores.Any())
|
if (scores == null || !scores.Any())
|
||||||
|
{
|
||||||
|
loading.Hide();
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// ensure placeholder is hidden when displaying scores
|
// ensure placeholder is hidden when displaying scores
|
||||||
PlaceholderState = PlaceholderState.Successful;
|
PlaceholderState = PlaceholderState.Successful;
|
||||||
@ -84,6 +85,7 @@ namespace osu.Game.Online.Leaderboards
|
|||||||
}
|
}
|
||||||
|
|
||||||
scrollContainer.ScrollTo(0f, false);
|
scrollContainer.ScrollTo(0f, false);
|
||||||
|
loading.Hide();
|
||||||
}, (showScoresCancellationSource = new CancellationTokenSource()).Token));
|
}, (showScoresCancellationSource = new CancellationTokenSource()).Token));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -72,7 +72,7 @@ namespace osu.Game.Online.Leaderboards
|
|||||||
}
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(IAPIProvider api, OsuColour colour)
|
private void load(IAPIProvider api, OsuColour colour, ScoreManager scoreManager)
|
||||||
{
|
{
|
||||||
var user = score.User;
|
var user = score.User;
|
||||||
|
|
||||||
@ -194,7 +194,7 @@ namespace osu.Game.Online.Leaderboards
|
|||||||
{
|
{
|
||||||
TextColour = Color4.White,
|
TextColour = Color4.White,
|
||||||
GlowColour = Color4Extensions.FromHex(@"83ccfa"),
|
GlowColour = Color4Extensions.FromHex(@"83ccfa"),
|
||||||
Text = score.TotalScore.ToString(@"N0"),
|
Current = scoreManager.GetBindableTotalScoreString(score),
|
||||||
Font = OsuFont.Numeric.With(size: 23),
|
Font = OsuFont.Numeric.With(size: 23),
|
||||||
},
|
},
|
||||||
RankContainer = new Container
|
RankContainer = new Container
|
||||||
|
@ -31,6 +31,7 @@ using osu.Framework.Input.Events;
|
|||||||
using osu.Framework.Platform;
|
using osu.Framework.Platform;
|
||||||
using osu.Framework.Threading;
|
using osu.Framework.Threading;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Collections;
|
||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Graphics.Containers;
|
using osu.Game.Graphics.Containers;
|
||||||
using osu.Game.Graphics.UserInterface;
|
using osu.Game.Graphics.UserInterface;
|
||||||
@ -38,6 +39,7 @@ using osu.Game.Input;
|
|||||||
using osu.Game.Overlays.Notifications;
|
using osu.Game.Overlays.Notifications;
|
||||||
using osu.Game.Input.Bindings;
|
using osu.Game.Input.Bindings;
|
||||||
using osu.Game.Online.Chat;
|
using osu.Game.Online.Chat;
|
||||||
|
using osu.Game.Overlays.Music;
|
||||||
using osu.Game.Skinning;
|
using osu.Game.Skinning;
|
||||||
using osuTK.Graphics;
|
using osuTK.Graphics;
|
||||||
using osu.Game.Overlays.Volume;
|
using osu.Game.Overlays.Volume;
|
||||||
@ -609,12 +611,19 @@ namespace osu.Game
|
|||||||
d.Origin = Anchor.TopRight;
|
d.Origin = Anchor.TopRight;
|
||||||
}), rightFloatingOverlayContent.Add, true);
|
}), rightFloatingOverlayContent.Add, true);
|
||||||
|
|
||||||
|
loadComponentSingleFile(new CollectionManager(Storage)
|
||||||
|
{
|
||||||
|
PostNotification = n => notifications.Post(n),
|
||||||
|
GetStableStorage = GetStorageForStableInstall
|
||||||
|
}, Add, true);
|
||||||
|
|
||||||
loadComponentSingleFile(screenshotManager, Add);
|
loadComponentSingleFile(screenshotManager, Add);
|
||||||
|
|
||||||
// dependency on notification overlay, dependent by settings overlay
|
// dependency on notification overlay, dependent by settings overlay
|
||||||
loadComponentSingleFile(CreateUpdateManager(), Add, true);
|
loadComponentSingleFile(CreateUpdateManager(), Add, true);
|
||||||
|
|
||||||
// overlay elements
|
// overlay elements
|
||||||
|
loadComponentSingleFile(new ManageCollectionsDialog(), overlayContent.Add, true);
|
||||||
loadComponentSingleFile(beatmapListing = new BeatmapListingOverlay(), overlayContent.Add, true);
|
loadComponentSingleFile(beatmapListing = new BeatmapListingOverlay(), overlayContent.Add, true);
|
||||||
loadComponentSingleFile(dashboard = new DashboardOverlay(), overlayContent.Add, true);
|
loadComponentSingleFile(dashboard = new DashboardOverlay(), overlayContent.Add, true);
|
||||||
loadComponentSingleFile(news = new NewsOverlay(), overlayContent.Add, true);
|
loadComponentSingleFile(news = new NewsOverlay(), overlayContent.Add, true);
|
||||||
@ -647,6 +656,7 @@ namespace osu.Game
|
|||||||
chatOverlay.State.ValueChanged += state => channelManager.HighPollRate.Value = state.NewValue == Visibility.Visible;
|
chatOverlay.State.ValueChanged += state => channelManager.HighPollRate.Value = state.NewValue == Visibility.Visible;
|
||||||
|
|
||||||
Add(externalLinkOpener = new ExternalLinkOpener());
|
Add(externalLinkOpener = new ExternalLinkOpener());
|
||||||
|
Add(new MusicKeyBindingHandler());
|
||||||
|
|
||||||
// side overlays which cancel each other.
|
// side overlays which cancel each other.
|
||||||
var singleDisplaySideOverlays = new OverlayContainer[] { Settings, notifications };
|
var singleDisplaySideOverlays = new OverlayContainer[] { Settings, notifications };
|
||||||
|
@ -58,6 +58,8 @@ namespace osu.Game
|
|||||||
|
|
||||||
protected ScoreManager ScoreManager;
|
protected ScoreManager ScoreManager;
|
||||||
|
|
||||||
|
protected BeatmapDifficultyManager DifficultyManager;
|
||||||
|
|
||||||
protected SkinManager SkinManager;
|
protected SkinManager SkinManager;
|
||||||
|
|
||||||
protected RulesetStore RulesetStore;
|
protected RulesetStore RulesetStore;
|
||||||
@ -197,8 +199,8 @@ namespace osu.Game
|
|||||||
dependencies.Cache(FileStore = new FileStore(contextFactory, Storage));
|
dependencies.Cache(FileStore = new FileStore(contextFactory, Storage));
|
||||||
|
|
||||||
// ordering is important here to ensure foreign keys rules are not broken in ModelStore.Cleanup()
|
// ordering is important here to ensure foreign keys rules are not broken in ModelStore.Cleanup()
|
||||||
dependencies.Cache(ScoreManager = new ScoreManager(RulesetStore, () => BeatmapManager, Storage, API, contextFactory, Host));
|
dependencies.Cache(ScoreManager = new ScoreManager(RulesetStore, () => BeatmapManager, Storage, API, contextFactory, Host, () => DifficultyManager, LocalConfig));
|
||||||
dependencies.Cache(BeatmapManager = new BeatmapManager(Storage, contextFactory, RulesetStore, API, Audio, Host, defaultBeatmap));
|
dependencies.Cache(BeatmapManager = new BeatmapManager(Storage, contextFactory, RulesetStore, API, Audio, Host, defaultBeatmap, true));
|
||||||
|
|
||||||
// this should likely be moved to ArchiveModelManager when another case appers where it is necessary
|
// this should likely be moved to ArchiveModelManager when another case appers where it is necessary
|
||||||
// to have inter-dependent model managers. this could be obtained with an IHasForeign<T> interface to
|
// to have inter-dependent model managers. this could be obtained with an IHasForeign<T> interface to
|
||||||
@ -221,9 +223,8 @@ namespace osu.Game
|
|||||||
ScoreManager.Undelete(getBeatmapScores(item), true);
|
ScoreManager.Undelete(getBeatmapScores(item), true);
|
||||||
});
|
});
|
||||||
|
|
||||||
var difficultyManager = new BeatmapDifficultyManager();
|
dependencies.Cache(DifficultyManager = new BeatmapDifficultyManager());
|
||||||
dependencies.Cache(difficultyManager);
|
AddInternal(DifficultyManager);
|
||||||
AddInternal(difficultyManager);
|
|
||||||
|
|
||||||
dependencies.Cache(KeyBindingStore = new KeyBindingStore(contextFactory, RulesetStore));
|
dependencies.Cache(KeyBindingStore = new KeyBindingStore(contextFactory, RulesetStore));
|
||||||
dependencies.Cache(SettingsStore = new SettingsStore(contextFactory));
|
dependencies.Cache(SettingsStore = new SettingsStore(contextFactory));
|
||||||
@ -250,10 +251,11 @@ namespace osu.Game
|
|||||||
AddInternal(apiAccess);
|
AddInternal(apiAccess);
|
||||||
AddInternal(RulesetConfigCache);
|
AddInternal(RulesetConfigCache);
|
||||||
|
|
||||||
GlobalActionContainer globalBinding;
|
|
||||||
|
|
||||||
MenuCursorContainer = new MenuCursorContainer { RelativeSizeAxes = Axes.Both };
|
MenuCursorContainer = new MenuCursorContainer { RelativeSizeAxes = Axes.Both };
|
||||||
MenuCursorContainer.Child = globalBinding = new GlobalActionContainer(this)
|
|
||||||
|
GlobalActionContainer globalBindings;
|
||||||
|
|
||||||
|
MenuCursorContainer.Child = globalBindings = new GlobalActionContainer(this)
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Child = content = new OsuTooltipContainer(MenuCursorContainer.Cursor) { RelativeSizeAxes = Axes.Both }
|
Child = content = new OsuTooltipContainer(MenuCursorContainer.Cursor) { RelativeSizeAxes = Axes.Both }
|
||||||
@ -261,8 +263,8 @@ namespace osu.Game
|
|||||||
|
|
||||||
base.Content.Add(CreateScalingContainer().WithChild(MenuCursorContainer));
|
base.Content.Add(CreateScalingContainer().WithChild(MenuCursorContainer));
|
||||||
|
|
||||||
KeyBindingStore.Register(globalBinding);
|
KeyBindingStore.Register(globalBindings);
|
||||||
dependencies.Cache(globalBinding);
|
dependencies.Cache(globalBindings);
|
||||||
|
|
||||||
PreviewTrackManager previewTrackManager;
|
PreviewTrackManager previewTrackManager;
|
||||||
dependencies.Cache(previewTrackManager = new PreviewTrackManager());
|
dependencies.Cache(previewTrackManager = new PreviewTrackManager());
|
||||||
|
@ -25,6 +25,9 @@ namespace osu.Game.Overlays.BeatmapSet.Scores
|
|||||||
private const float row_height = 22;
|
private const float row_height = 22;
|
||||||
private const int text_size = 12;
|
private const int text_size = 12;
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private ScoreManager scoreManager { get; set; }
|
||||||
|
|
||||||
private readonly FillFlowContainer backgroundFlow;
|
private readonly FillFlowContainer backgroundFlow;
|
||||||
|
|
||||||
private Color4 highAccuracyColour;
|
private Color4 highAccuracyColour;
|
||||||
@ -121,7 +124,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores
|
|||||||
new OsuSpriteText
|
new OsuSpriteText
|
||||||
{
|
{
|
||||||
Margin = new MarginPadding { Right = horizontal_inset },
|
Margin = new MarginPadding { Right = horizontal_inset },
|
||||||
Text = $@"{score.TotalScore:N0}",
|
Current = scoreManager.GetBindableTotalScoreString(score),
|
||||||
Font = OsuFont.GetFont(size: text_size, weight: index == 0 ? FontWeight.Bold : FontWeight.Medium)
|
Font = OsuFont.GetFont(size: text_size, weight: index == 0 ? FontWeight.Bold : FontWeight.Medium)
|
||||||
},
|
},
|
||||||
new OsuSpriteText
|
new OsuSpriteText
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Extensions;
|
using osu.Framework.Extensions;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
@ -38,6 +39,9 @@ namespace osu.Game.Overlays.BeatmapSet.Scores
|
|||||||
private readonly FillFlowContainer<InfoColumn> statisticsColumns;
|
private readonly FillFlowContainer<InfoColumn> statisticsColumns;
|
||||||
private readonly ModsInfoColumn modsColumn;
|
private readonly ModsInfoColumn modsColumn;
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private ScoreManager scoreManager { get; set; }
|
||||||
|
|
||||||
public TopScoreStatisticsSection()
|
public TopScoreStatisticsSection()
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.X;
|
RelativeSizeAxes = Axes.X;
|
||||||
@ -87,6 +91,15 @@ namespace osu.Game.Overlays.BeatmapSet.Scores
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load()
|
||||||
|
{
|
||||||
|
if (score != null)
|
||||||
|
totalScoreColumn.Current = scoreManager.GetBindableTotalScoreString(score);
|
||||||
|
}
|
||||||
|
|
||||||
|
private ScoreInfo score;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Sets the score to be displayed.
|
/// Sets the score to be displayed.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -94,7 +107,11 @@ namespace osu.Game.Overlays.BeatmapSet.Scores
|
|||||||
{
|
{
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
totalScoreColumn.Text = $@"{value.TotalScore:N0}";
|
if (score == value)
|
||||||
|
return;
|
||||||
|
|
||||||
|
score = value;
|
||||||
|
|
||||||
accuracyColumn.Text = value.DisplayAccuracy;
|
accuracyColumn.Text = value.DisplayAccuracy;
|
||||||
maxComboColumn.Text = $@"{value.MaxCombo:N0}x";
|
maxComboColumn.Text = $@"{value.MaxCombo:N0}x";
|
||||||
ppColumn.Alpha = value.Beatmap?.Status == BeatmapSetOnlineStatus.Ranked ? 1 : 0;
|
ppColumn.Alpha = value.Beatmap?.Status == BeatmapSetOnlineStatus.Ranked ? 1 : 0;
|
||||||
@ -102,6 +119,9 @@ namespace osu.Game.Overlays.BeatmapSet.Scores
|
|||||||
|
|
||||||
statisticsColumns.ChildrenEnumerable = value.SortedStatistics.Select(kvp => createStatisticsColumn(kvp.Key, kvp.Value));
|
statisticsColumns.ChildrenEnumerable = value.SortedStatistics.Select(kvp => createStatisticsColumn(kvp.Key, kvp.Value));
|
||||||
modsColumn.Mods = value.Mods;
|
modsColumn.Mods = value.Mods;
|
||||||
|
|
||||||
|
if (scoreManager != null)
|
||||||
|
totalScoreColumn.Current = scoreManager.GetBindableTotalScoreString(value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -190,6 +210,12 @@ namespace osu.Game.Overlays.BeatmapSet.Scores
|
|||||||
{
|
{
|
||||||
set => text.Text = value;
|
set => text.Text = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Bindable<string> Current
|
||||||
|
{
|
||||||
|
get => text.Current;
|
||||||
|
set => text.Current = value;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class ModsInfoColumn : InfoColumn
|
private class ModsInfoColumn : InfoColumn
|
||||||
|
81
osu.Game/Overlays/Music/MusicKeyBindingHandler.cs
Normal file
81
osu.Game/Overlays/Music/MusicKeyBindingHandler.cs
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Input.Bindings;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Input.Bindings;
|
||||||
|
using osu.Game.Overlays.OSD;
|
||||||
|
|
||||||
|
namespace osu.Game.Overlays.Music
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Handles <see cref="GlobalAction"/>s related to music playback, and displays <see cref="Toast"/>s via the global <see cref="OnScreenDisplay"/> accordingly.
|
||||||
|
/// </summary>
|
||||||
|
public class MusicKeyBindingHandler : Component, IKeyBindingHandler<GlobalAction>
|
||||||
|
{
|
||||||
|
[Resolved]
|
||||||
|
private IBindable<WorkingBeatmap> beatmap { get; set; }
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private MusicController musicController { get; set; }
|
||||||
|
|
||||||
|
[Resolved(canBeNull: true)]
|
||||||
|
private OnScreenDisplay onScreenDisplay { get; set; }
|
||||||
|
|
||||||
|
public bool OnPressed(GlobalAction action)
|
||||||
|
{
|
||||||
|
if (beatmap.Disabled)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
switch (action)
|
||||||
|
{
|
||||||
|
case GlobalAction.MusicPlay:
|
||||||
|
// use previous state as TogglePause may not update the track's state immediately (state update is run on the audio thread see https://github.com/ppy/osu/issues/9880#issuecomment-674668842)
|
||||||
|
bool wasPlaying = musicController.IsPlaying;
|
||||||
|
|
||||||
|
if (musicController.TogglePause())
|
||||||
|
onScreenDisplay?.Display(new MusicActionToast(wasPlaying ? "Pause track" : "Play track"));
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case GlobalAction.MusicNext:
|
||||||
|
musicController.NextTrack(() => onScreenDisplay?.Display(new MusicActionToast("Next track")));
|
||||||
|
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case GlobalAction.MusicPrev:
|
||||||
|
musicController.PreviousTrack(res =>
|
||||||
|
{
|
||||||
|
switch (res)
|
||||||
|
{
|
||||||
|
case PreviousTrackResult.Restart:
|
||||||
|
onScreenDisplay?.Display(new MusicActionToast("Restart track"));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case PreviousTrackResult.Previous:
|
||||||
|
onScreenDisplay?.Display(new MusicActionToast("Previous track"));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnReleased(GlobalAction action)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
private class MusicActionToast : Toast
|
||||||
|
{
|
||||||
|
public MusicActionToast(string action)
|
||||||
|
: base("Music Playback", action, string.Empty)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -12,12 +12,9 @@ using osu.Framework.Bindables;
|
|||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Audio;
|
using osu.Framework.Graphics.Audio;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Input.Bindings;
|
|
||||||
using osu.Framework.Utils;
|
using osu.Framework.Utils;
|
||||||
using osu.Framework.Threading;
|
using osu.Framework.Threading;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Input.Bindings;
|
|
||||||
using osu.Game.Overlays.OSD;
|
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
|
|
||||||
namespace osu.Game.Overlays
|
namespace osu.Game.Overlays
|
||||||
@ -25,7 +22,7 @@ namespace osu.Game.Overlays
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Handles playback of the global music track.
|
/// Handles playback of the global music track.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class MusicController : CompositeDrawable, IKeyBindingHandler<GlobalAction>
|
public class MusicController : CompositeDrawable
|
||||||
{
|
{
|
||||||
[Resolved]
|
[Resolved]
|
||||||
private BeatmapManager beatmaps { get; set; }
|
private BeatmapManager beatmaps { get; set; }
|
||||||
@ -62,9 +59,6 @@ namespace osu.Game.Overlays
|
|||||||
[Resolved]
|
[Resolved]
|
||||||
private IBindable<IReadOnlyList<Mod>> mods { get; set; }
|
private IBindable<IReadOnlyList<Mod>> mods { get; set; }
|
||||||
|
|
||||||
[Resolved(canBeNull: true)]
|
|
||||||
private OnScreenDisplay onScreenDisplay { get; set; }
|
|
||||||
|
|
||||||
[NotNull]
|
[NotNull]
|
||||||
public DrawableTrack CurrentTrack { get; private set; } = new DrawableTrack(new TrackVirtual(1000));
|
public DrawableTrack CurrentTrack { get; private set; } = new DrawableTrack(new TrackVirtual(1000));
|
||||||
|
|
||||||
@ -207,7 +201,13 @@ namespace osu.Game.Overlays
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Play the previous track or restart the current track if it's current time below <see cref="restart_cutoff_point"/>.
|
/// Play the previous track or restart the current track if it's current time below <see cref="restart_cutoff_point"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void PreviousTrack() => Schedule(() => prev());
|
/// <param name="onSuccess">Invoked when the operation has been performed successfully.</param>
|
||||||
|
public void PreviousTrack(Action<PreviousTrackResult> onSuccess = null) => Schedule(() =>
|
||||||
|
{
|
||||||
|
PreviousTrackResult res = prev();
|
||||||
|
if (res != PreviousTrackResult.None)
|
||||||
|
onSuccess?.Invoke(res);
|
||||||
|
});
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Play the previous track or restart the current track if it's current time below <see cref="restart_cutoff_point"/>.
|
/// Play the previous track or restart the current track if it's current time below <see cref="restart_cutoff_point"/>.
|
||||||
@ -243,7 +243,14 @@ namespace osu.Game.Overlays
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Play the next random or playlist track.
|
/// Play the next random or playlist track.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void NextTrack() => Schedule(() => next());
|
/// <param name="onSuccess">Invoked when the operation has been performed successfully.</param>
|
||||||
|
/// <returns>A <see cref="ScheduledDelegate"/> of the operation.</returns>
|
||||||
|
public void NextTrack(Action onSuccess = null) => Schedule(() =>
|
||||||
|
{
|
||||||
|
bool res = next();
|
||||||
|
if (res)
|
||||||
|
onSuccess?.Invoke();
|
||||||
|
});
|
||||||
|
|
||||||
private bool next()
|
private bool next()
|
||||||
{
|
{
|
||||||
@ -407,54 +414,6 @@ namespace osu.Game.Overlays
|
|||||||
mod.ApplyToTrack(CurrentTrack);
|
mod.ApplyToTrack(CurrentTrack);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool OnPressed(GlobalAction action)
|
|
||||||
{
|
|
||||||
if (beatmap.Disabled)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
switch (action)
|
|
||||||
{
|
|
||||||
case GlobalAction.MusicPlay:
|
|
||||||
if (TogglePause())
|
|
||||||
onScreenDisplay?.Display(new MusicControllerToast(IsPlaying ? "Play track" : "Pause track"));
|
|
||||||
return true;
|
|
||||||
|
|
||||||
case GlobalAction.MusicNext:
|
|
||||||
if (next())
|
|
||||||
onScreenDisplay?.Display(new MusicControllerToast("Next track"));
|
|
||||||
|
|
||||||
return true;
|
|
||||||
|
|
||||||
case GlobalAction.MusicPrev:
|
|
||||||
switch (prev())
|
|
||||||
{
|
|
||||||
case PreviousTrackResult.Restart:
|
|
||||||
onScreenDisplay?.Display(new MusicControllerToast("Restart track"));
|
|
||||||
break;
|
|
||||||
|
|
||||||
case PreviousTrackResult.Previous:
|
|
||||||
onScreenDisplay?.Display(new MusicControllerToast("Previous track"));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void OnReleased(GlobalAction action)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public class MusicControllerToast : Toast
|
|
||||||
{
|
|
||||||
public MusicControllerToast(string action)
|
|
||||||
: base("Music Playback", action, string.Empty)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum TrackChangeDirection
|
public enum TrackChangeDirection
|
||||||
|
@ -3,9 +3,11 @@
|
|||||||
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using JetBrains.Annotations;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Collections;
|
||||||
using osu.Game.Graphics.UserInterface;
|
using osu.Game.Graphics.UserInterface;
|
||||||
using osu.Game.Scoring;
|
using osu.Game.Scoring;
|
||||||
using osu.Game.Skinning;
|
using osu.Game.Skinning;
|
||||||
@ -19,14 +21,15 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance
|
|||||||
private TriangleButton importBeatmapsButton;
|
private TriangleButton importBeatmapsButton;
|
||||||
private TriangleButton importScoresButton;
|
private TriangleButton importScoresButton;
|
||||||
private TriangleButton importSkinsButton;
|
private TriangleButton importSkinsButton;
|
||||||
|
private TriangleButton importCollectionsButton;
|
||||||
private TriangleButton deleteBeatmapsButton;
|
private TriangleButton deleteBeatmapsButton;
|
||||||
private TriangleButton deleteScoresButton;
|
private TriangleButton deleteScoresButton;
|
||||||
private TriangleButton deleteSkinsButton;
|
private TriangleButton deleteSkinsButton;
|
||||||
private TriangleButton restoreButton;
|
private TriangleButton restoreButton;
|
||||||
private TriangleButton undeleteButton;
|
private TriangleButton undeleteButton;
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader(permitNulls: true)]
|
||||||
private void load(BeatmapManager beatmaps, ScoreManager scores, SkinManager skins, DialogOverlay dialogOverlay)
|
private void load(BeatmapManager beatmaps, ScoreManager scores, SkinManager skins, [CanBeNull] CollectionManager collectionManager, DialogOverlay dialogOverlay)
|
||||||
{
|
{
|
||||||
if (beatmaps.SupportsImportFromStable)
|
if (beatmaps.SupportsImportFromStable)
|
||||||
{
|
{
|
||||||
@ -93,20 +96,46 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
AddRange(new Drawable[]
|
Add(deleteSkinsButton = new DangerousSettingsButton
|
||||||
{
|
{
|
||||||
deleteSkinsButton = new DangerousSettingsButton
|
Text = "Delete ALL skins",
|
||||||
|
Action = () =>
|
||||||
{
|
{
|
||||||
Text = "Delete ALL skins",
|
dialogOverlay?.Push(new DeleteAllBeatmapsDialog(() =>
|
||||||
|
{
|
||||||
|
deleteSkinsButton.Enabled.Value = false;
|
||||||
|
Task.Run(() => skins.Delete(skins.GetAllUserSkins())).ContinueWith(t => Schedule(() => deleteSkinsButton.Enabled.Value = true));
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (collectionManager != null)
|
||||||
|
{
|
||||||
|
if (collectionManager.SupportsImportFromStable)
|
||||||
|
{
|
||||||
|
Add(importCollectionsButton = new SettingsButton
|
||||||
|
{
|
||||||
|
Text = "Import collections from stable",
|
||||||
|
Action = () =>
|
||||||
|
{
|
||||||
|
importCollectionsButton.Enabled.Value = false;
|
||||||
|
collectionManager.ImportFromStableAsync().ContinueWith(t => Schedule(() => importCollectionsButton.Enabled.Value = true));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Add(new DangerousSettingsButton
|
||||||
|
{
|
||||||
|
Text = "Delete ALL collections",
|
||||||
Action = () =>
|
Action = () =>
|
||||||
{
|
{
|
||||||
dialogOverlay?.Push(new DeleteAllBeatmapsDialog(() =>
|
dialogOverlay?.Push(new DeleteAllBeatmapsDialog(collectionManager.DeleteAll));
|
||||||
{
|
|
||||||
deleteSkinsButton.Enabled.Value = false;
|
|
||||||
Task.Run(() => skins.Delete(skins.GetAllUserSkins())).ContinueWith(t => Schedule(() => deleteSkinsButton.Enabled.Value = true));
|
|
||||||
}));
|
|
||||||
}
|
}
|
||||||
},
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
AddRange(new Drawable[]
|
||||||
|
{
|
||||||
restoreButton = new SettingsButton
|
restoreButton = new SettingsButton
|
||||||
{
|
{
|
||||||
Text = "Restore all hidden difficulties",
|
Text = "Restore all hidden difficulties",
|
||||||
|
@ -12,6 +12,7 @@ namespace osu.Game.Rulesets.Difficulty
|
|||||||
public Skill[] Skills;
|
public Skill[] Skills;
|
||||||
|
|
||||||
public double StarRating;
|
public double StarRating;
|
||||||
|
public int MaxCombo;
|
||||||
|
|
||||||
public DifficultyAttributes()
|
public DifficultyAttributes()
|
||||||
{
|
{
|
||||||
|
@ -6,6 +6,7 @@ using System.Collections.Generic;
|
|||||||
using System.Collections.Specialized;
|
using System.Collections.Specialized;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
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.Input;
|
using osu.Framework.Input;
|
||||||
@ -13,6 +14,7 @@ using osu.Framework.Input.Events;
|
|||||||
using osu.Framework.Logging;
|
using osu.Framework.Logging;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Beatmaps.ControlPoints;
|
using osu.Game.Beatmaps.ControlPoints;
|
||||||
|
using osu.Game.Overlays.Settings;
|
||||||
using osu.Game.Rulesets.Configuration;
|
using osu.Game.Rulesets.Configuration;
|
||||||
using osu.Game.Rulesets.Edit.Tools;
|
using osu.Game.Rulesets.Edit.Tools;
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
@ -92,9 +94,18 @@ namespace osu.Game.Rulesets.Edit
|
|||||||
Name = "Sidebar",
|
Name = "Sidebar",
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Padding = new MarginPadding { Right = 10 },
|
Padding = new MarginPadding { Right = 10 },
|
||||||
|
Spacing = new Vector2(10),
|
||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
new ToolboxGroup { Child = toolboxCollection = new RadioButtonCollection { RelativeSizeAxes = Axes.X } }
|
new ToolboxGroup("toolbox") { Child = toolboxCollection = new RadioButtonCollection { RelativeSizeAxes = Axes.X } },
|
||||||
|
new ToolboxGroup("toggles")
|
||||||
|
{
|
||||||
|
ChildrenEnumerable = Toggles.Select(b => new SettingsCheckbox
|
||||||
|
{
|
||||||
|
Bindable = b,
|
||||||
|
LabelText = b?.Description ?? "unknown"
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
new Container
|
new Container
|
||||||
@ -126,7 +137,7 @@ namespace osu.Game.Rulesets.Edit
|
|||||||
|
|
||||||
toolboxCollection.Items = CompositionTools
|
toolboxCollection.Items = CompositionTools
|
||||||
.Prepend(new SelectTool())
|
.Prepend(new SelectTool())
|
||||||
.Select(t => new RadioButton(t.Name, () => toolSelected(t)))
|
.Select(t => new RadioButton(t.Name, () => toolSelected(t), t.CreateIcon))
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
setSelectTool();
|
setSelectTool();
|
||||||
@ -156,6 +167,12 @@ namespace osu.Game.Rulesets.Edit
|
|||||||
/// </remarks>
|
/// </remarks>
|
||||||
protected abstract IReadOnlyList<HitObjectCompositionTool> CompositionTools { get; }
|
protected abstract IReadOnlyList<HitObjectCompositionTool> CompositionTools { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A collection of toggles which will be displayed to the user.
|
||||||
|
/// The display name will be decided by <see cref="Bindable{T}.Description"/>.
|
||||||
|
/// </summary>
|
||||||
|
protected virtual IEnumerable<BindableBool> Toggles => Enumerable.Empty<BindableBool>();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Construct a relevant blueprint container. This will manage hitobject selection/placement input handling and display logic.
|
/// Construct a relevant blueprint container. This will manage hitobject selection/placement input handling and display logic.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -8,9 +8,8 @@ namespace osu.Game.Rulesets.Edit
|
|||||||
{
|
{
|
||||||
public class ToolboxGroup : PlayerSettingsGroup
|
public class ToolboxGroup : PlayerSettingsGroup
|
||||||
{
|
{
|
||||||
protected override string Title => "toolbox";
|
public ToolboxGroup(string title)
|
||||||
|
: base(title)
|
||||||
public ToolboxGroup()
|
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.X;
|
RelativeSizeAxes = Axes.X;
|
||||||
Width = 1;
|
Width = 1;
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Edit.Tools
|
namespace osu.Game.Rulesets.Edit.Tools
|
||||||
{
|
{
|
||||||
public abstract class HitObjectCompositionTool
|
public abstract class HitObjectCompositionTool
|
||||||
@ -14,6 +16,8 @@ namespace osu.Game.Rulesets.Edit.Tools
|
|||||||
|
|
||||||
public abstract PlacementBlueprint CreatePlacementBlueprint();
|
public abstract PlacementBlueprint CreatePlacementBlueprint();
|
||||||
|
|
||||||
|
public virtual Drawable CreateIcon() => null;
|
||||||
|
|
||||||
public override string ToString() => Name;
|
public override string ToString() => Name;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +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 osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Sprites;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Edit.Tools
|
namespace osu.Game.Rulesets.Edit.Tools
|
||||||
{
|
{
|
||||||
public class SelectTool : HitObjectCompositionTool
|
public class SelectTool : HitObjectCompositionTool
|
||||||
@ -10,6 +13,8 @@ namespace osu.Game.Rulesets.Edit.Tools
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override Drawable CreateIcon() => new SpriteIcon { Icon = FontAwesome.Solid.MousePointer };
|
||||||
|
|
||||||
public override PlacementBlueprint CreatePlacementBlueprint() => null;
|
public override PlacementBlueprint CreatePlacementBlueprint() => null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -67,7 +67,7 @@ namespace osu.Game.Rulesets.Scoring
|
|||||||
private readonly double accuracyPortion;
|
private readonly double accuracyPortion;
|
||||||
private readonly double comboPortion;
|
private readonly double comboPortion;
|
||||||
|
|
||||||
private double maxHighestCombo;
|
private int maxHighestCombo;
|
||||||
private double maxBaseScore;
|
private double maxBaseScore;
|
||||||
private double rollingMaxBaseScore;
|
private double rollingMaxBaseScore;
|
||||||
private double baseScore;
|
private double baseScore;
|
||||||
@ -203,19 +203,36 @@ namespace osu.Game.Rulesets.Scoring
|
|||||||
}
|
}
|
||||||
|
|
||||||
private double getScore(ScoringMode mode)
|
private double getScore(ScoringMode mode)
|
||||||
|
{
|
||||||
|
return GetScore(mode, maxHighestCombo,
|
||||||
|
maxBaseScore > 0 ? baseScore / maxBaseScore : 0,
|
||||||
|
maxHighestCombo > 0 ? (double)HighestCombo.Value / maxHighestCombo : 0,
|
||||||
|
bonusScore);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Computes the total score.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="mode">The <see cref="ScoringMode"/> to compute the total score in.</param>
|
||||||
|
/// <param name="maxCombo">The maximum combo achievable in the beatmap.</param>
|
||||||
|
/// <param name="accuracyRatio">The accuracy percentage achieved by the player.</param>
|
||||||
|
/// <param name="comboRatio">The proportion of <paramref name="maxCombo"/> achieved by the player.</param>
|
||||||
|
/// <param name="bonusScore">Any bonus score to be added.</param>
|
||||||
|
/// <returns>The total score.</returns>
|
||||||
|
public double GetScore(ScoringMode mode, int maxCombo, double accuracyRatio, double comboRatio, double bonusScore)
|
||||||
{
|
{
|
||||||
switch (mode)
|
switch (mode)
|
||||||
{
|
{
|
||||||
default:
|
default:
|
||||||
case ScoringMode.Standardised:
|
case ScoringMode.Standardised:
|
||||||
double accuracyScore = accuracyPortion * baseScore / maxBaseScore;
|
double accuracyScore = accuracyPortion * accuracyRatio;
|
||||||
double comboScore = comboPortion * HighestCombo.Value / maxHighestCombo;
|
double comboScore = comboPortion * comboRatio;
|
||||||
|
|
||||||
return (max_score * (accuracyScore + comboScore) + bonusScore) * scoreMultiplier;
|
return (max_score * (accuracyScore + comboScore) + bonusScore) * scoreMultiplier;
|
||||||
|
|
||||||
case ScoringMode.Classic:
|
case ScoringMode.Classic:
|
||||||
// should emulate osu-stable's scoring as closely as we can (https://osu.ppy.sh/help/wiki/Score/ScoreV1)
|
// should emulate osu-stable's scoring as closely as we can (https://osu.ppy.sh/help/wiki/Score/ScoreV1)
|
||||||
return bonusScore + baseScore * (1 + Math.Max(0, HighestCombo.Value - 1) * scoreMultiplier / 25);
|
return bonusScore + (accuracyRatio * maxCombo * 300) * (1 + Math.Max(0, (comboRatio * maxCombo) - 1) * scoreMultiplier / 25);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6,15 +6,20 @@ using System.Collections.Generic;
|
|||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Linq.Expressions;
|
using System.Linq.Expressions;
|
||||||
|
using System.Threading;
|
||||||
|
using JetBrains.Annotations;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Logging;
|
using osu.Framework.Logging;
|
||||||
using osu.Framework.Platform;
|
using osu.Framework.Platform;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Configuration;
|
||||||
using osu.Game.Database;
|
using osu.Game.Database;
|
||||||
using osu.Game.IO.Archives;
|
using osu.Game.IO.Archives;
|
||||||
using osu.Game.Online.API;
|
using osu.Game.Online.API;
|
||||||
using osu.Game.Online.API.Requests;
|
using osu.Game.Online.API.Requests;
|
||||||
using osu.Game.Rulesets;
|
using osu.Game.Rulesets;
|
||||||
|
using osu.Game.Rulesets.Scoring;
|
||||||
using osu.Game.Scoring.Legacy;
|
using osu.Game.Scoring.Legacy;
|
||||||
|
|
||||||
namespace osu.Game.Scoring
|
namespace osu.Game.Scoring
|
||||||
@ -30,11 +35,20 @@ namespace osu.Game.Scoring
|
|||||||
private readonly RulesetStore rulesets;
|
private readonly RulesetStore rulesets;
|
||||||
private readonly Func<BeatmapManager> beatmaps;
|
private readonly Func<BeatmapManager> beatmaps;
|
||||||
|
|
||||||
public ScoreManager(RulesetStore rulesets, Func<BeatmapManager> beatmaps, Storage storage, IAPIProvider api, IDatabaseContextFactory contextFactory, IIpcHost importHost = null)
|
[CanBeNull]
|
||||||
|
private readonly Func<BeatmapDifficultyManager> difficulties;
|
||||||
|
|
||||||
|
[CanBeNull]
|
||||||
|
private readonly OsuConfigManager configManager;
|
||||||
|
|
||||||
|
public ScoreManager(RulesetStore rulesets, Func<BeatmapManager> beatmaps, Storage storage, IAPIProvider api, IDatabaseContextFactory contextFactory, IIpcHost importHost = null,
|
||||||
|
Func<BeatmapDifficultyManager> difficulties = null, OsuConfigManager configManager = null)
|
||||||
: base(storage, contextFactory, api, new ScoreStore(contextFactory, storage), importHost)
|
: base(storage, contextFactory, api, new ScoreStore(contextFactory, storage), importHost)
|
||||||
{
|
{
|
||||||
this.rulesets = rulesets;
|
this.rulesets = rulesets;
|
||||||
this.beatmaps = beatmaps;
|
this.beatmaps = beatmaps;
|
||||||
|
this.difficulties = difficulties;
|
||||||
|
this.configManager = configManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override ScoreInfo CreateModel(ArchiveReader archive)
|
protected override ScoreInfo CreateModel(ArchiveReader archive)
|
||||||
@ -72,5 +86,118 @@ namespace osu.Game.Scoring
|
|||||||
protected override bool CheckLocalAvailability(ScoreInfo model, IQueryable<ScoreInfo> items)
|
protected override bool CheckLocalAvailability(ScoreInfo model, IQueryable<ScoreInfo> items)
|
||||||
=> base.CheckLocalAvailability(model, items)
|
=> base.CheckLocalAvailability(model, items)
|
||||||
|| (model.OnlineScoreID != null && items.Any(i => i.OnlineScoreID == model.OnlineScoreID));
|
|| (model.OnlineScoreID != null && items.Any(i => i.OnlineScoreID == model.OnlineScoreID));
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Retrieves a bindable that represents the total score of a <see cref="ScoreInfo"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Responds to changes in the currently-selected <see cref="ScoringMode"/>.
|
||||||
|
/// </remarks>
|
||||||
|
/// <param name="score">The <see cref="ScoreInfo"/> to retrieve the bindable for.</param>
|
||||||
|
/// <returns>The bindable containing the total score.</returns>
|
||||||
|
public Bindable<long> GetBindableTotalScore(ScoreInfo score)
|
||||||
|
{
|
||||||
|
var bindable = new TotalScoreBindable(score, difficulties);
|
||||||
|
configManager?.BindWith(OsuSetting.ScoreDisplayMode, bindable.ScoringMode);
|
||||||
|
return bindable;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Retrieves a bindable that represents the formatted total score string of a <see cref="ScoreInfo"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Responds to changes in the currently-selected <see cref="ScoringMode"/>.
|
||||||
|
/// </remarks>
|
||||||
|
/// <param name="score">The <see cref="ScoreInfo"/> to retrieve the bindable for.</param>
|
||||||
|
/// <returns>The bindable containing the formatted total score string.</returns>
|
||||||
|
public Bindable<string> GetBindableTotalScoreString(ScoreInfo score) => new TotalScoreStringBindable(GetBindableTotalScore(score));
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Provides the total score of a <see cref="ScoreInfo"/>. Responds to changes in the currently-selected <see cref="ScoringMode"/>.
|
||||||
|
/// </summary>
|
||||||
|
private class TotalScoreBindable : Bindable<long>
|
||||||
|
{
|
||||||
|
public readonly Bindable<ScoringMode> ScoringMode = new Bindable<ScoringMode>();
|
||||||
|
|
||||||
|
private readonly ScoreInfo score;
|
||||||
|
private readonly Func<BeatmapDifficultyManager> difficulties;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new <see cref="TotalScoreBindable"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="score">The <see cref="ScoreInfo"/> to provide the total score of.</param>
|
||||||
|
/// <param name="difficulties">A function to retrieve the <see cref="BeatmapDifficultyManager"/>.</param>
|
||||||
|
public TotalScoreBindable(ScoreInfo score, Func<BeatmapDifficultyManager> difficulties)
|
||||||
|
{
|
||||||
|
this.score = score;
|
||||||
|
this.difficulties = difficulties;
|
||||||
|
|
||||||
|
ScoringMode.BindValueChanged(onScoringModeChanged, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private IBindable<StarDifficulty> difficultyBindable;
|
||||||
|
private CancellationTokenSource difficultyCancellationSource;
|
||||||
|
|
||||||
|
private void onScoringModeChanged(ValueChangedEvent<ScoringMode> mode)
|
||||||
|
{
|
||||||
|
difficultyCancellationSource?.Cancel();
|
||||||
|
difficultyCancellationSource = null;
|
||||||
|
|
||||||
|
if (score.Beatmap == null)
|
||||||
|
{
|
||||||
|
Value = score.TotalScore;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int? beatmapMaxCombo = score.Beatmap.MaxCombo;
|
||||||
|
|
||||||
|
if (beatmapMaxCombo == null)
|
||||||
|
{
|
||||||
|
if (score.Beatmap.ID == 0 || difficulties == null)
|
||||||
|
{
|
||||||
|
// We don't have enough information (max combo) to compute the score, so let's use the provided score.
|
||||||
|
Value = score.TotalScore;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We can compute the max combo locally after the async beatmap difficulty computation.
|
||||||
|
difficultyBindable = difficulties().GetBindableDifficulty(score.Beatmap, score.Ruleset, score.Mods, (difficultyCancellationSource = new CancellationTokenSource()).Token);
|
||||||
|
difficultyBindable.BindValueChanged(d => updateScore(d.NewValue.MaxCombo), true);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
updateScore(beatmapMaxCombo.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateScore(int beatmapMaxCombo)
|
||||||
|
{
|
||||||
|
if (beatmapMaxCombo == 0)
|
||||||
|
{
|
||||||
|
Value = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var ruleset = score.Ruleset.CreateInstance();
|
||||||
|
var scoreProcessor = ruleset.CreateScoreProcessor();
|
||||||
|
|
||||||
|
scoreProcessor.Mods.Value = score.Mods;
|
||||||
|
|
||||||
|
Value = (long)Math.Round(scoreProcessor.GetScore(ScoringMode.Value, beatmapMaxCombo, score.Accuracy, (double)score.MaxCombo / beatmapMaxCombo, 0));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Provides the total score of a <see cref="ScoreInfo"/> as a formatted string. Responds to changes in the currently-selected <see cref="ScoringMode"/>.
|
||||||
|
/// </summary>
|
||||||
|
private class TotalScoreStringBindable : Bindable<string>
|
||||||
|
{
|
||||||
|
// ReSharper disable once PrivateFieldCanBeConvertedToLocalVariable (need to hold a reference)
|
||||||
|
private readonly IBindable<long> totalScore;
|
||||||
|
|
||||||
|
public TotalScoreStringBindable(IBindable<long> totalScore)
|
||||||
|
{
|
||||||
|
this.totalScore = totalScore;
|
||||||
|
this.totalScore.BindValueChanged(v => Value = v.NewValue.ToString("N0"), true);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -32,8 +32,6 @@ namespace osu.Game.Screens.Edit.Components.Menus
|
|||||||
Height = 1,
|
Height = 1,
|
||||||
Colour = Color4.White.Opacity(0.2f),
|
Colour = Color4.White.Opacity(0.2f),
|
||||||
});
|
});
|
||||||
|
|
||||||
Current.Value = EditorScreenMode.Compose;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
|
@ -5,7 +5,6 @@ using System;
|
|||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Extensions.Color4Extensions;
|
using osu.Framework.Extensions.Color4Extensions;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
|
||||||
using osu.Framework.Graphics.Effects;
|
using osu.Framework.Graphics.Effects;
|
||||||
using osu.Framework.Graphics.Shapes;
|
using osu.Framework.Graphics.Shapes;
|
||||||
using osu.Framework.Graphics.Sprites;
|
using osu.Framework.Graphics.Sprites;
|
||||||
@ -29,7 +28,7 @@ namespace osu.Game.Screens.Edit.Components.RadioButtons
|
|||||||
private Color4 selectedBackgroundColour;
|
private Color4 selectedBackgroundColour;
|
||||||
private Color4 selectedBubbleColour;
|
private Color4 selectedBubbleColour;
|
||||||
|
|
||||||
private readonly Drawable bubble;
|
private Drawable icon;
|
||||||
private readonly RadioButton button;
|
private readonly RadioButton button;
|
||||||
|
|
||||||
public DrawableRadioButton(RadioButton button)
|
public DrawableRadioButton(RadioButton button)
|
||||||
@ -40,19 +39,6 @@ namespace osu.Game.Screens.Edit.Components.RadioButtons
|
|||||||
Action = button.Select;
|
Action = button.Select;
|
||||||
|
|
||||||
RelativeSizeAxes = Axes.X;
|
RelativeSizeAxes = Axes.X;
|
||||||
|
|
||||||
bubble = new CircularContainer
|
|
||||||
{
|
|
||||||
Anchor = Anchor.CentreLeft,
|
|
||||||
Origin = Anchor.CentreLeft,
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
FillMode = FillMode.Fit,
|
|
||||||
Scale = new Vector2(0.5f),
|
|
||||||
X = 10,
|
|
||||||
Masking = true,
|
|
||||||
Blending = BlendingParameters.Additive,
|
|
||||||
Child = new Box { RelativeSizeAxes = Axes.Both }
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
@ -73,7 +59,14 @@ namespace osu.Game.Screens.Edit.Components.RadioButtons
|
|||||||
Colour = Color4.Black.Opacity(0.5f)
|
Colour = Color4.Black.Opacity(0.5f)
|
||||||
};
|
};
|
||||||
|
|
||||||
Add(bubble);
|
Add(icon = (button.CreateIcon?.Invoke() ?? new Circle()).With(b =>
|
||||||
|
{
|
||||||
|
b.Blending = BlendingParameters.Additive;
|
||||||
|
b.Anchor = Anchor.CentreLeft;
|
||||||
|
b.Origin = Anchor.CentreLeft;
|
||||||
|
b.Size = new Vector2(20);
|
||||||
|
b.X = 10;
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void LoadComplete()
|
protected override void LoadComplete()
|
||||||
@ -96,7 +89,7 @@ namespace osu.Game.Screens.Edit.Components.RadioButtons
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
BackgroundColour = button.Selected.Value ? selectedBackgroundColour : defaultBackgroundColour;
|
BackgroundColour = button.Selected.Value ? selectedBackgroundColour : defaultBackgroundColour;
|
||||||
bubble.Colour = button.Selected.Value ? selectedBubbleColour : defaultBubbleColour;
|
icon.Colour = button.Selected.Value ? selectedBubbleColour : defaultBubbleColour;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override SpriteText CreateText() => new OsuSpriteText
|
protected override SpriteText CreateText() => new OsuSpriteText
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
|
||||||
namespace osu.Game.Screens.Edit.Components.RadioButtons
|
namespace osu.Game.Screens.Edit.Components.RadioButtons
|
||||||
{
|
{
|
||||||
@ -19,11 +20,17 @@ namespace osu.Game.Screens.Edit.Components.RadioButtons
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public object Item;
|
public object Item;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A function which creates a drawable icon to represent this item. If null, a sane default should be used.
|
||||||
|
/// </summary>
|
||||||
|
public readonly Func<Drawable> CreateIcon;
|
||||||
|
|
||||||
private readonly Action action;
|
private readonly Action action;
|
||||||
|
|
||||||
public RadioButton(object item, Action action)
|
public RadioButton(object item, Action action, Func<Drawable> createIcon = null)
|
||||||
{
|
{
|
||||||
Item = item;
|
Item = item;
|
||||||
|
CreateIcon = createIcon;
|
||||||
this.action = action;
|
this.action = action;
|
||||||
Selected = new BindableBool();
|
Selected = new BindableBool();
|
||||||
}
|
}
|
||||||
|
@ -61,7 +61,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
|||||||
|
|
||||||
AddRangeInternal(new[]
|
AddRangeInternal(new[]
|
||||||
{
|
{
|
||||||
DragBox = CreateDragBox(select),
|
DragBox = CreateDragBox(selectBlueprintsFromDragRectangle),
|
||||||
selectionHandler,
|
selectionHandler,
|
||||||
SelectionBlueprints = CreateSelectionBlueprintContainer(),
|
SelectionBlueprints = CreateSelectionBlueprintContainer(),
|
||||||
selectionHandler.CreateProxy(),
|
selectionHandler.CreateProxy(),
|
||||||
@ -326,7 +326,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
|||||||
/// Select all masks in a given rectangle selection area.
|
/// Select all masks in a given rectangle selection area.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <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 selectBlueprintsFromDragRectangle(RectangleF rect)
|
||||||
{
|
{
|
||||||
foreach (var blueprint in SelectionBlueprints)
|
foreach (var blueprint in SelectionBlueprints)
|
||||||
{
|
{
|
||||||
|
@ -2,38 +2,40 @@
|
|||||||
// 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 osuTK.Graphics;
|
using System.Collections.Generic;
|
||||||
using osu.Framework.Screens;
|
using osu.Framework;
|
||||||
|
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.Shapes;
|
using osu.Framework.Graphics.Shapes;
|
||||||
using osu.Game.Graphics;
|
|
||||||
using osu.Game.Screens.Edit.Components.Timelines.Summary;
|
|
||||||
using osu.Framework.Allocation;
|
|
||||||
using osu.Framework.Bindables;
|
|
||||||
using osu.Framework.Graphics.UserInterface;
|
using osu.Framework.Graphics.UserInterface;
|
||||||
using osu.Framework.Input.Events;
|
|
||||||
using osu.Framework.Platform;
|
|
||||||
using osu.Framework.Timing;
|
|
||||||
using osu.Game.Graphics.UserInterface;
|
|
||||||
using osu.Game.Screens.Edit.Components;
|
|
||||||
using osu.Game.Screens.Edit.Components.Menus;
|
|
||||||
using osu.Game.Screens.Edit.Design;
|
|
||||||
using osuTK.Input;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using osu.Framework;
|
|
||||||
using osu.Framework.Input;
|
using osu.Framework.Input;
|
||||||
using osu.Framework.Input.Bindings;
|
using osu.Framework.Input.Bindings;
|
||||||
|
using osu.Framework.Input.Events;
|
||||||
using osu.Framework.Logging;
|
using osu.Framework.Logging;
|
||||||
|
using osu.Framework.Platform;
|
||||||
|
using osu.Framework.Screens;
|
||||||
|
using osu.Framework.Timing;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Graphics.Cursor;
|
using osu.Game.Graphics.Cursor;
|
||||||
|
using osu.Game.Graphics.UserInterface;
|
||||||
using osu.Game.Input.Bindings;
|
using osu.Game.Input.Bindings;
|
||||||
|
using osu.Game.Online.API;
|
||||||
|
using osu.Game.Overlays;
|
||||||
using osu.Game.Rulesets.Edit;
|
using osu.Game.Rulesets.Edit;
|
||||||
|
using osu.Game.Screens.Edit.Components;
|
||||||
|
using osu.Game.Screens.Edit.Components.Menus;
|
||||||
|
using osu.Game.Screens.Edit.Components.Timelines.Summary;
|
||||||
using osu.Game.Screens.Edit.Compose;
|
using osu.Game.Screens.Edit.Compose;
|
||||||
|
using osu.Game.Screens.Edit.Design;
|
||||||
using osu.Game.Screens.Edit.Setup;
|
using osu.Game.Screens.Edit.Setup;
|
||||||
using osu.Game.Screens.Edit.Timing;
|
using osu.Game.Screens.Edit.Timing;
|
||||||
using osu.Game.Screens.Play;
|
using osu.Game.Screens.Play;
|
||||||
using osu.Game.Users;
|
using osu.Game.Users;
|
||||||
|
using osuTK.Graphics;
|
||||||
|
using osuTK.Input;
|
||||||
|
|
||||||
namespace osu.Game.Screens.Edit
|
namespace osu.Game.Screens.Edit
|
||||||
{
|
{
|
||||||
@ -50,9 +52,18 @@ namespace osu.Game.Screens.Edit
|
|||||||
|
|
||||||
public override bool AllowRateAdjustments => false;
|
public override bool AllowRateAdjustments => false;
|
||||||
|
|
||||||
|
protected bool HasUnsavedChanges => lastSavedHash != changeHandler.CurrentStateHash;
|
||||||
|
|
||||||
[Resolved]
|
[Resolved]
|
||||||
private BeatmapManager beatmapManager { get; set; }
|
private BeatmapManager beatmapManager { get; set; }
|
||||||
|
|
||||||
|
[Resolved(canBeNull: true)]
|
||||||
|
private DialogOverlay dialogOverlay { get; set; }
|
||||||
|
|
||||||
|
private bool exitConfirmed;
|
||||||
|
|
||||||
|
private string lastSavedHash;
|
||||||
|
|
||||||
private Box bottomBackground;
|
private Box bottomBackground;
|
||||||
private Container screenContainer;
|
private Container screenContainer;
|
||||||
|
|
||||||
@ -72,6 +83,9 @@ namespace osu.Game.Screens.Edit
|
|||||||
protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent)
|
protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent)
|
||||||
=> dependencies = new DependencyContainer(base.CreateChildDependencies(parent));
|
=> dependencies = new DependencyContainer(base.CreateChildDependencies(parent));
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private IAPIProvider api { get; set; }
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(OsuColour colours, GameHost host)
|
private void load(OsuColour colours, GameHost host)
|
||||||
{
|
{
|
||||||
@ -89,6 +103,14 @@ namespace osu.Game.Screens.Edit
|
|||||||
// todo: remove caching of this and consume via editorBeatmap?
|
// todo: remove caching of this and consume via editorBeatmap?
|
||||||
dependencies.Cache(beatDivisor);
|
dependencies.Cache(beatDivisor);
|
||||||
|
|
||||||
|
bool isNewBeatmap = false;
|
||||||
|
|
||||||
|
if (Beatmap.Value is DummyWorkingBeatmap)
|
||||||
|
{
|
||||||
|
isNewBeatmap = true;
|
||||||
|
Beatmap.Value = beatmapManager.CreateNew(Ruleset.Value, api.LocalUser.Value);
|
||||||
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
playableBeatmap = Beatmap.Value.GetPlayableBeatmap(Beatmap.Value.BeatmapInfo.Ruleset);
|
playableBeatmap = Beatmap.Value.GetPlayableBeatmap(Beatmap.Value.BeatmapInfo.Ruleset);
|
||||||
@ -106,13 +128,15 @@ namespace osu.Game.Screens.Edit
|
|||||||
changeHandler = new EditorChangeHandler(editorBeatmap);
|
changeHandler = new EditorChangeHandler(editorBeatmap);
|
||||||
dependencies.CacheAs<IEditorChangeHandler>(changeHandler);
|
dependencies.CacheAs<IEditorChangeHandler>(changeHandler);
|
||||||
|
|
||||||
|
updateLastSavedHash();
|
||||||
|
|
||||||
EditorMenuBar menuBar;
|
EditorMenuBar menuBar;
|
||||||
OsuMenuItem undoMenuItem;
|
OsuMenuItem undoMenuItem;
|
||||||
OsuMenuItem redoMenuItem;
|
OsuMenuItem redoMenuItem;
|
||||||
|
|
||||||
var fileMenuItems = new List<MenuItem>
|
var fileMenuItems = new List<MenuItem>
|
||||||
{
|
{
|
||||||
new EditorMenuItem("Save", MenuItemType.Standard, saveBeatmap)
|
new EditorMenuItem("Save", MenuItemType.Standard, Save)
|
||||||
};
|
};
|
||||||
|
|
||||||
if (RuntimeInfo.IsDesktop)
|
if (RuntimeInfo.IsDesktop)
|
||||||
@ -147,6 +171,7 @@ namespace osu.Game.Screens.Edit
|
|||||||
Anchor = Anchor.CentreLeft,
|
Anchor = Anchor.CentreLeft,
|
||||||
Origin = Anchor.CentreLeft,
|
Origin = Anchor.CentreLeft,
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Mode = { Value = isNewBeatmap ? EditorScreenMode.SongSetup : EditorScreenMode.Compose },
|
||||||
Items = new[]
|
Items = new[]
|
||||||
{
|
{
|
||||||
new MenuItem("File")
|
new MenuItem("File")
|
||||||
@ -224,6 +249,17 @@ namespace osu.Game.Screens.Edit
|
|||||||
bottomBackground.Colour = colours.Gray2;
|
bottomBackground.Colour = colours.Gray2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected void Save()
|
||||||
|
{
|
||||||
|
// apply any set-level metadata changes.
|
||||||
|
beatmapManager.Update(playableBeatmap.BeatmapInfo.BeatmapSet);
|
||||||
|
|
||||||
|
// save the loaded beatmap's data stream.
|
||||||
|
beatmapManager.Save(playableBeatmap.BeatmapInfo, editorBeatmap, editorBeatmap.BeatmapSkin);
|
||||||
|
|
||||||
|
updateLastSavedHash();
|
||||||
|
}
|
||||||
|
|
||||||
protected override void Update()
|
protected override void Update()
|
||||||
{
|
{
|
||||||
base.Update();
|
base.Update();
|
||||||
@ -243,7 +279,7 @@ namespace osu.Game.Screens.Edit
|
|||||||
return true;
|
return true;
|
||||||
|
|
||||||
case PlatformActionType.Save:
|
case PlatformActionType.Save:
|
||||||
saveBeatmap();
|
Save();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -333,12 +369,31 @@ namespace osu.Game.Screens.Edit
|
|||||||
|
|
||||||
public override bool OnExiting(IScreen next)
|
public override bool OnExiting(IScreen next)
|
||||||
{
|
{
|
||||||
|
if (!exitConfirmed && dialogOverlay != null && HasUnsavedChanges)
|
||||||
|
{
|
||||||
|
dialogOverlay?.Push(new PromptForSaveDialog(confirmExit, confirmExitWithSave));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
Background.FadeColour(Color4.White, 500);
|
Background.FadeColour(Color4.White, 500);
|
||||||
resetTrack();
|
resetTrack();
|
||||||
|
|
||||||
return base.OnExiting(next);
|
return base.OnExiting(next);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void confirmExitWithSave()
|
||||||
|
{
|
||||||
|
exitConfirmed = true;
|
||||||
|
Save();
|
||||||
|
this.Exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void confirmExit()
|
||||||
|
{
|
||||||
|
exitConfirmed = true;
|
||||||
|
this.Exit();
|
||||||
|
}
|
||||||
|
|
||||||
protected void Undo() => changeHandler.RestoreState(-1);
|
protected void Undo() => changeHandler.RestoreState(-1);
|
||||||
|
|
||||||
protected void Redo() => changeHandler.RestoreState(1);
|
protected void Redo() => changeHandler.RestoreState(1);
|
||||||
@ -385,7 +440,11 @@ namespace osu.Game.Screens.Edit
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
LoadComponentAsync(currentScreen, screenContainer.Add);
|
LoadComponentAsync(currentScreen, newScreen =>
|
||||||
|
{
|
||||||
|
if (newScreen == currentScreen)
|
||||||
|
screenContainer.Add(newScreen);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void seek(UIEvent e, int direction)
|
private void seek(UIEvent e, int direction)
|
||||||
@ -398,14 +457,17 @@ namespace osu.Game.Screens.Edit
|
|||||||
clock.SeekForward(!clock.IsRunning, amount);
|
clock.SeekForward(!clock.IsRunning, amount);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void saveBeatmap() => beatmapManager.Save(playableBeatmap.BeatmapInfo, editorBeatmap, editorBeatmap.BeatmapSkin);
|
|
||||||
|
|
||||||
private void exportBeatmap()
|
private void exportBeatmap()
|
||||||
{
|
{
|
||||||
saveBeatmap();
|
Save();
|
||||||
beatmapManager.Export(Beatmap.Value.BeatmapSetInfo);
|
beatmapManager.Export(Beatmap.Value.BeatmapSetInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void updateLastSavedHash()
|
||||||
|
{
|
||||||
|
lastSavedHash = changeHandler.CurrentStateHash;
|
||||||
|
}
|
||||||
|
|
||||||
public double SnapTime(double time, double? referenceTime) => editorBeatmap.SnapTime(time, referenceTime);
|
public double SnapTime(double time, double? referenceTime) => editorBeatmap.SnapTime(time, referenceTime);
|
||||||
|
|
||||||
public double GetBeatLengthAtTime(double referenceTime) => editorBeatmap.GetBeatLengthAtTime(referenceTime);
|
public double GetBeatLengthAtTime(double referenceTime) => editorBeatmap.GetBeatLengthAtTime(referenceTime);
|
||||||
|
@ -6,6 +6,7 @@ using System.Collections.Generic;
|
|||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Extensions;
|
||||||
using osu.Game.Beatmaps.Formats;
|
using osu.Game.Beatmaps.Formats;
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
|
|
||||||
@ -24,6 +25,18 @@ namespace osu.Game.Screens.Edit
|
|||||||
|
|
||||||
private int currentState = -1;
|
private int currentState = -1;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A SHA-2 hash representing the current visible editor state.
|
||||||
|
/// </summary>
|
||||||
|
public string CurrentStateHash
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
using (var stream = new MemoryStream(savedStates[currentState]))
|
||||||
|
return stream.ComputeSHA2Hash();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private readonly EditorBeatmap editorBeatmap;
|
private readonly EditorBeatmap editorBeatmap;
|
||||||
private int bulkChangesStarted;
|
private int bulkChangesStarted;
|
||||||
private bool isRestoring;
|
private bool isRestoring;
|
||||||
|
@ -43,7 +43,7 @@ namespace osu.Game.Screens.Edit
|
|||||||
|
|
||||||
public void Exit()
|
public void Exit()
|
||||||
{
|
{
|
||||||
this.FadeOut(250).Expire();
|
Expire();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
37
osu.Game/Screens/Edit/PromptForSaveDialog.cs
Normal file
37
osu.Game/Screens/Edit/PromptForSaveDialog.cs
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
// 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 osu.Framework.Graphics.Sprites;
|
||||||
|
using osu.Game.Overlays.Dialog;
|
||||||
|
|
||||||
|
namespace osu.Game.Screens.Edit
|
||||||
|
{
|
||||||
|
public class PromptForSaveDialog : PopupDialog
|
||||||
|
{
|
||||||
|
public PromptForSaveDialog(Action exit, Action saveAndExit)
|
||||||
|
{
|
||||||
|
HeaderText = "Did you want to save your changes?";
|
||||||
|
|
||||||
|
Icon = FontAwesome.Regular.Save;
|
||||||
|
|
||||||
|
Buttons = new PopupDialogButton[]
|
||||||
|
{
|
||||||
|
new PopupDialogCancelButton
|
||||||
|
{
|
||||||
|
Text = @"Save my masterpiece!",
|
||||||
|
Action = saveAndExit
|
||||||
|
},
|
||||||
|
new PopupDialogOkButton
|
||||||
|
{
|
||||||
|
Text = @"Forget all changes",
|
||||||
|
Action = exit
|
||||||
|
},
|
||||||
|
new PopupDialogCancelButton
|
||||||
|
{
|
||||||
|
Text = @"Oops, continue editing",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,13 +1,123 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System.Linq;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Shapes;
|
||||||
|
using osu.Framework.Graphics.UserInterface;
|
||||||
|
using osu.Game.Beatmaps.Drawables;
|
||||||
|
using osu.Game.Graphics;
|
||||||
|
using osu.Game.Graphics.Containers;
|
||||||
|
using osu.Game.Graphics.Sprites;
|
||||||
|
using osu.Game.Graphics.UserInterfaceV2;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
namespace osu.Game.Screens.Edit.Setup
|
namespace osu.Game.Screens.Edit.Setup
|
||||||
{
|
{
|
||||||
public class SetupScreen : EditorScreen
|
public class SetupScreen : EditorScreen
|
||||||
{
|
{
|
||||||
public SetupScreen()
|
private FillFlowContainer flow;
|
||||||
|
private LabelledTextBox artistTextBox;
|
||||||
|
private LabelledTextBox titleTextBox;
|
||||||
|
private LabelledTextBox creatorTextBox;
|
||||||
|
private LabelledTextBox difficultyTextBox;
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(OsuColour colours)
|
||||||
{
|
{
|
||||||
Child = new ScreenWhiteBox.UnderConstructionMessage("Setup mode");
|
Child = new Container
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Padding = new MarginPadding(50),
|
||||||
|
Child = new Container
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Masking = true,
|
||||||
|
CornerRadius = 10,
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
new Box
|
||||||
|
{
|
||||||
|
Colour = colours.GreySeafoamDark,
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
},
|
||||||
|
new OsuScrollContainer
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Padding = new MarginPadding(10),
|
||||||
|
Child = flow = new FillFlowContainer
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
AutoSizeAxes = Axes.Y,
|
||||||
|
Spacing = new Vector2(20),
|
||||||
|
Direction = FillDirection.Vertical,
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
new Container
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
Height = 250,
|
||||||
|
Masking = true,
|
||||||
|
CornerRadius = 10,
|
||||||
|
Child = new BeatmapBackgroundSprite(Beatmap.Value)
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
FillMode = FillMode.Fill,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
new OsuSpriteText
|
||||||
|
{
|
||||||
|
Text = "Beatmap metadata"
|
||||||
|
},
|
||||||
|
artistTextBox = new LabelledTextBox
|
||||||
|
{
|
||||||
|
Label = "Artist",
|
||||||
|
Current = { Value = Beatmap.Value.Metadata.Artist },
|
||||||
|
TabbableContentContainer = this
|
||||||
|
},
|
||||||
|
titleTextBox = new LabelledTextBox
|
||||||
|
{
|
||||||
|
Label = "Title",
|
||||||
|
Current = { Value = Beatmap.Value.Metadata.Title },
|
||||||
|
TabbableContentContainer = this
|
||||||
|
},
|
||||||
|
creatorTextBox = new LabelledTextBox
|
||||||
|
{
|
||||||
|
Label = "Creator",
|
||||||
|
Current = { Value = Beatmap.Value.Metadata.AuthorString },
|
||||||
|
TabbableContentContainer = this
|
||||||
|
},
|
||||||
|
difficultyTextBox = new LabelledTextBox
|
||||||
|
{
|
||||||
|
Label = "Difficulty Name",
|
||||||
|
Current = { Value = Beatmap.Value.BeatmapInfo.Version },
|
||||||
|
TabbableContentContainer = this
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
foreach (var item in flow.OfType<LabelledTextBox>())
|
||||||
|
item.OnCommit += onCommit;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onCommit(TextBox sender, bool newText)
|
||||||
|
{
|
||||||
|
if (!newText) return;
|
||||||
|
|
||||||
|
// for now, update these on commit rather than making BeatmapMetadata bindables.
|
||||||
|
// after switching database engines we can reconsider if switching to bindables is a good direction.
|
||||||
|
Beatmap.Value.Metadata.Artist = artistTextBox.Current.Value;
|
||||||
|
Beatmap.Value.Metadata.Title = titleTextBox.Current.Value;
|
||||||
|
Beatmap.Value.Metadata.AuthorString = creatorTextBox.Current.Value;
|
||||||
|
Beatmap.Value.BeatmapInfo.Version = difficultyTextBox.Current.Value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -98,7 +98,11 @@ namespace osu.Game.Screens.Menu
|
|||||||
{
|
{
|
||||||
buttons = new ButtonSystem
|
buttons = new ButtonSystem
|
||||||
{
|
{
|
||||||
OnEdit = delegate { this.Push(new Editor()); },
|
OnEdit = delegate
|
||||||
|
{
|
||||||
|
Beatmap.SetDefault();
|
||||||
|
this.Push(new Editor());
|
||||||
|
},
|
||||||
OnSolo = onSolo,
|
OnSolo = onSolo,
|
||||||
OnMulti = delegate { this.Push(new Multiplayer()); },
|
OnMulti = delegate { this.Push(new Multiplayer()); },
|
||||||
OnExit = confirmAndExit,
|
OnExit = confirmAndExit,
|
||||||
|
@ -37,8 +37,6 @@ namespace osu.Game.Screens.Multi
|
|||||||
|
|
||||||
public readonly Bindable<PlaylistItem> SelectedItem = new Bindable<PlaylistItem>();
|
public readonly Bindable<PlaylistItem> SelectedItem = new Bindable<PlaylistItem>();
|
||||||
|
|
||||||
protected override bool ShowDragHandle => allowEdit;
|
|
||||||
|
|
||||||
private Container maskingContainer;
|
private Container maskingContainer;
|
||||||
private Container difficultyIconContainer;
|
private Container difficultyIconContainer;
|
||||||
private LinkFlowContainer beatmapText;
|
private LinkFlowContainer beatmapText;
|
||||||
@ -63,12 +61,13 @@ namespace osu.Game.Screens.Multi
|
|||||||
|
|
||||||
// TODO: edit support should be moved out into a derived class
|
// TODO: edit support should be moved out into a derived class
|
||||||
this.allowEdit = allowEdit;
|
this.allowEdit = allowEdit;
|
||||||
|
|
||||||
this.allowSelection = allowSelection;
|
this.allowSelection = allowSelection;
|
||||||
|
|
||||||
beatmap.BindTo(item.Beatmap);
|
beatmap.BindTo(item.Beatmap);
|
||||||
ruleset.BindTo(item.Ruleset);
|
ruleset.BindTo(item.Ruleset);
|
||||||
requiredMods.BindTo(item.RequiredMods);
|
requiredMods.BindTo(item.RequiredMods);
|
||||||
|
|
||||||
|
ShowDragHandle.Value = allowEdit;
|
||||||
}
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
|
@ -10,7 +10,10 @@ namespace osu.Game.Screens.Play.PlayerSettings
|
|||||||
{
|
{
|
||||||
public class CollectionSettings : PlayerSettingsGroup
|
public class CollectionSettings : PlayerSettingsGroup
|
||||||
{
|
{
|
||||||
protected override string Title => @"collections";
|
public CollectionSettings()
|
||||||
|
: base("collections")
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load()
|
private void load()
|
||||||
|
@ -10,7 +10,10 @@ namespace osu.Game.Screens.Play.PlayerSettings
|
|||||||
{
|
{
|
||||||
public class DiscussionSettings : PlayerSettingsGroup
|
public class DiscussionSettings : PlayerSettingsGroup
|
||||||
{
|
{
|
||||||
protected override string Title => @"discussions";
|
public DiscussionSettings()
|
||||||
|
: base("discussions")
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(OsuConfigManager config)
|
private void load(OsuConfigManager config)
|
||||||
|
@ -9,11 +9,10 @@ namespace osu.Game.Screens.Play.PlayerSettings
|
|||||||
{
|
{
|
||||||
public class InputSettings : PlayerSettingsGroup
|
public class InputSettings : PlayerSettingsGroup
|
||||||
{
|
{
|
||||||
protected override string Title => "Input settings";
|
|
||||||
|
|
||||||
private readonly PlayerCheckbox mouseButtonsCheckbox;
|
private readonly PlayerCheckbox mouseButtonsCheckbox;
|
||||||
|
|
||||||
public InputSettings()
|
public InputSettings()
|
||||||
|
: base("Input Settings")
|
||||||
{
|
{
|
||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
|
@ -13,8 +13,6 @@ namespace osu.Game.Screens.Play.PlayerSettings
|
|||||||
{
|
{
|
||||||
private const int padding = 10;
|
private const int padding = 10;
|
||||||
|
|
||||||
protected override string Title => @"playback";
|
|
||||||
|
|
||||||
public readonly Bindable<double> UserPlaybackRate = new BindableDouble(1)
|
public readonly Bindable<double> UserPlaybackRate = new BindableDouble(1)
|
||||||
{
|
{
|
||||||
Default = 1,
|
Default = 1,
|
||||||
@ -28,6 +26,7 @@ namespace osu.Game.Screens.Play.PlayerSettings
|
|||||||
private readonly OsuSpriteText multiplierText;
|
private readonly OsuSpriteText multiplierText;
|
||||||
|
|
||||||
public PlaybackSettings()
|
public PlaybackSettings()
|
||||||
|
: base("playback")
|
||||||
{
|
{
|
||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
|
@ -17,11 +17,6 @@ namespace osu.Game.Screens.Play.PlayerSettings
|
|||||||
{
|
{
|
||||||
public abstract class PlayerSettingsGroup : Container
|
public abstract class PlayerSettingsGroup : Container
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// The title to be displayed in the header of this group.
|
|
||||||
/// </summary>
|
|
||||||
protected abstract string Title { get; }
|
|
||||||
|
|
||||||
private const float transition_duration = 250;
|
private const float transition_duration = 250;
|
||||||
private const int container_width = 270;
|
private const int container_width = 270;
|
||||||
private const int border_thickness = 2;
|
private const int border_thickness = 2;
|
||||||
@ -58,7 +53,11 @@ namespace osu.Game.Screens.Play.PlayerSettings
|
|||||||
|
|
||||||
private Color4 expandedColour;
|
private Color4 expandedColour;
|
||||||
|
|
||||||
protected PlayerSettingsGroup()
|
/// <summary>
|
||||||
|
/// Create a new instance.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="title">The title to be displayed in the header of this group.</param>
|
||||||
|
protected PlayerSettingsGroup(string title)
|
||||||
{
|
{
|
||||||
AutoSizeAxes = Axes.Y;
|
AutoSizeAxes = Axes.Y;
|
||||||
Width = container_width;
|
Width = container_width;
|
||||||
@ -95,7 +94,7 @@ namespace osu.Game.Screens.Play.PlayerSettings
|
|||||||
{
|
{
|
||||||
Origin = Anchor.CentreLeft,
|
Origin = Anchor.CentreLeft,
|
||||||
Anchor = Anchor.CentreLeft,
|
Anchor = Anchor.CentreLeft,
|
||||||
Text = Title.ToUpperInvariant(),
|
Text = title.ToUpperInvariant(),
|
||||||
Font = OsuFont.GetFont(weight: FontWeight.Bold, size: 17),
|
Font = OsuFont.GetFont(weight: FontWeight.Bold, size: 17),
|
||||||
Margin = new MarginPadding { Left = 10 },
|
Margin = new MarginPadding { Left = 10 },
|
||||||
},
|
},
|
||||||
|
@ -10,8 +10,6 @@ namespace osu.Game.Screens.Play.PlayerSettings
|
|||||||
{
|
{
|
||||||
public class VisualSettings : PlayerSettingsGroup
|
public class VisualSettings : PlayerSettingsGroup
|
||||||
{
|
{
|
||||||
protected override string Title => "Visual settings";
|
|
||||||
|
|
||||||
private readonly PlayerSliderBar<double> dimSliderBar;
|
private readonly PlayerSliderBar<double> dimSliderBar;
|
||||||
private readonly PlayerSliderBar<double> blurSliderBar;
|
private readonly PlayerSliderBar<double> blurSliderBar;
|
||||||
private readonly PlayerCheckbox showStoryboardToggle;
|
private readonly PlayerCheckbox showStoryboardToggle;
|
||||||
@ -19,6 +17,7 @@ namespace osu.Game.Screens.Play.PlayerSettings
|
|||||||
private readonly PlayerCheckbox beatmapHitsoundsToggle;
|
private readonly PlayerCheckbox beatmapHitsoundsToggle;
|
||||||
|
|
||||||
public VisualSettings()
|
public VisualSettings()
|
||||||
|
: base("Visual Settings")
|
||||||
{
|
{
|
||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
|
@ -30,6 +30,9 @@ namespace osu.Game.Screens.Ranking.Contracted
|
|||||||
{
|
{
|
||||||
private readonly ScoreInfo score;
|
private readonly ScoreInfo score;
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private ScoreManager scoreManager { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates a new <see cref="ContractedPanelMiddleContent"/>.
|
/// Creates a new <see cref="ContractedPanelMiddleContent"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -160,7 +163,7 @@ namespace osu.Game.Screens.Ranking.Contracted
|
|||||||
{
|
{
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
Text = score.TotalScore.ToString("N0"),
|
Current = scoreManager.GetBindableTotalScoreString(score),
|
||||||
Font = OsuFont.GetFont(size: 20, weight: FontWeight.Medium, fixedWidth: true),
|
Font = OsuFont.GetFont(size: 20, weight: FontWeight.Medium, fixedWidth: true),
|
||||||
Spacing = new Vector2(-1, 0)
|
Spacing = new Vector2(-1, 0)
|
||||||
},
|
},
|
||||||
|
@ -25,15 +25,16 @@ namespace osu.Game.Screens.Ranking.Expanded
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public class ExpandedPanelMiddleContent : CompositeDrawable
|
public class ExpandedPanelMiddleContent : CompositeDrawable
|
||||||
{
|
{
|
||||||
private readonly ScoreInfo score;
|
private const float padding = 10;
|
||||||
|
|
||||||
|
private readonly ScoreInfo score;
|
||||||
private readonly List<StatisticDisplay> statisticDisplays = new List<StatisticDisplay>();
|
private readonly List<StatisticDisplay> statisticDisplays = new List<StatisticDisplay>();
|
||||||
|
|
||||||
private FillFlowContainer starAndModDisplay;
|
private FillFlowContainer starAndModDisplay;
|
||||||
|
|
||||||
private RollingCounter<long> scoreCounter;
|
private RollingCounter<long> scoreCounter;
|
||||||
|
|
||||||
private const float padding = 10;
|
[Resolved]
|
||||||
|
private ScoreManager scoreManager { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates a new <see cref="ExpandedPanelMiddleContent"/>.
|
/// Creates a new <see cref="ExpandedPanelMiddleContent"/>.
|
||||||
@ -238,7 +239,7 @@ namespace osu.Game.Screens.Ranking.Expanded
|
|||||||
using (BeginDelayedSequence(AccuracyCircle.ACCURACY_TRANSFORM_DELAY, true))
|
using (BeginDelayedSequence(AccuracyCircle.ACCURACY_TRANSFORM_DELAY, true))
|
||||||
{
|
{
|
||||||
scoreCounter.FadeIn();
|
scoreCounter.FadeIn();
|
||||||
scoreCounter.Current.Value = score.TotalScore;
|
scoreCounter.Current = scoreManager.GetBindableTotalScore(score);
|
||||||
|
|
||||||
double delay = 0;
|
double delay = 0;
|
||||||
|
|
||||||
|
@ -60,6 +60,9 @@ namespace osu.Game.Screens.Select.Carousel
|
|||||||
match &= terms.Any(term => term.IndexOf(criteriaTerm, StringComparison.InvariantCultureIgnoreCase) >= 0);
|
match &= terms.Any(term => term.IndexOf(criteriaTerm, StringComparison.InvariantCultureIgnoreCase) >= 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (match)
|
||||||
|
match &= criteria.Collection?.Beatmaps.Contains(Beatmap) ?? true;
|
||||||
|
|
||||||
Filtered.Value = !match;
|
Filtered.Value = !match;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
@ -17,6 +18,7 @@ using osu.Framework.Graphics.UserInterface;
|
|||||||
using osu.Framework.Input.Events;
|
using osu.Framework.Input.Events;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Beatmaps.Drawables;
|
using osu.Game.Beatmaps.Drawables;
|
||||||
|
using osu.Game.Collections;
|
||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Graphics.Backgrounds;
|
using osu.Game.Graphics.Backgrounds;
|
||||||
using osu.Game.Graphics.Sprites;
|
using osu.Game.Graphics.Sprites;
|
||||||
@ -46,6 +48,12 @@ namespace osu.Game.Screens.Select.Carousel
|
|||||||
[Resolved]
|
[Resolved]
|
||||||
private BeatmapDifficultyManager difficultyManager { get; set; }
|
private BeatmapDifficultyManager difficultyManager { get; set; }
|
||||||
|
|
||||||
|
[Resolved(CanBeNull = true)]
|
||||||
|
private CollectionManager collectionManager { get; set; }
|
||||||
|
|
||||||
|
[Resolved(CanBeNull = true)]
|
||||||
|
private ManageCollectionsDialog manageCollectionsDialog { get; set; }
|
||||||
|
|
||||||
private IBindable<StarDifficulty> starDifficultyBindable;
|
private IBindable<StarDifficulty> starDifficultyBindable;
|
||||||
private CancellationTokenSource starDifficultyCancellationSource;
|
private CancellationTokenSource starDifficultyCancellationSource;
|
||||||
|
|
||||||
@ -213,16 +221,39 @@ namespace osu.Game.Screens.Select.Carousel
|
|||||||
if (editRequested != null)
|
if (editRequested != null)
|
||||||
items.Add(new OsuMenuItem("Edit", MenuItemType.Standard, () => editRequested(beatmap)));
|
items.Add(new OsuMenuItem("Edit", MenuItemType.Standard, () => editRequested(beatmap)));
|
||||||
|
|
||||||
|
if (beatmap.OnlineBeatmapID.HasValue && beatmapOverlay != null)
|
||||||
|
items.Add(new OsuMenuItem("Details...", MenuItemType.Standard, () => beatmapOverlay.FetchAndShowBeatmap(beatmap.OnlineBeatmapID.Value)));
|
||||||
|
|
||||||
|
if (collectionManager != null)
|
||||||
|
{
|
||||||
|
var collectionItems = collectionManager.Collections.Select(createCollectionMenuItem).ToList();
|
||||||
|
if (manageCollectionsDialog != null)
|
||||||
|
collectionItems.Add(new OsuMenuItem("Manage...", MenuItemType.Standard, manageCollectionsDialog.Show));
|
||||||
|
|
||||||
|
items.Add(new OsuMenuItem("Collections") { Items = collectionItems });
|
||||||
|
}
|
||||||
|
|
||||||
if (hideRequested != null)
|
if (hideRequested != null)
|
||||||
items.Add(new OsuMenuItem("Hide", MenuItemType.Destructive, () => hideRequested(beatmap)));
|
items.Add(new OsuMenuItem("Hide", MenuItemType.Destructive, () => hideRequested(beatmap)));
|
||||||
|
|
||||||
if (beatmap.OnlineBeatmapID.HasValue && beatmapOverlay != null)
|
|
||||||
items.Add(new OsuMenuItem("Details", MenuItemType.Standard, () => beatmapOverlay.FetchAndShowBeatmap(beatmap.OnlineBeatmapID.Value)));
|
|
||||||
|
|
||||||
return items.ToArray();
|
return items.ToArray();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private MenuItem createCollectionMenuItem(BeatmapCollection collection)
|
||||||
|
{
|
||||||
|
return new ToggleMenuItem(collection.Name.Value, MenuItemType.Standard, s =>
|
||||||
|
{
|
||||||
|
if (s)
|
||||||
|
collection.Beatmaps.Add(beatmap);
|
||||||
|
else
|
||||||
|
collection.Beatmaps.Remove(beatmap);
|
||||||
|
})
|
||||||
|
{
|
||||||
|
State = { Value = collection.Beatmaps.Contains(beatmap) }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
protected override void Dispose(bool isDisposing)
|
protected override void Dispose(bool isDisposing)
|
||||||
{
|
{
|
||||||
base.Dispose(isDisposing);
|
base.Dispose(isDisposing);
|
||||||
|
@ -16,6 +16,7 @@ using osu.Framework.Input.Events;
|
|||||||
using osu.Framework.Localisation;
|
using osu.Framework.Localisation;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Beatmaps.Drawables;
|
using osu.Game.Beatmaps.Drawables;
|
||||||
|
using osu.Game.Collections;
|
||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Graphics.Sprites;
|
using osu.Game.Graphics.Sprites;
|
||||||
using osu.Game.Graphics.UserInterface;
|
using osu.Game.Graphics.UserInterface;
|
||||||
@ -34,6 +35,12 @@ namespace osu.Game.Screens.Select.Carousel
|
|||||||
[Resolved(CanBeNull = true)]
|
[Resolved(CanBeNull = true)]
|
||||||
private DialogOverlay dialogOverlay { get; set; }
|
private DialogOverlay dialogOverlay { get; set; }
|
||||||
|
|
||||||
|
[Resolved(CanBeNull = true)]
|
||||||
|
private CollectionManager collectionManager { get; set; }
|
||||||
|
|
||||||
|
[Resolved(CanBeNull = true)]
|
||||||
|
private ManageCollectionsDialog manageCollectionsDialog { get; set; }
|
||||||
|
|
||||||
private readonly BeatmapSetInfo beatmapSet;
|
private readonly BeatmapSetInfo beatmapSet;
|
||||||
|
|
||||||
public DrawableCarouselBeatmapSet(CarouselBeatmapSet set)
|
public DrawableCarouselBeatmapSet(CarouselBeatmapSet set)
|
||||||
@ -135,16 +142,61 @@ namespace osu.Game.Screens.Select.Carousel
|
|||||||
if (beatmapSet.OnlineBeatmapSetID != null && viewDetails != null)
|
if (beatmapSet.OnlineBeatmapSetID != null && viewDetails != null)
|
||||||
items.Add(new OsuMenuItem("Details...", MenuItemType.Standard, () => viewDetails(beatmapSet.OnlineBeatmapSetID.Value)));
|
items.Add(new OsuMenuItem("Details...", MenuItemType.Standard, () => viewDetails(beatmapSet.OnlineBeatmapSetID.Value)));
|
||||||
|
|
||||||
|
if (collectionManager != null)
|
||||||
|
{
|
||||||
|
var collectionItems = collectionManager.Collections.Select(createCollectionMenuItem).ToList();
|
||||||
|
if (manageCollectionsDialog != null)
|
||||||
|
collectionItems.Add(new OsuMenuItem("Manage...", MenuItemType.Standard, manageCollectionsDialog.Show));
|
||||||
|
|
||||||
|
items.Add(new OsuMenuItem("Collections") { Items = collectionItems });
|
||||||
|
}
|
||||||
|
|
||||||
if (beatmapSet.Beatmaps.Any(b => b.Hidden))
|
if (beatmapSet.Beatmaps.Any(b => b.Hidden))
|
||||||
items.Add(new OsuMenuItem("Restore all hidden", MenuItemType.Standard, () => restoreHiddenRequested(beatmapSet)));
|
items.Add(new OsuMenuItem("Restore all hidden", MenuItemType.Standard, () => restoreHiddenRequested(beatmapSet)));
|
||||||
|
|
||||||
if (dialogOverlay != null)
|
if (dialogOverlay != null)
|
||||||
items.Add(new OsuMenuItem("Delete", MenuItemType.Destructive, () => dialogOverlay.Push(new BeatmapDeleteDialog(beatmapSet))));
|
items.Add(new OsuMenuItem("Delete...", MenuItemType.Destructive, () => dialogOverlay.Push(new BeatmapDeleteDialog(beatmapSet))));
|
||||||
|
|
||||||
return items.ToArray();
|
return items.ToArray();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private MenuItem createCollectionMenuItem(BeatmapCollection collection)
|
||||||
|
{
|
||||||
|
TernaryState state;
|
||||||
|
|
||||||
|
var countExisting = beatmapSet.Beatmaps.Count(b => collection.Beatmaps.Contains(b));
|
||||||
|
|
||||||
|
if (countExisting == beatmapSet.Beatmaps.Count)
|
||||||
|
state = TernaryState.True;
|
||||||
|
else if (countExisting > 0)
|
||||||
|
state = TernaryState.Indeterminate;
|
||||||
|
else
|
||||||
|
state = TernaryState.False;
|
||||||
|
|
||||||
|
return new TernaryStateMenuItem(collection.Name.Value, MenuItemType.Standard, s =>
|
||||||
|
{
|
||||||
|
foreach (var b in beatmapSet.Beatmaps)
|
||||||
|
{
|
||||||
|
switch (s)
|
||||||
|
{
|
||||||
|
case TernaryState.True:
|
||||||
|
if (collection.Beatmaps.Contains(b))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
collection.Beatmaps.Add(b);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case TernaryState.False:
|
||||||
|
collection.Beatmaps.Remove(b);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
{
|
||||||
|
State = { Value = state }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
private class PanelBackground : BufferedContainer
|
private class PanelBackground : BufferedContainer
|
||||||
{
|
{
|
||||||
public PanelBackground(WorkingBeatmap working)
|
public PanelBackground(WorkingBeatmap working)
|
||||||
|
273
osu.Game/Screens/Select/CollectionFilterDropdown.cs
Normal file
273
osu.Game/Screens/Select/CollectionFilterDropdown.cs
Normal file
@ -0,0 +1,273 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System.Collections.Specialized;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Linq;
|
||||||
|
using JetBrains.Annotations;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Sprites;
|
||||||
|
using osu.Framework.Graphics.UserInterface;
|
||||||
|
using osu.Framework.Input.Events;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Collections;
|
||||||
|
using osu.Game.Graphics;
|
||||||
|
using osu.Game.Graphics.Containers;
|
||||||
|
using osu.Game.Graphics.UserInterface;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
|
namespace osu.Game.Screens.Select
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A dropdown to select the <see cref="CollectionMenuItem"/> to filter beatmaps using.
|
||||||
|
/// </summary>
|
||||||
|
public class CollectionFilterDropdown : OsuDropdown<CollectionMenuItem>
|
||||||
|
{
|
||||||
|
private readonly IBindableList<BeatmapCollection> collections = new BindableList<BeatmapCollection>();
|
||||||
|
private readonly IBindableList<BeatmapInfo> beatmaps = new BindableList<BeatmapInfo>();
|
||||||
|
private readonly BindableList<CollectionMenuItem> filters = new BindableList<CollectionMenuItem>();
|
||||||
|
|
||||||
|
[Resolved(CanBeNull = true)]
|
||||||
|
private ManageCollectionsDialog manageCollectionsDialog { get; set; }
|
||||||
|
|
||||||
|
public CollectionFilterDropdown()
|
||||||
|
{
|
||||||
|
ItemSource = filters;
|
||||||
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader(permitNulls: true)]
|
||||||
|
private void load([CanBeNull] CollectionManager collectionManager)
|
||||||
|
{
|
||||||
|
if (collectionManager != null)
|
||||||
|
collections.BindTo(collectionManager.Collections);
|
||||||
|
|
||||||
|
collections.CollectionChanged += (_, __) => collectionsChanged();
|
||||||
|
collectionsChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
|
||||||
|
Current.BindValueChanged(filterChanged, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Occurs when a collection has been added or removed.
|
||||||
|
/// </summary>
|
||||||
|
private void collectionsChanged()
|
||||||
|
{
|
||||||
|
var selectedItem = SelectedItem?.Value?.Collection;
|
||||||
|
|
||||||
|
filters.Clear();
|
||||||
|
filters.Add(new AllBeatmapsCollectionMenuItem());
|
||||||
|
filters.AddRange(collections.Select(c => new CollectionMenuItem(c)));
|
||||||
|
filters.Add(new ManageCollectionsMenuItem());
|
||||||
|
|
||||||
|
Current.Value = filters.SingleOrDefault(f => f.Collection != null && f.Collection == selectedItem) ?? filters[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Occurs when the <see cref="CollectionMenuItem"/> selection has changed.
|
||||||
|
/// </summary>
|
||||||
|
private void filterChanged(ValueChangedEvent<CollectionMenuItem> filter)
|
||||||
|
{
|
||||||
|
// Binding the beatmaps will trigger a collection change event, which results in an infinite-loop. This is rebound later, when it's safe to do so.
|
||||||
|
beatmaps.CollectionChanged -= filterBeatmapsChanged;
|
||||||
|
|
||||||
|
if (filter.OldValue?.Collection != null)
|
||||||
|
beatmaps.UnbindFrom(filter.OldValue.Collection.Beatmaps);
|
||||||
|
|
||||||
|
if (filter.NewValue?.Collection != null)
|
||||||
|
beatmaps.BindTo(filter.NewValue.Collection.Beatmaps);
|
||||||
|
|
||||||
|
beatmaps.CollectionChanged += filterBeatmapsChanged;
|
||||||
|
|
||||||
|
// Never select the manage collection filter - rollback to the previous filter.
|
||||||
|
// This is done after the above since it is important that bindable is unbound from OldValue, which is lost after forcing it back to the old value.
|
||||||
|
if (filter.NewValue is ManageCollectionsMenuItem)
|
||||||
|
{
|
||||||
|
Current.Value = filter.OldValue;
|
||||||
|
manageCollectionsDialog?.Show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Occurs when the beatmaps contained by a <see cref="BeatmapCollection"/> have changed.
|
||||||
|
/// </summary>
|
||||||
|
private void filterBeatmapsChanged(object sender, NotifyCollectionChangedEventArgs e)
|
||||||
|
{
|
||||||
|
// The filtered beatmaps have changed, without the filter having changed itself. So a change in filter must be notified.
|
||||||
|
// Note that this does NOT propagate to bound bindables, so the FilterControl must bind directly to the value change event of this bindable.
|
||||||
|
Current.TriggerChange();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override string GenerateItemText(CollectionMenuItem item) => item.CollectionName.Value;
|
||||||
|
|
||||||
|
protected override DropdownHeader CreateHeader() => new CollectionDropdownHeader
|
||||||
|
{
|
||||||
|
SelectedItem = { BindTarget = Current }
|
||||||
|
};
|
||||||
|
|
||||||
|
protected override DropdownMenu CreateMenu() => new CollectionDropdownMenu();
|
||||||
|
|
||||||
|
public class CollectionDropdownHeader : OsuDropdownHeader
|
||||||
|
{
|
||||||
|
public readonly Bindable<CollectionMenuItem> SelectedItem = new Bindable<CollectionMenuItem>();
|
||||||
|
private readonly Bindable<string> collectionName = new Bindable<string>();
|
||||||
|
|
||||||
|
protected override string Label
|
||||||
|
{
|
||||||
|
get => base.Label;
|
||||||
|
set { } // See updateText().
|
||||||
|
}
|
||||||
|
|
||||||
|
public CollectionDropdownHeader()
|
||||||
|
{
|
||||||
|
Height = 25;
|
||||||
|
Icon.Size = new Vector2(16);
|
||||||
|
Foreground.Padding = new MarginPadding { Top = 4, Bottom = 4, Left = 8, Right = 4 };
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
|
||||||
|
SelectedItem.BindValueChanged(_ => updateBindable(), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateBindable()
|
||||||
|
{
|
||||||
|
collectionName.UnbindAll();
|
||||||
|
|
||||||
|
if (SelectedItem.Value != null)
|
||||||
|
collectionName.BindTo(SelectedItem.Value.CollectionName);
|
||||||
|
|
||||||
|
collectionName.BindValueChanged(_ => updateText(), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dropdowns don't bind to value changes, so the real name is copied directly from the selected item here.
|
||||||
|
private void updateText() => base.Label = collectionName.Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
private class CollectionDropdownMenu : OsuDropdownMenu
|
||||||
|
{
|
||||||
|
public CollectionDropdownMenu()
|
||||||
|
{
|
||||||
|
MaxHeight = 200;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override DrawableDropdownMenuItem CreateDrawableDropdownMenuItem(MenuItem item) => new CollectionDropdownMenuItem(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
private class CollectionDropdownMenuItem : OsuDropdownMenu.DrawableOsuDropdownMenuItem
|
||||||
|
{
|
||||||
|
[NotNull]
|
||||||
|
protected new CollectionMenuItem Item => ((DropdownMenuItem<CollectionMenuItem>)base.Item).Value;
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private OsuColour colours { get; set; }
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private IBindable<WorkingBeatmap> beatmap { get; set; }
|
||||||
|
|
||||||
|
[CanBeNull]
|
||||||
|
private readonly BindableList<BeatmapInfo> collectionBeatmaps;
|
||||||
|
|
||||||
|
[NotNull]
|
||||||
|
private readonly Bindable<string> collectionName;
|
||||||
|
|
||||||
|
private IconButton addOrRemoveButton;
|
||||||
|
private Content content;
|
||||||
|
private bool beatmapInCollection;
|
||||||
|
|
||||||
|
public CollectionDropdownMenuItem(MenuItem item)
|
||||||
|
: base(item)
|
||||||
|
{
|
||||||
|
collectionBeatmaps = Item.Collection?.Beatmaps.GetBoundCopy();
|
||||||
|
collectionName = Item.CollectionName.GetBoundCopy();
|
||||||
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load()
|
||||||
|
{
|
||||||
|
AddInternal(addOrRemoveButton = new IconButton
|
||||||
|
{
|
||||||
|
Anchor = Anchor.CentreRight,
|
||||||
|
Origin = Anchor.CentreRight,
|
||||||
|
X = -OsuScrollContainer.SCROLL_BAR_HEIGHT,
|
||||||
|
Scale = new Vector2(0.65f),
|
||||||
|
Action = addOrRemove,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
|
||||||
|
if (collectionBeatmaps != null)
|
||||||
|
{
|
||||||
|
collectionBeatmaps.CollectionChanged += (_, __) => collectionChanged();
|
||||||
|
beatmap.BindValueChanged(_ => collectionChanged(), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Although the DrawableMenuItem binds to value changes of the item's text, the item is an internal implementation detail of Dropdown that has no knowledge
|
||||||
|
// of the underlying CollectionFilter value and its accompanying name, so the real name has to be copied here. Without this, the collection name wouldn't update when changed.
|
||||||
|
collectionName.BindValueChanged(name => content.Text = name.NewValue, true);
|
||||||
|
|
||||||
|
updateButtonVisibility();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override bool OnHover(HoverEvent e)
|
||||||
|
{
|
||||||
|
updateButtonVisibility();
|
||||||
|
return base.OnHover(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnHoverLost(HoverLostEvent e)
|
||||||
|
{
|
||||||
|
updateButtonVisibility();
|
||||||
|
base.OnHoverLost(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void collectionChanged()
|
||||||
|
{
|
||||||
|
Debug.Assert(collectionBeatmaps != null);
|
||||||
|
|
||||||
|
beatmapInCollection = collectionBeatmaps.Contains(beatmap.Value.BeatmapInfo);
|
||||||
|
|
||||||
|
addOrRemoveButton.Enabled.Value = !beatmap.IsDefault;
|
||||||
|
addOrRemoveButton.Icon = beatmapInCollection ? FontAwesome.Solid.MinusSquare : FontAwesome.Solid.PlusSquare;
|
||||||
|
addOrRemoveButton.TooltipText = beatmapInCollection ? "Remove selected beatmap" : "Add selected beatmap";
|
||||||
|
|
||||||
|
updateButtonVisibility();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnSelectChange()
|
||||||
|
{
|
||||||
|
base.OnSelectChange();
|
||||||
|
updateButtonVisibility();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateButtonVisibility()
|
||||||
|
{
|
||||||
|
if (collectionBeatmaps == null)
|
||||||
|
addOrRemoveButton.Alpha = 0;
|
||||||
|
else
|
||||||
|
addOrRemoveButton.Alpha = IsHovered || IsPreSelected || beatmapInCollection ? 1 : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addOrRemove()
|
||||||
|
{
|
||||||
|
Debug.Assert(collectionBeatmaps != null);
|
||||||
|
|
||||||
|
if (!collectionBeatmaps.Remove(beatmap.Value.BeatmapInfo))
|
||||||
|
collectionBeatmaps.Add(beatmap.Value.BeatmapInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override Drawable CreateContent() => content = (Content)base.CreateContent();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
55
osu.Game/Screens/Select/CollectionMenuItem.cs
Normal file
55
osu.Game/Screens/Select/CollectionMenuItem.cs
Normal 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 JetBrains.Annotations;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Game.Collections;
|
||||||
|
|
||||||
|
namespace osu.Game.Screens.Select
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A <see cref="BeatmapCollection"/> filter.
|
||||||
|
/// </summary>
|
||||||
|
public class CollectionMenuItem
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The collection to filter beatmaps from.
|
||||||
|
/// May be null to not filter by collection (include all beatmaps).
|
||||||
|
/// </summary>
|
||||||
|
[CanBeNull]
|
||||||
|
public readonly BeatmapCollection Collection;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The name of the collection.
|
||||||
|
/// </summary>
|
||||||
|
[NotNull]
|
||||||
|
public readonly Bindable<string> CollectionName;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new <see cref="CollectionMenuItem"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="collection">The collection to filter beatmaps from.</param>
|
||||||
|
public CollectionMenuItem([CanBeNull] BeatmapCollection collection)
|
||||||
|
{
|
||||||
|
Collection = collection;
|
||||||
|
CollectionName = Collection?.Name.GetBoundCopy() ?? new Bindable<string>("All beatmaps");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class AllBeatmapsCollectionMenuItem : CollectionMenuItem
|
||||||
|
{
|
||||||
|
public AllBeatmapsCollectionMenuItem()
|
||||||
|
: base(null)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ManageCollectionsMenuItem : CollectionMenuItem
|
||||||
|
{
|
||||||
|
public ManageCollectionsMenuItem()
|
||||||
|
: base(null)
|
||||||
|
{
|
||||||
|
CollectionName.Value = "Manage collections...";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -21,7 +21,8 @@ namespace osu.Game.Screens.Select
|
|||||||
{
|
{
|
||||||
public class FilterControl : Container
|
public class FilterControl : Container
|
||||||
{
|
{
|
||||||
public const float HEIGHT = 100;
|
public const float HEIGHT = 2 * side_margin + 85;
|
||||||
|
private const float side_margin = 20;
|
||||||
|
|
||||||
public Action<FilterCriteria> FilterChanged;
|
public Action<FilterCriteria> FilterChanged;
|
||||||
|
|
||||||
@ -41,6 +42,7 @@ namespace osu.Game.Screens.Select
|
|||||||
Sort = sortMode.Value,
|
Sort = sortMode.Value,
|
||||||
AllowConvertedBeatmaps = showConverted.Value,
|
AllowConvertedBeatmaps = showConverted.Value,
|
||||||
Ruleset = ruleset.Value,
|
Ruleset = ruleset.Value,
|
||||||
|
Collection = collectionDropdown?.Current.Value.Collection
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!minimumStars.IsDefault)
|
if (!minimumStars.IsDefault)
|
||||||
@ -54,6 +56,7 @@ namespace osu.Game.Screens.Select
|
|||||||
}
|
}
|
||||||
|
|
||||||
private SeekLimitedSearchTextBox searchTextBox;
|
private SeekLimitedSearchTextBox searchTextBox;
|
||||||
|
private CollectionFilterDropdown collectionDropdown;
|
||||||
|
|
||||||
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) =>
|
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) =>
|
||||||
base.ReceivePositionalInputAt(screenSpacePos) || sortTabs.ReceivePositionalInputAt(screenSpacePos);
|
base.ReceivePositionalInputAt(screenSpacePos) || sortTabs.ReceivePositionalInputAt(screenSpacePos);
|
||||||
@ -90,65 +93,112 @@ namespace osu.Game.Screens.Select
|
|||||||
},
|
},
|
||||||
new Container
|
new Container
|
||||||
{
|
{
|
||||||
Padding = new MarginPadding(20),
|
Padding = new MarginPadding(side_margin),
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Width = 0.5f,
|
Width = 0.5f,
|
||||||
Anchor = Anchor.TopRight,
|
Anchor = Anchor.TopRight,
|
||||||
Origin = Anchor.TopRight,
|
Origin = Anchor.TopRight,
|
||||||
Children = new Drawable[]
|
Child = new GridContainer
|
||||||
{
|
{
|
||||||
searchTextBox = new SeekLimitedSearchTextBox { RelativeSizeAxes = Axes.X },
|
RelativeSizeAxes = Axes.Both,
|
||||||
new Box
|
RowDimensions = new[]
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.X,
|
new Dimension(GridSizeMode.Absolute, 60),
|
||||||
Height = 1,
|
new Dimension(GridSizeMode.Absolute, 5),
|
||||||
Colour = OsuColour.Gray(80),
|
new Dimension(GridSizeMode.Absolute, 20),
|
||||||
Origin = Anchor.BottomLeft,
|
|
||||||
Anchor = Anchor.BottomLeft,
|
|
||||||
},
|
},
|
||||||
new FillFlowContainer
|
Content = new[]
|
||||||
{
|
{
|
||||||
Anchor = Anchor.BottomRight,
|
new Drawable[]
|
||||||
Origin = Anchor.BottomRight,
|
|
||||||
Direction = FillDirection.Horizontal,
|
|
||||||
RelativeSizeAxes = Axes.X,
|
|
||||||
AutoSizeAxes = Axes.Y,
|
|
||||||
Spacing = new Vector2(OsuTabControl<SortMode>.HORIZONTAL_SPACING, 0),
|
|
||||||
Children = new Drawable[]
|
|
||||||
{
|
{
|
||||||
new OsuTabControlCheckbox
|
new Container
|
||||||
{
|
{
|
||||||
Text = "Show converted",
|
RelativeSizeAxes = Axes.Both,
|
||||||
Current = config.GetBindable<bool>(OsuSetting.ShowConvertedBeatmaps),
|
Children = new Drawable[]
|
||||||
Anchor = Anchor.BottomRight,
|
{
|
||||||
Origin = Anchor.BottomRight,
|
searchTextBox = new SeekLimitedSearchTextBox { RelativeSizeAxes = Axes.X },
|
||||||
},
|
new Box
|
||||||
sortTabs = new OsuTabControl<SortMode>
|
{
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
Height = 1,
|
||||||
|
Colour = OsuColour.Gray(80),
|
||||||
|
Origin = Anchor.BottomLeft,
|
||||||
|
Anchor = Anchor.BottomLeft,
|
||||||
|
},
|
||||||
|
new FillFlowContainer
|
||||||
|
{
|
||||||
|
Anchor = Anchor.BottomRight,
|
||||||
|
Origin = Anchor.BottomRight,
|
||||||
|
Direction = FillDirection.Horizontal,
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
AutoSizeAxes = Axes.Y,
|
||||||
|
Spacing = new Vector2(OsuTabControl<SortMode>.HORIZONTAL_SPACING, 0),
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
new OsuTabControlCheckbox
|
||||||
|
{
|
||||||
|
Text = "Show converted",
|
||||||
|
Current = config.GetBindable<bool>(OsuSetting.ShowConvertedBeatmaps),
|
||||||
|
Anchor = Anchor.BottomRight,
|
||||||
|
Origin = Anchor.BottomRight,
|
||||||
|
},
|
||||||
|
sortTabs = new OsuTabControl<SortMode>
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
Width = 0.5f,
|
||||||
|
Height = 24,
|
||||||
|
AutoSort = true,
|
||||||
|
Anchor = Anchor.BottomRight,
|
||||||
|
Origin = Anchor.BottomRight,
|
||||||
|
AccentColour = colours.GreenLight,
|
||||||
|
Current = { BindTarget = sortMode }
|
||||||
|
},
|
||||||
|
new OsuSpriteText
|
||||||
|
{
|
||||||
|
Text = "Sort by",
|
||||||
|
Font = OsuFont.GetFont(size: 14),
|
||||||
|
Margin = new MarginPadding(5),
|
||||||
|
Anchor = Anchor.BottomRight,
|
||||||
|
Origin = Anchor.BottomRight,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
null,
|
||||||
|
new Drawable[]
|
||||||
|
{
|
||||||
|
new Container
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.X,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Width = 0.5f,
|
Children = new Drawable[]
|
||||||
Height = 24,
|
{
|
||||||
AutoSort = true,
|
collectionDropdown = new CollectionFilterDropdown
|
||||||
Anchor = Anchor.BottomRight,
|
{
|
||||||
Origin = Anchor.BottomRight,
|
Anchor = Anchor.TopRight,
|
||||||
AccentColour = colours.GreenLight,
|
Origin = Anchor.TopRight,
|
||||||
Current = { BindTarget = sortMode }
|
RelativeSizeAxes = Axes.X,
|
||||||
},
|
Width = 0.4f,
|
||||||
new OsuSpriteText
|
}
|
||||||
{
|
}
|
||||||
Text = "Sort by",
|
}
|
||||||
Font = OsuFont.GetFont(size: 14),
|
},
|
||||||
Margin = new MarginPadding(5),
|
}
|
||||||
Anchor = Anchor.BottomRight,
|
|
||||||
Origin = Anchor.BottomRight,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
searchTextBox.Current.ValueChanged += _ => FilterChanged?.Invoke(CreateCriteria());
|
collectionDropdown.Current.ValueChanged += val =>
|
||||||
|
{
|
||||||
|
if (val.NewValue == null)
|
||||||
|
// may be null briefly while menu is repopulated.
|
||||||
|
return;
|
||||||
|
|
||||||
|
updateCriteria();
|
||||||
|
};
|
||||||
|
|
||||||
|
searchTextBox.Current.ValueChanged += _ => updateCriteria();
|
||||||
|
|
||||||
updateCriteria();
|
updateCriteria();
|
||||||
}
|
}
|
||||||
@ -156,7 +206,6 @@ namespace osu.Game.Screens.Select
|
|||||||
public void Deactivate()
|
public void Deactivate()
|
||||||
{
|
{
|
||||||
searchTextBox.ReadOnly = true;
|
searchTextBox.ReadOnly = true;
|
||||||
|
|
||||||
searchTextBox.HoldFocus = false;
|
searchTextBox.HoldFocus = false;
|
||||||
if (searchTextBox.HasFocus)
|
if (searchTextBox.HasFocus)
|
||||||
GetContainingInputManager().ChangeFocus(searchTextBox);
|
GetContainingInputManager().ChangeFocus(searchTextBox);
|
||||||
|
@ -4,7 +4,9 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using JetBrains.Annotations;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Collections;
|
||||||
using osu.Game.Rulesets;
|
using osu.Game.Rulesets;
|
||||||
using osu.Game.Screens.Select.Filter;
|
using osu.Game.Screens.Select.Filter;
|
||||||
|
|
||||||
@ -51,6 +53,12 @@ namespace osu.Game.Screens.Select
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The collection to filter beatmaps from.
|
||||||
|
/// </summary>
|
||||||
|
[CanBeNull]
|
||||||
|
public BeatmapCollection Collection;
|
||||||
|
|
||||||
public struct OptionalRange<T> : IEquatable<OptionalRange<T>>
|
public struct OptionalRange<T> : IEquatable<OptionalRange<T>>
|
||||||
where T : struct
|
where T : struct
|
||||||
{
|
{
|
||||||
|
@ -27,6 +27,7 @@ namespace osu.Game.Tests.Beatmaps
|
|||||||
BeatmapInfo.Ruleset = ruleset;
|
BeatmapInfo.Ruleset = ruleset;
|
||||||
BeatmapInfo.RulesetID = ruleset.ID ?? 0;
|
BeatmapInfo.RulesetID = ruleset.ID ?? 0;
|
||||||
BeatmapInfo.BeatmapSet.Metadata = BeatmapInfo.Metadata;
|
BeatmapInfo.BeatmapSet.Metadata = BeatmapInfo.Metadata;
|
||||||
|
BeatmapInfo.BeatmapSet.Files = new List<BeatmapSetFileInfo>();
|
||||||
BeatmapInfo.BeatmapSet.Beatmaps = new List<BeatmapInfo> { BeatmapInfo };
|
BeatmapInfo.BeatmapSet.Beatmaps = new List<BeatmapInfo> { BeatmapInfo };
|
||||||
BeatmapInfo.BeatmapSet.OnlineInfo = new BeatmapSetOnlineInfo
|
BeatmapInfo.BeatmapSet.OnlineInfo = new BeatmapSetOnlineInfo
|
||||||
{
|
{
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
// 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.Runtime.CompilerServices;
|
||||||
using osu.Framework.Platform;
|
using osu.Framework.Platform;
|
||||||
|
|
||||||
namespace osu.Game.Tests
|
namespace osu.Game.Tests
|
||||||
@ -10,8 +11,15 @@ namespace osu.Game.Tests
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public class CleanRunHeadlessGameHost : HeadlessGameHost
|
public class CleanRunHeadlessGameHost : HeadlessGameHost
|
||||||
{
|
{
|
||||||
public CleanRunHeadlessGameHost(string gameName = @"", bool bindIPC = false, bool realtime = true)
|
/// <summary>
|
||||||
: base(gameName, bindIPC, realtime)
|
/// Create a new instance.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="gameSuffix">An optional suffix which will isolate this host from others called from the same method source.</param>
|
||||||
|
/// <param name="bindIPC">Whether to bind IPC channels.</param>
|
||||||
|
/// <param name="realtime">Whether the host should be forced to run in realtime, rather than accelerated test time.</param>
|
||||||
|
/// <param name="callingMethodName">The name of the calling method, used for test file isolation and clean-up.</param>
|
||||||
|
public CleanRunHeadlessGameHost(string gameSuffix = @"", bool bindIPC = false, bool realtime = true, [CallerMemberName] string callingMethodName = @"")
|
||||||
|
: base(callingMethodName + gameSuffix, bindIPC, realtime)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -24,7 +24,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.907.0" />
|
<PackageReference Include="ppy.osu.Framework" Version="2020.910.0" />
|
||||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2020.904.0" />
|
<PackageReference Include="ppy.osu.Game.Resources" Version="2020.904.0" />
|
||||||
<PackageReference Include="Sentry" Version="2.1.6" />
|
<PackageReference Include="Sentry" Version="2.1.6" />
|
||||||
<PackageReference Include="SharpCompress" Version="0.26.0" />
|
<PackageReference Include="SharpCompress" Version="0.26.0" />
|
||||||
|
@ -70,7 +70,7 @@
|
|||||||
<Reference Include="System.Net.Http" />
|
<Reference Include="System.Net.Http" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup Label="Package References">
|
<ItemGroup Label="Package References">
|
||||||
<PackageReference Include="ppy.osu.Framework.iOS" Version="2020.907.0" />
|
<PackageReference Include="ppy.osu.Framework.iOS" Version="2020.910.0" />
|
||||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2020.904.0" />
|
<PackageReference Include="ppy.osu.Game.Resources" Version="2020.904.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. -->
|
||||||
@ -80,7 +80,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.907.0" />
|
<PackageReference Include="ppy.osu.Framework" Version="2020.910.0" />
|
||||||
<PackageReference Include="SharpCompress" Version="0.26.0" />
|
<PackageReference Include="SharpCompress" Version="0.26.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" />
|
||||||
|
Loading…
x
Reference in New Issue
Block a user