mirror of
https://github.com/osukey/osukey.git
synced 2025-08-05 15:44:04 +09:00
Merge pull request #12361 from Naxesss/verify-tab
Add basic AiMod-like features
This commit is contained in:
@ -0,0 +1,260 @@
|
|||||||
|
// 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.Game.Beatmaps;
|
||||||
|
using osu.Game.Rulesets.Objects;
|
||||||
|
using osu.Game.Rulesets.Objects.Types;
|
||||||
|
using osu.Game.Rulesets.Osu.Edit.Checks;
|
||||||
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
|
using osu.Game.Rulesets.Osu.UI;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Osu.Tests.Editor.Checks
|
||||||
|
{
|
||||||
|
[TestFixture]
|
||||||
|
public class CheckOffscreenObjectsTest
|
||||||
|
{
|
||||||
|
private static readonly Vector2 playfield_centre = OsuPlayfield.BASE_SIZE * 0.5f;
|
||||||
|
|
||||||
|
private CheckOffscreenObjects check;
|
||||||
|
|
||||||
|
[SetUp]
|
||||||
|
public void Setup()
|
||||||
|
{
|
||||||
|
check = new CheckOffscreenObjects();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestCircleInCenter()
|
||||||
|
{
|
||||||
|
var beatmap = new Beatmap<HitObject>
|
||||||
|
{
|
||||||
|
HitObjects = new List<HitObject>
|
||||||
|
{
|
||||||
|
new HitCircle
|
||||||
|
{
|
||||||
|
StartTime = 3000,
|
||||||
|
Position = playfield_centre // Playfield is 640 x 480.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Assert.That(check.Run(beatmap), Is.Empty);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestCircleNearEdge()
|
||||||
|
{
|
||||||
|
var beatmap = new Beatmap<HitObject>
|
||||||
|
{
|
||||||
|
HitObjects = new List<HitObject>
|
||||||
|
{
|
||||||
|
new HitCircle
|
||||||
|
{
|
||||||
|
StartTime = 3000,
|
||||||
|
Position = new Vector2(5, 5)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Assert.That(check.Run(beatmap), Is.Empty);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestCircleNearEdgeStackedOffscreen()
|
||||||
|
{
|
||||||
|
var beatmap = new Beatmap<HitObject>
|
||||||
|
{
|
||||||
|
HitObjects = new List<HitObject>
|
||||||
|
{
|
||||||
|
new HitCircle
|
||||||
|
{
|
||||||
|
StartTime = 3000,
|
||||||
|
Position = new Vector2(5, 5),
|
||||||
|
StackHeight = 5
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
assertOffscreenCircle(beatmap);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestCircleOffscreen()
|
||||||
|
{
|
||||||
|
var beatmap = new Beatmap<HitObject>
|
||||||
|
{
|
||||||
|
HitObjects = new List<HitObject>
|
||||||
|
{
|
||||||
|
new HitCircle
|
||||||
|
{
|
||||||
|
StartTime = 3000,
|
||||||
|
Position = new Vector2(0, 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
assertOffscreenCircle(beatmap);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestSliderInCenter()
|
||||||
|
{
|
||||||
|
var beatmap = new Beatmap<HitObject>
|
||||||
|
{
|
||||||
|
HitObjects = new List<HitObject>
|
||||||
|
{
|
||||||
|
new Slider
|
||||||
|
{
|
||||||
|
StartTime = 3000,
|
||||||
|
Position = new Vector2(420, 240),
|
||||||
|
Path = new SliderPath(new[]
|
||||||
|
{
|
||||||
|
new PathControlPoint(new Vector2(0, 0), PathType.Linear),
|
||||||
|
new PathControlPoint(new Vector2(-100, 0))
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Assert.That(check.Run(beatmap), Is.Empty);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestSliderNearEdge()
|
||||||
|
{
|
||||||
|
var beatmap = new Beatmap<HitObject>
|
||||||
|
{
|
||||||
|
HitObjects = new List<HitObject>
|
||||||
|
{
|
||||||
|
new Slider
|
||||||
|
{
|
||||||
|
StartTime = 3000,
|
||||||
|
Position = playfield_centre,
|
||||||
|
Path = new SliderPath(new[]
|
||||||
|
{
|
||||||
|
new PathControlPoint(new Vector2(0, 0), PathType.Linear),
|
||||||
|
new PathControlPoint(new Vector2(0, -playfield_centre.Y + 5))
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Assert.That(check.Run(beatmap), Is.Empty);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestSliderNearEdgeStackedOffscreen()
|
||||||
|
{
|
||||||
|
var beatmap = new Beatmap<HitObject>
|
||||||
|
{
|
||||||
|
HitObjects = new List<HitObject>
|
||||||
|
{
|
||||||
|
new Slider
|
||||||
|
{
|
||||||
|
StartTime = 3000,
|
||||||
|
Position = playfield_centre,
|
||||||
|
Path = new SliderPath(new[]
|
||||||
|
{
|
||||||
|
new PathControlPoint(new Vector2(0, 0), PathType.Linear),
|
||||||
|
new PathControlPoint(new Vector2(0, -playfield_centre.Y + 5))
|
||||||
|
}),
|
||||||
|
StackHeight = 5
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
assertOffscreenSlider(beatmap);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestSliderOffscreenStart()
|
||||||
|
{
|
||||||
|
var beatmap = new Beatmap<HitObject>
|
||||||
|
{
|
||||||
|
HitObjects = new List<HitObject>
|
||||||
|
{
|
||||||
|
new Slider
|
||||||
|
{
|
||||||
|
StartTime = 3000,
|
||||||
|
Position = new Vector2(0, 0),
|
||||||
|
Path = new SliderPath(new[]
|
||||||
|
{
|
||||||
|
new PathControlPoint(new Vector2(0, 0), PathType.Linear),
|
||||||
|
new PathControlPoint(playfield_centre)
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
assertOffscreenSlider(beatmap);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestSliderOffscreenEnd()
|
||||||
|
{
|
||||||
|
var beatmap = new Beatmap<HitObject>
|
||||||
|
{
|
||||||
|
HitObjects = new List<HitObject>
|
||||||
|
{
|
||||||
|
new Slider
|
||||||
|
{
|
||||||
|
StartTime = 3000,
|
||||||
|
Position = playfield_centre,
|
||||||
|
Path = new SliderPath(new[]
|
||||||
|
{
|
||||||
|
new PathControlPoint(new Vector2(0, 0), PathType.Linear),
|
||||||
|
new PathControlPoint(-playfield_centre)
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
assertOffscreenSlider(beatmap);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestSliderOffscreenPath()
|
||||||
|
{
|
||||||
|
var beatmap = new Beatmap<HitObject>
|
||||||
|
{
|
||||||
|
HitObjects = new List<HitObject>
|
||||||
|
{
|
||||||
|
new Slider
|
||||||
|
{
|
||||||
|
StartTime = 3000,
|
||||||
|
Position = playfield_centre,
|
||||||
|
Path = new SliderPath(new[]
|
||||||
|
{
|
||||||
|
// Circular arc shoots over the top of the screen.
|
||||||
|
new PathControlPoint(new Vector2(0, 0), PathType.PerfectCurve),
|
||||||
|
new PathControlPoint(new Vector2(-100, -200)),
|
||||||
|
new PathControlPoint(new Vector2(100, -200))
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
assertOffscreenSlider(beatmap);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertOffscreenCircle(IBeatmap beatmap)
|
||||||
|
{
|
||||||
|
var issues = check.Run(beatmap).ToList();
|
||||||
|
|
||||||
|
Assert.That(issues, Has.Count.EqualTo(1));
|
||||||
|
Assert.That(issues.Single().Template is CheckOffscreenObjects.IssueTemplateOffscreenCircle);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertOffscreenSlider(IBeatmap beatmap)
|
||||||
|
{
|
||||||
|
var issues = check.Run(beatmap).ToList();
|
||||||
|
|
||||||
|
Assert.That(issues, Has.Count.EqualTo(1));
|
||||||
|
Assert.That(issues.Single().Template is CheckOffscreenObjects.IssueTemplateOffscreenSlider);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
115
osu.Game.Rulesets.Osu/Edit/Checks/CheckOffscreenObjects.cs
Normal file
115
osu.Game.Rulesets.Osu/Edit/Checks/CheckOffscreenObjects.cs
Normal file
@ -0,0 +1,115 @@
|
|||||||
|
// 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 osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Rulesets.Edit.Checks.Components;
|
||||||
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Osu.Edit.Checks
|
||||||
|
{
|
||||||
|
public class CheckOffscreenObjects : ICheck
|
||||||
|
{
|
||||||
|
// A close approximation for the bounding box of the screen in gameplay on 4:3 aspect ratio.
|
||||||
|
// Uses gameplay space coordinates (512 x 384 playfield / 640 x 480 screen area).
|
||||||
|
// See https://github.com/ppy/osu/pull/12361#discussion_r612199777 for reference.
|
||||||
|
private const int min_x = -67;
|
||||||
|
private const int min_y = -60;
|
||||||
|
private const int max_x = 579;
|
||||||
|
private const int max_y = 428;
|
||||||
|
|
||||||
|
// The amount of milliseconds to step through a slider path at a time
|
||||||
|
// (higher = more performant, but higher false-negative chance).
|
||||||
|
private const int path_step_size = 5;
|
||||||
|
|
||||||
|
public CheckMetadata Metadata { get; } = new CheckMetadata(CheckCategory.Compose, "Offscreen hitobjects");
|
||||||
|
|
||||||
|
public IEnumerable<IssueTemplate> PossibleTemplates => new IssueTemplate[]
|
||||||
|
{
|
||||||
|
new IssueTemplateOffscreenCircle(this),
|
||||||
|
new IssueTemplateOffscreenSlider(this)
|
||||||
|
};
|
||||||
|
|
||||||
|
public IEnumerable<Issue> Run(IBeatmap beatmap)
|
||||||
|
{
|
||||||
|
foreach (var hitobject in beatmap.HitObjects)
|
||||||
|
{
|
||||||
|
switch (hitobject)
|
||||||
|
{
|
||||||
|
case Slider slider:
|
||||||
|
{
|
||||||
|
foreach (var issue in sliderIssues(slider))
|
||||||
|
yield return issue;
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case HitCircle circle:
|
||||||
|
{
|
||||||
|
if (isOffscreen(circle.StackedPosition, circle.Radius))
|
||||||
|
yield return new IssueTemplateOffscreenCircle(this).Create(circle);
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Steps through points on the slider to ensure the entire path is on-screen.
|
||||||
|
/// Returns at most one issue.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="slider">The slider whose path to check.</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
private IEnumerable<Issue> sliderIssues(Slider slider)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < slider.Distance; i += path_step_size)
|
||||||
|
{
|
||||||
|
double progress = i / slider.Distance;
|
||||||
|
Vector2 position = slider.StackedPositionAt(progress);
|
||||||
|
|
||||||
|
if (!isOffscreen(position, slider.Radius))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// `SpanDuration` ensures we don't include reverses.
|
||||||
|
double time = slider.StartTime + progress * slider.SpanDuration;
|
||||||
|
yield return new IssueTemplateOffscreenSlider(this).Create(slider, time);
|
||||||
|
|
||||||
|
yield break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Above loop may skip the last position in the slider due to step size.
|
||||||
|
if (!isOffscreen(slider.StackedEndPosition, slider.Radius))
|
||||||
|
yield break;
|
||||||
|
|
||||||
|
yield return new IssueTemplateOffscreenSlider(this).Create(slider, slider.EndTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool isOffscreen(Vector2 position, double radius)
|
||||||
|
{
|
||||||
|
return position.X - radius < min_x || position.X + radius > max_x ||
|
||||||
|
position.Y - radius < min_y || position.Y + radius > max_y;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class IssueTemplateOffscreenCircle : IssueTemplate
|
||||||
|
{
|
||||||
|
public IssueTemplateOffscreenCircle(ICheck check)
|
||||||
|
: base(check, IssueType.Problem, "This circle goes offscreen on a 4:3 aspect ratio.")
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public Issue Create(HitCircle circle) => new Issue(circle, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public class IssueTemplateOffscreenSlider : IssueTemplate
|
||||||
|
{
|
||||||
|
public IssueTemplateOffscreenSlider(ICheck check)
|
||||||
|
: base(check, IssueType.Problem, "This slider goes offscreen here on a 4:3 aspect ratio.")
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public Issue Create(Slider slider, double offscreenTime) => new Issue(slider, this) { Time = offscreenTime };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
22
osu.Game.Rulesets.Osu/Edit/OsuBeatmapVerifier.cs
Normal file
22
osu.Game.Rulesets.Osu/Edit/OsuBeatmapVerifier.cs
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
// 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 osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Rulesets.Edit;
|
||||||
|
using osu.Game.Rulesets.Edit.Checks.Components;
|
||||||
|
using osu.Game.Rulesets.Osu.Edit.Checks;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Osu.Edit
|
||||||
|
{
|
||||||
|
public class OsuBeatmapVerifier : IBeatmapVerifier
|
||||||
|
{
|
||||||
|
private readonly List<ICheck> checks = new List<ICheck>
|
||||||
|
{
|
||||||
|
new CheckOffscreenObjects()
|
||||||
|
};
|
||||||
|
|
||||||
|
public IEnumerable<Issue> Run(IBeatmap beatmap) => checks.SelectMany(check => check.Run(beatmap));
|
||||||
|
}
|
||||||
|
}
|
@ -206,6 +206,8 @@ namespace osu.Game.Rulesets.Osu
|
|||||||
|
|
||||||
public override HitObjectComposer CreateHitObjectComposer() => new OsuHitObjectComposer(this);
|
public override HitObjectComposer CreateHitObjectComposer() => new OsuHitObjectComposer(this);
|
||||||
|
|
||||||
|
public override IBeatmapVerifier CreateBeatmapVerifier() => new OsuBeatmapVerifier();
|
||||||
|
|
||||||
public override string Description => "osu!";
|
public override string Description => "osu!";
|
||||||
|
|
||||||
public override string ShortName => SHORT_NAME;
|
public override string ShortName => SHORT_NAME;
|
||||||
|
67
osu.Game.Tests/Editing/Checks/CheckBackgroundTest.cs
Normal file
67
osu.Game.Tests/Editing/Checks/CheckBackgroundTest.cs
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
// 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.Game.Beatmaps;
|
||||||
|
using osu.Game.Rulesets.Edit.Checks;
|
||||||
|
using osu.Game.Rulesets.Objects;
|
||||||
|
|
||||||
|
namespace osu.Game.Tests.Editing.Checks
|
||||||
|
{
|
||||||
|
[TestFixture]
|
||||||
|
public class CheckBackgroundTest
|
||||||
|
{
|
||||||
|
private CheckBackground check;
|
||||||
|
private IBeatmap beatmap;
|
||||||
|
|
||||||
|
[SetUp]
|
||||||
|
public void Setup()
|
||||||
|
{
|
||||||
|
check = new CheckBackground();
|
||||||
|
beatmap = new Beatmap<HitObject>
|
||||||
|
{
|
||||||
|
BeatmapInfo = new BeatmapInfo
|
||||||
|
{
|
||||||
|
Metadata = new BeatmapMetadata { BackgroundFile = "abc123.jpg" },
|
||||||
|
BeatmapSet = new BeatmapSetInfo
|
||||||
|
{
|
||||||
|
Files = new List<BeatmapSetFileInfo>(new[]
|
||||||
|
{
|
||||||
|
new BeatmapSetFileInfo { Filename = "abc123.jpg" }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestBackgroundSetAndInFiles()
|
||||||
|
{
|
||||||
|
Assert.That(check.Run(beatmap), Is.Empty);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestBackgroundSetAndNotInFiles()
|
||||||
|
{
|
||||||
|
beatmap.BeatmapInfo.BeatmapSet.Files.Clear();
|
||||||
|
|
||||||
|
var issues = check.Run(beatmap).ToList();
|
||||||
|
|
||||||
|
Assert.That(issues, Has.Count.EqualTo(1));
|
||||||
|
Assert.That(issues.Single().Template is CheckBackground.IssueTemplateDoesNotExist);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestBackgroundNotSet()
|
||||||
|
{
|
||||||
|
beatmap.Metadata.BackgroundFile = null;
|
||||||
|
|
||||||
|
var issues = check.Run(beatmap).ToList();
|
||||||
|
|
||||||
|
Assert.That(issues, Has.Count.EqualTo(1));
|
||||||
|
Assert.That(issues.Single().Template is CheckBackground.IssueTemplateNoneSet);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -70,6 +70,7 @@ namespace osu.Game.Input.Bindings
|
|||||||
new KeyBinding(new[] { InputKey.F2 }, GlobalAction.EditorDesignMode),
|
new KeyBinding(new[] { InputKey.F2 }, GlobalAction.EditorDesignMode),
|
||||||
new KeyBinding(new[] { InputKey.F3 }, GlobalAction.EditorTimingMode),
|
new KeyBinding(new[] { InputKey.F3 }, GlobalAction.EditorTimingMode),
|
||||||
new KeyBinding(new[] { InputKey.F4 }, GlobalAction.EditorSetupMode),
|
new KeyBinding(new[] { InputKey.F4 }, GlobalAction.EditorSetupMode),
|
||||||
|
new KeyBinding(new[] { InputKey.Control, InputKey.Shift, InputKey.A }, GlobalAction.EditorVerifyMode),
|
||||||
};
|
};
|
||||||
|
|
||||||
public IEnumerable<KeyBinding> InGameKeyBindings => new[]
|
public IEnumerable<KeyBinding> InGameKeyBindings => new[]
|
||||||
@ -247,5 +248,8 @@ namespace osu.Game.Input.Bindings
|
|||||||
|
|
||||||
[Description("Beatmap Options")]
|
[Description("Beatmap Options")]
|
||||||
ToggleBeatmapOptions,
|
ToggleBeatmapOptions,
|
||||||
|
|
||||||
|
[Description("Verify mode")]
|
||||||
|
EditorVerifyMode,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
24
osu.Game/Rulesets/Edit/BeatmapVerifier.cs
Normal file
24
osu.Game/Rulesets/Edit/BeatmapVerifier.cs
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
// 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 osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Rulesets.Edit.Checks;
|
||||||
|
using osu.Game.Rulesets.Edit.Checks.Components;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Edit
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A ruleset-agnostic beatmap verifier that identifies issues in common metadata or mapping standards.
|
||||||
|
/// </summary>
|
||||||
|
public class BeatmapVerifier : IBeatmapVerifier
|
||||||
|
{
|
||||||
|
private readonly List<ICheck> checks = new List<ICheck>
|
||||||
|
{
|
||||||
|
new CheckBackground(),
|
||||||
|
};
|
||||||
|
|
||||||
|
public IEnumerable<Issue> Run(IBeatmap beatmap) => checks.SelectMany(check => check.Run(beatmap));
|
||||||
|
}
|
||||||
|
}
|
61
osu.Game/Rulesets/Edit/Checks/CheckBackground.cs
Normal file
61
osu.Game/Rulesets/Edit/Checks/CheckBackground.cs
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
// 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 osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Rulesets.Edit.Checks.Components;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Edit.Checks
|
||||||
|
{
|
||||||
|
public class CheckBackground : ICheck
|
||||||
|
{
|
||||||
|
public CheckMetadata Metadata { get; } = new CheckMetadata(CheckCategory.Resources, "Missing background");
|
||||||
|
|
||||||
|
public IEnumerable<IssueTemplate> PossibleTemplates => new IssueTemplate[]
|
||||||
|
{
|
||||||
|
new IssueTemplateNoneSet(this),
|
||||||
|
new IssueTemplateDoesNotExist(this)
|
||||||
|
};
|
||||||
|
|
||||||
|
public IEnumerable<Issue> Run(IBeatmap beatmap)
|
||||||
|
{
|
||||||
|
if (beatmap.Metadata.BackgroundFile == null)
|
||||||
|
{
|
||||||
|
yield return new IssueTemplateNoneSet(this).Create();
|
||||||
|
|
||||||
|
yield break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the background is set, also make sure it still exists.
|
||||||
|
|
||||||
|
var set = beatmap.BeatmapInfo.BeatmapSet;
|
||||||
|
var file = set.Files.FirstOrDefault(f => f.Filename == beatmap.Metadata.BackgroundFile);
|
||||||
|
|
||||||
|
if (file != null)
|
||||||
|
yield break;
|
||||||
|
|
||||||
|
yield return new IssueTemplateDoesNotExist(this).Create(beatmap.Metadata.BackgroundFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
public class IssueTemplateNoneSet : IssueTemplate
|
||||||
|
{
|
||||||
|
public IssueTemplateNoneSet(ICheck check)
|
||||||
|
: base(check, IssueType.Problem, "No background has been set.")
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public Issue Create() => new Issue(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public class IssueTemplateDoesNotExist : IssueTemplate
|
||||||
|
{
|
||||||
|
public IssueTemplateDoesNotExist(ICheck check)
|
||||||
|
: base(check, IssueType.Problem, "The background file \"{0}\" does not exist.")
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public Issue Create(string filename) => new Issue(this, filename);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
61
osu.Game/Rulesets/Edit/Checks/Components/CheckCategory.cs
Normal file
61
osu.Game/Rulesets/Edit/Checks/Components/CheckCategory.cs
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
// 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.
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Edit.Checks.Components
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The category of an issue.
|
||||||
|
/// </summary>
|
||||||
|
public enum CheckCategory
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Anything to do with control points.
|
||||||
|
/// </summary>
|
||||||
|
Timing,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Anything to do with artist, title, creator, etc.
|
||||||
|
/// </summary>
|
||||||
|
Metadata,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Anything to do with non-audio files, e.g. background, skin, sprites, and video.
|
||||||
|
/// </summary>
|
||||||
|
Resources,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Anything to do with audio files, e.g. song and hitsounds.
|
||||||
|
/// </summary>
|
||||||
|
Audio,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Anything to do with files that don't fit into the above, e.g. unused, osu, or osb.
|
||||||
|
/// </summary>
|
||||||
|
Files,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Anything to do with hitobjects unrelated to spread.
|
||||||
|
/// </summary>
|
||||||
|
Compose,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Anything to do with difficulty levels or their progression.
|
||||||
|
/// </summary>
|
||||||
|
Spread,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Anything to do with variables like CS, OD, AR, HP, and global SV.
|
||||||
|
/// </summary>
|
||||||
|
Settings,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Anything to do with hitobject feedback.
|
||||||
|
/// </summary>
|
||||||
|
HitObjects,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Anything to do with storyboarding, breaks, video offset, etc.
|
||||||
|
/// </summary>
|
||||||
|
Events
|
||||||
|
}
|
||||||
|
}
|
24
osu.Game/Rulesets/Edit/Checks/Components/CheckMetadata.cs
Normal file
24
osu.Game/Rulesets/Edit/Checks/Components/CheckMetadata.cs
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
// 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.
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Edit.Checks.Components
|
||||||
|
{
|
||||||
|
public class CheckMetadata
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The category this check belongs to. E.g. <see cref="CheckCategory.Metadata"/>, <see cref="CheckCategory.Timing"/>, or <see cref="CheckCategory.Compose"/>.
|
||||||
|
/// </summary>
|
||||||
|
public readonly CheckCategory Category;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Describes the issue(s) that this check looks for. Keep this brief, such that it fits into "No {description}". E.g. "Offscreen objects" / "Too short sliders".
|
||||||
|
/// </summary>
|
||||||
|
public readonly string Description;
|
||||||
|
|
||||||
|
public CheckMetadata(CheckCategory category, string description)
|
||||||
|
{
|
||||||
|
Category = category;
|
||||||
|
Description = description;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
30
osu.Game/Rulesets/Edit/Checks/Components/ICheck.cs
Normal file
30
osu.Game/Rulesets/Edit/Checks/Components/ICheck.cs
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
// 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 osu.Game.Beatmaps;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Edit.Checks.Components
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A specific check that can be run on a beatmap to verify or find issues.
|
||||||
|
/// </summary>
|
||||||
|
public interface ICheck
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The metadata for this check.
|
||||||
|
/// </summary>
|
||||||
|
public CheckMetadata Metadata { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// All possible templates for issues that this check may return.
|
||||||
|
/// </summary>
|
||||||
|
public IEnumerable<IssueTemplate> PossibleTemplates { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Runs this check and returns any issues detected for the provided beatmap.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="beatmap">The beatmap to run the check on.</param>
|
||||||
|
public IEnumerable<Issue> Run(IBeatmap beatmap);
|
||||||
|
}
|
||||||
|
}
|
77
osu.Game/Rulesets/Edit/Checks/Components/Issue.cs
Normal file
77
osu.Game/Rulesets/Edit/Checks/Components/Issue.cs
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
// 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.Linq;
|
||||||
|
using osu.Game.Extensions;
|
||||||
|
using osu.Game.Rulesets.Objects;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Edit.Checks.Components
|
||||||
|
{
|
||||||
|
public class Issue
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The time which this issue is associated with, if any, otherwise null.
|
||||||
|
/// </summary>
|
||||||
|
public double? Time;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The hitobjects which this issue is associated with. Empty by default.
|
||||||
|
/// </summary>
|
||||||
|
public IReadOnlyList<HitObject> HitObjects;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The template which this issue is using. This provides properties such as the <see cref="IssueType"/>, and the <see cref="IssueTemplate.UnformattedMessage"/>.
|
||||||
|
/// </summary>
|
||||||
|
public IssueTemplate Template;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The check that this issue originates from.
|
||||||
|
/// </summary>
|
||||||
|
public ICheck Check => Template.Check;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The arguments that give this issue its context, based on the <see cref="IssueTemplate"/>. These are then substituted into the <see cref="IssueTemplate.UnformattedMessage"/>.
|
||||||
|
/// This could for instance include timestamps, which diff is being compared to, what some volume is, etc.
|
||||||
|
/// </summary>
|
||||||
|
public object[] Arguments;
|
||||||
|
|
||||||
|
public Issue(IssueTemplate template, params object[] args)
|
||||||
|
{
|
||||||
|
Time = null;
|
||||||
|
HitObjects = Array.Empty<HitObject>();
|
||||||
|
Template = template;
|
||||||
|
Arguments = args;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Issue(double? time, IssueTemplate template, params object[] args)
|
||||||
|
: this(template, args)
|
||||||
|
{
|
||||||
|
Time = time;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Issue(HitObject hitObject, IssueTemplate template, params object[] args)
|
||||||
|
: this(template, args)
|
||||||
|
{
|
||||||
|
Time = hitObject.StartTime;
|
||||||
|
HitObjects = new[] { hitObject };
|
||||||
|
}
|
||||||
|
|
||||||
|
public Issue(IEnumerable<HitObject> hitObjects, IssueTemplate template, params object[] args)
|
||||||
|
: this(template, args)
|
||||||
|
{
|
||||||
|
var hitObjectList = hitObjects.ToList();
|
||||||
|
|
||||||
|
Time = hitObjectList.FirstOrDefault()?.StartTime;
|
||||||
|
HitObjects = hitObjectList;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string ToString() => Template.GetMessage(Arguments);
|
||||||
|
|
||||||
|
public string GetEditorTimestamp()
|
||||||
|
{
|
||||||
|
return Time == null ? string.Empty : Time.Value.ToEditorFormattedString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
74
osu.Game/Rulesets/Edit/Checks/Components/IssueTemplate.cs
Normal file
74
osu.Game/Rulesets/Edit/Checks/Components/IssueTemplate.cs
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
// 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 Humanizer;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osuTK.Graphics;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Edit.Checks.Components
|
||||||
|
{
|
||||||
|
public class IssueTemplate
|
||||||
|
{
|
||||||
|
private static readonly Color4 problem_red = new Colour4(1.0f, 0.4f, 0.4f, 1.0f);
|
||||||
|
private static readonly Color4 warning_yellow = new Colour4(1.0f, 0.8f, 0.2f, 1.0f);
|
||||||
|
private static readonly Color4 negligible_green = new Colour4(0.33f, 0.8f, 0.5f, 1.0f);
|
||||||
|
private static readonly Color4 error_gray = new Colour4(0.5f, 0.5f, 0.5f, 1.0f);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The check that this template originates from.
|
||||||
|
/// </summary>
|
||||||
|
public readonly ICheck Check;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The type of the issue.
|
||||||
|
/// </summary>
|
||||||
|
public readonly IssueType Type;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The unformatted message given when this issue is detected.
|
||||||
|
/// This gets populated later when an issue is constructed with this template.
|
||||||
|
/// E.g. "Inconsistent snapping (1/{0}) with [{1}] (1/{2})."
|
||||||
|
/// </summary>
|
||||||
|
public readonly string UnformattedMessage;
|
||||||
|
|
||||||
|
public IssueTemplate(ICheck check, IssueType type, string unformattedMessage)
|
||||||
|
{
|
||||||
|
Check = check;
|
||||||
|
Type = type;
|
||||||
|
UnformattedMessage = unformattedMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns the formatted message given the arguments used to format it.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="args">The arguments used to format the message.</param>
|
||||||
|
public string GetMessage(params object[] args) => UnformattedMessage.FormatWith(args);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns the colour corresponding to the type of this issue.
|
||||||
|
/// </summary>
|
||||||
|
public Colour4 Colour
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
switch (Type)
|
||||||
|
{
|
||||||
|
case IssueType.Problem:
|
||||||
|
return problem_red;
|
||||||
|
|
||||||
|
case IssueType.Warning:
|
||||||
|
return warning_yellow;
|
||||||
|
|
||||||
|
case IssueType.Negligible:
|
||||||
|
return negligible_green;
|
||||||
|
|
||||||
|
case IssueType.Error:
|
||||||
|
return error_gray;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return Color4.White;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
25
osu.Game/Rulesets/Edit/Checks/Components/IssueType.cs
Normal file
25
osu.Game/Rulesets/Edit/Checks/Components/IssueType.cs
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
// 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.
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Edit.Checks.Components
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The type, or severity, of an issue.
|
||||||
|
/// </summary>
|
||||||
|
public enum IssueType
|
||||||
|
{
|
||||||
|
/// <summary> A must-fix in the vast majority of cases. </summary>
|
||||||
|
Problem,
|
||||||
|
|
||||||
|
/// <summary> A possible mistake. Often requires critical thinking. </summary>
|
||||||
|
Warning,
|
||||||
|
|
||||||
|
// TODO: Try/catch all checks run and return error templates if exceptions occur.
|
||||||
|
/// <summary> An error occurred and a complete check could not be made. </summary>
|
||||||
|
Error,
|
||||||
|
|
||||||
|
// TODO: Negligible issues should be hidden by default.
|
||||||
|
/// <summary> A possible mistake so minor/unlikely that it can often be safely ignored. </summary>
|
||||||
|
Negligible,
|
||||||
|
}
|
||||||
|
}
|
17
osu.Game/Rulesets/Edit/IBeatmapVerifier.cs
Normal file
17
osu.Game/Rulesets/Edit/IBeatmapVerifier.cs
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
// 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 osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Rulesets.Edit.Checks.Components;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Edit
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A class which can run against a beatmap and surface issues to the user which could go against known criteria or hinder gameplay.
|
||||||
|
/// </summary>
|
||||||
|
public interface IBeatmapVerifier
|
||||||
|
{
|
||||||
|
public IEnumerable<Issue> Run(IBeatmap beatmap);
|
||||||
|
}
|
||||||
|
}
|
@ -201,6 +201,8 @@ namespace osu.Game.Rulesets
|
|||||||
|
|
||||||
public virtual HitObjectComposer CreateHitObjectComposer() => null;
|
public virtual HitObjectComposer CreateHitObjectComposer() => null;
|
||||||
|
|
||||||
|
public virtual IBeatmapVerifier CreateBeatmapVerifier() => null;
|
||||||
|
|
||||||
public virtual Drawable CreateIcon() => new SpriteIcon { Icon = FontAwesome.Solid.QuestionCircle };
|
public virtual Drawable CreateIcon() => new SpriteIcon { Icon = FontAwesome.Solid.QuestionCircle };
|
||||||
|
|
||||||
public virtual IResourceStore<byte[]> CreateResourceStore() => new NamespacedResourceStore<byte[]>(new DllResourceStore(GetType().Assembly), @"Resources");
|
public virtual IResourceStore<byte[]> CreateResourceStore() => new NamespacedResourceStore<byte[]>(new DllResourceStore(GetType().Assembly), @"Resources");
|
||||||
|
@ -35,6 +35,7 @@ using osu.Game.Screens.Edit.Compose;
|
|||||||
using osu.Game.Screens.Edit.Design;
|
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.Edit.Verify;
|
||||||
using osu.Game.Screens.Play;
|
using osu.Game.Screens.Play;
|
||||||
using osu.Game.Users;
|
using osu.Game.Users;
|
||||||
using osuTK.Graphics;
|
using osuTK.Graphics;
|
||||||
@ -444,6 +445,10 @@ namespace osu.Game.Screens.Edit
|
|||||||
menuBar.Mode.Value = EditorScreenMode.SongSetup;
|
menuBar.Mode.Value = EditorScreenMode.SongSetup;
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
|
case GlobalAction.EditorVerifyMode:
|
||||||
|
menuBar.Mode.Value = EditorScreenMode.Verify;
|
||||||
|
return true;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -631,6 +636,10 @@ namespace osu.Game.Screens.Edit
|
|||||||
case EditorScreenMode.Timing:
|
case EditorScreenMode.Timing:
|
||||||
currentScreen = new TimingScreen();
|
currentScreen = new TimingScreen();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case EditorScreenMode.Verify:
|
||||||
|
currentScreen = new VerifyScreen();
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
LoadComponentAsync(currentScreen, newScreen =>
|
LoadComponentAsync(currentScreen, newScreen =>
|
||||||
|
@ -18,5 +18,8 @@ namespace osu.Game.Screens.Edit
|
|||||||
|
|
||||||
[Description("timing")]
|
[Description("timing")]
|
||||||
Timing,
|
Timing,
|
||||||
|
|
||||||
|
[Description("verify")]
|
||||||
|
Verify,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
140
osu.Game/Screens/Edit/EditorTable.cs
Normal file
140
osu.Game/Screens/Edit/EditorTable.cs
Normal file
@ -0,0 +1,140 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Shapes;
|
||||||
|
using osu.Framework.Input.Events;
|
||||||
|
using osu.Game.Graphics;
|
||||||
|
using osu.Game.Graphics.Containers;
|
||||||
|
using osu.Game.Graphics.Sprites;
|
||||||
|
using osuTK.Graphics;
|
||||||
|
|
||||||
|
namespace osu.Game.Screens.Edit
|
||||||
|
{
|
||||||
|
public abstract class EditorTable : TableContainer
|
||||||
|
{
|
||||||
|
private const float horizontal_inset = 20;
|
||||||
|
|
||||||
|
protected const float ROW_HEIGHT = 25;
|
||||||
|
|
||||||
|
protected const int TEXT_SIZE = 14;
|
||||||
|
|
||||||
|
protected readonly FillFlowContainer<RowBackground> BackgroundFlow;
|
||||||
|
|
||||||
|
protected EditorTable()
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.X;
|
||||||
|
AutoSizeAxes = Axes.Y;
|
||||||
|
|
||||||
|
Padding = new MarginPadding { Horizontal = horizontal_inset };
|
||||||
|
RowSize = new Dimension(GridSizeMode.Absolute, ROW_HEIGHT);
|
||||||
|
|
||||||
|
AddInternal(BackgroundFlow = new FillFlowContainer<RowBackground>
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Depth = 1f,
|
||||||
|
Padding = new MarginPadding { Horizontal = -horizontal_inset },
|
||||||
|
Margin = new MarginPadding { Top = ROW_HEIGHT }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override Drawable CreateHeader(int index, TableColumn column) => new HeaderText(column?.Header ?? string.Empty);
|
||||||
|
|
||||||
|
private class HeaderText : OsuSpriteText
|
||||||
|
{
|
||||||
|
public HeaderText(string text)
|
||||||
|
{
|
||||||
|
Text = text.ToUpper();
|
||||||
|
Font = OsuFont.GetFont(size: 12, weight: FontWeight.Bold);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class RowBackground : OsuClickableContainer
|
||||||
|
{
|
||||||
|
public readonly object Item;
|
||||||
|
|
||||||
|
private const int fade_duration = 100;
|
||||||
|
|
||||||
|
private readonly Box hoveredBackground;
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private EditorClock clock { get; set; }
|
||||||
|
|
||||||
|
public RowBackground(object item)
|
||||||
|
{
|
||||||
|
Item = item;
|
||||||
|
|
||||||
|
RelativeSizeAxes = Axes.X;
|
||||||
|
Height = 25;
|
||||||
|
|
||||||
|
AlwaysPresent = true;
|
||||||
|
|
||||||
|
CornerRadius = 3;
|
||||||
|
Masking = true;
|
||||||
|
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
hoveredBackground = new Box
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Alpha = 0,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// todo delete
|
||||||
|
Action = () =>
|
||||||
|
{
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private Color4 colourHover;
|
||||||
|
private Color4 colourSelected;
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(OsuColour colours)
|
||||||
|
{
|
||||||
|
hoveredBackground.Colour = colourHover = colours.BlueDarker;
|
||||||
|
colourSelected = colours.YellowDarker;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool selected;
|
||||||
|
|
||||||
|
public bool Selected
|
||||||
|
{
|
||||||
|
get => selected;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (value == selected)
|
||||||
|
return;
|
||||||
|
|
||||||
|
selected = value;
|
||||||
|
updateState();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override bool OnHover(HoverEvent e)
|
||||||
|
{
|
||||||
|
updateState();
|
||||||
|
return base.OnHover(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnHoverLost(HoverLostEvent e)
|
||||||
|
{
|
||||||
|
updateState();
|
||||||
|
base.OnHoverLost(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateState()
|
||||||
|
{
|
||||||
|
hoveredBackground.FadeColour(selected ? colourSelected : colourHover, 450, Easing.OutQuint);
|
||||||
|
|
||||||
|
if (selected || IsHovered)
|
||||||
|
hoveredBackground.FadeIn(fade_duration, Easing.OutQuint);
|
||||||
|
else
|
||||||
|
hoveredBackground.FadeOut(fade_duration, Easing.OutQuint);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -8,59 +8,43 @@ 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;
|
||||||
using osu.Framework.Graphics.Shapes;
|
|
||||||
using osu.Framework.Input.Events;
|
|
||||||
using osu.Game.Beatmaps.ControlPoints;
|
using osu.Game.Beatmaps.ControlPoints;
|
||||||
using osu.Game.Extensions;
|
using osu.Game.Extensions;
|
||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Graphics.Containers;
|
|
||||||
using osu.Game.Graphics.Sprites;
|
using osu.Game.Graphics.Sprites;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
using osuTK.Graphics;
|
using osuTK.Graphics;
|
||||||
|
|
||||||
namespace osu.Game.Screens.Edit.Timing
|
namespace osu.Game.Screens.Edit.Timing
|
||||||
{
|
{
|
||||||
public class ControlPointTable : TableContainer
|
public class ControlPointTable : EditorTable
|
||||||
{
|
{
|
||||||
private const float horizontal_inset = 20;
|
|
||||||
private const float row_height = 25;
|
|
||||||
private const int text_size = 14;
|
|
||||||
|
|
||||||
private readonly FillFlowContainer backgroundFlow;
|
|
||||||
|
|
||||||
[Resolved]
|
[Resolved]
|
||||||
private Bindable<ControlPointGroup> selectedGroup { get; set; }
|
private Bindable<ControlPointGroup> selectedGroup { get; set; }
|
||||||
|
|
||||||
public ControlPointTable()
|
[Resolved]
|
||||||
{
|
private EditorClock clock { get; set; }
|
||||||
RelativeSizeAxes = Axes.X;
|
|
||||||
AutoSizeAxes = Axes.Y;
|
|
||||||
|
|
||||||
Padding = new MarginPadding { Horizontal = horizontal_inset };
|
|
||||||
RowSize = new Dimension(GridSizeMode.Absolute, row_height);
|
|
||||||
|
|
||||||
AddInternal(backgroundFlow = new FillFlowContainer
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
Depth = 1f,
|
|
||||||
Padding = new MarginPadding { Horizontal = -horizontal_inset },
|
|
||||||
Margin = new MarginPadding { Top = row_height }
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public IEnumerable<ControlPointGroup> ControlGroups
|
public IEnumerable<ControlPointGroup> ControlGroups
|
||||||
{
|
{
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
Content = null;
|
Content = null;
|
||||||
backgroundFlow.Clear();
|
BackgroundFlow.Clear();
|
||||||
|
|
||||||
if (value?.Any() != true)
|
if (value?.Any() != true)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
foreach (var group in value)
|
foreach (var group in value)
|
||||||
{
|
{
|
||||||
backgroundFlow.Add(new RowBackground(group));
|
BackgroundFlow.Add(new RowBackground(group)
|
||||||
|
{
|
||||||
|
Action = () =>
|
||||||
|
{
|
||||||
|
selectedGroup.Value = group;
|
||||||
|
clock.SeekSmoothlyTo(group.Time);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Columns = createHeaders();
|
Columns = createHeaders();
|
||||||
@ -68,6 +52,16 @@ namespace osu.Game.Screens.Edit.Timing
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
|
||||||
|
selectedGroup.BindValueChanged(group =>
|
||||||
|
{
|
||||||
|
foreach (var b in BackgroundFlow) b.Selected = b.Item == group.NewValue;
|
||||||
|
}, true);
|
||||||
|
}
|
||||||
|
|
||||||
private TableColumn[] createHeaders()
|
private TableColumn[] createHeaders()
|
||||||
{
|
{
|
||||||
var columns = new List<TableColumn>
|
var columns = new List<TableColumn>
|
||||||
@ -86,13 +80,13 @@ namespace osu.Game.Screens.Edit.Timing
|
|||||||
new OsuSpriteText
|
new OsuSpriteText
|
||||||
{
|
{
|
||||||
Text = $"#{index + 1}",
|
Text = $"#{index + 1}",
|
||||||
Font = OsuFont.GetFont(size: text_size, weight: FontWeight.Bold),
|
Font = OsuFont.GetFont(size: TEXT_SIZE, weight: FontWeight.Bold),
|
||||||
Margin = new MarginPadding(10)
|
Margin = new MarginPadding(10)
|
||||||
},
|
},
|
||||||
new OsuSpriteText
|
new OsuSpriteText
|
||||||
{
|
{
|
||||||
Text = group.Time.ToEditorFormattedString(),
|
Text = group.Time.ToEditorFormattedString(),
|
||||||
Font = OsuFont.GetFont(size: text_size, weight: FontWeight.Bold)
|
Font = OsuFont.GetFont(size: TEXT_SIZE, weight: FontWeight.Bold)
|
||||||
},
|
},
|
||||||
null,
|
null,
|
||||||
new ControlGroupAttributes(group),
|
new ControlGroupAttributes(group),
|
||||||
@ -163,111 +157,5 @@ namespace osu.Game.Screens.Edit.Timing
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override Drawable CreateHeader(int index, TableColumn column) => new HeaderText(column?.Header ?? string.Empty);
|
|
||||||
|
|
||||||
private class HeaderText : OsuSpriteText
|
|
||||||
{
|
|
||||||
public HeaderText(string text)
|
|
||||||
{
|
|
||||||
Text = text.ToUpper();
|
|
||||||
Font = OsuFont.GetFont(size: 12, weight: FontWeight.Bold);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class RowBackground : OsuClickableContainer
|
|
||||||
{
|
|
||||||
private readonly ControlPointGroup controlGroup;
|
|
||||||
private const int fade_duration = 100;
|
|
||||||
|
|
||||||
private readonly Box hoveredBackground;
|
|
||||||
|
|
||||||
[Resolved]
|
|
||||||
private EditorClock clock { get; set; }
|
|
||||||
|
|
||||||
[Resolved]
|
|
||||||
private Bindable<ControlPointGroup> selectedGroup { get; set; }
|
|
||||||
|
|
||||||
public RowBackground(ControlPointGroup controlGroup)
|
|
||||||
{
|
|
||||||
this.controlGroup = controlGroup;
|
|
||||||
RelativeSizeAxes = Axes.X;
|
|
||||||
Height = 25;
|
|
||||||
|
|
||||||
AlwaysPresent = true;
|
|
||||||
|
|
||||||
CornerRadius = 3;
|
|
||||||
Masking = true;
|
|
||||||
|
|
||||||
Children = new Drawable[]
|
|
||||||
{
|
|
||||||
hoveredBackground = new Box
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
Alpha = 0,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
Action = () =>
|
|
||||||
{
|
|
||||||
selectedGroup.Value = controlGroup;
|
|
||||||
clock.SeekSmoothlyTo(controlGroup.Time);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private Color4 colourHover;
|
|
||||||
private Color4 colourSelected;
|
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
|
||||||
private void load(OsuColour colours)
|
|
||||||
{
|
|
||||||
hoveredBackground.Colour = colourHover = colours.BlueDarker;
|
|
||||||
colourSelected = colours.YellowDarker;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void LoadComplete()
|
|
||||||
{
|
|
||||||
base.LoadComplete();
|
|
||||||
|
|
||||||
selectedGroup.BindValueChanged(group => { Selected = controlGroup == group.NewValue; }, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool selected;
|
|
||||||
|
|
||||||
protected bool Selected
|
|
||||||
{
|
|
||||||
get => selected;
|
|
||||||
set
|
|
||||||
{
|
|
||||||
if (value == selected)
|
|
||||||
return;
|
|
||||||
|
|
||||||
selected = value;
|
|
||||||
updateState();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override bool OnHover(HoverEvent e)
|
|
||||||
{
|
|
||||||
updateState();
|
|
||||||
return base.OnHover(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void OnHoverLost(HoverLostEvent e)
|
|
||||||
{
|
|
||||||
updateState();
|
|
||||||
base.OnHoverLost(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateState()
|
|
||||||
{
|
|
||||||
hoveredBackground.FadeColour(selected ? colourSelected : colourHover, 450, Easing.OutQuint);
|
|
||||||
|
|
||||||
if (selected || IsHovered)
|
|
||||||
hoveredBackground.FadeIn(fade_duration, Easing.OutQuint);
|
|
||||||
else
|
|
||||||
hoveredBackground.FadeOut(fade_duration, Easing.OutQuint);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
46
osu.Game/Screens/Edit/Verify/IssueSettings.cs
Normal file
46
osu.Game/Screens/Edit/Verify/IssueSettings.cs
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
// 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 osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Shapes;
|
||||||
|
using osu.Game.Graphics;
|
||||||
|
using osu.Game.Graphics.Containers;
|
||||||
|
|
||||||
|
namespace osu.Game.Screens.Edit.Verify
|
||||||
|
{
|
||||||
|
public class IssueSettings : CompositeDrawable
|
||||||
|
{
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(OsuColour colours)
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both;
|
||||||
|
|
||||||
|
InternalChildren = new Drawable[]
|
||||||
|
{
|
||||||
|
new Box
|
||||||
|
{
|
||||||
|
Colour = colours.Gray3,
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
},
|
||||||
|
new OsuScrollContainer
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Child = new FillFlowContainer
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
AutoSizeAxes = Axes.Y,
|
||||||
|
Direction = FillDirection.Vertical,
|
||||||
|
Children = createSections()
|
||||||
|
},
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private IReadOnlyList<Drawable> createSections() => new Drawable[]
|
||||||
|
{
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
128
osu.Game/Screens/Edit/Verify/IssueTable.cs
Normal file
128
osu.Game/Screens/Edit/Verify/IssueTable.cs
Normal file
@ -0,0 +1,128 @@
|
|||||||
|
// 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 osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Extensions;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Game.Graphics;
|
||||||
|
using osu.Game.Graphics.Sprites;
|
||||||
|
using osu.Game.Input.Bindings;
|
||||||
|
using osu.Game.Rulesets.Edit.Checks.Components;
|
||||||
|
|
||||||
|
namespace osu.Game.Screens.Edit.Verify
|
||||||
|
{
|
||||||
|
public class IssueTable : EditorTable
|
||||||
|
{
|
||||||
|
[Resolved]
|
||||||
|
private Bindable<Issue> selectedIssue { get; set; }
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private EditorClock clock { get; set; }
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private EditorBeatmap editorBeatmap { get; set; }
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private Editor editor { get; set; }
|
||||||
|
|
||||||
|
public IEnumerable<Issue> Issues
|
||||||
|
{
|
||||||
|
set
|
||||||
|
{
|
||||||
|
Content = null;
|
||||||
|
BackgroundFlow.Clear();
|
||||||
|
|
||||||
|
if (value == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
foreach (var issue in value)
|
||||||
|
{
|
||||||
|
BackgroundFlow.Add(new RowBackground(issue)
|
||||||
|
{
|
||||||
|
Action = () =>
|
||||||
|
{
|
||||||
|
selectedIssue.Value = issue;
|
||||||
|
|
||||||
|
if (issue.Time != null)
|
||||||
|
{
|
||||||
|
clock.Seek(issue.Time.Value);
|
||||||
|
editor.OnPressed(GlobalAction.EditorComposeMode);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!issue.HitObjects.Any())
|
||||||
|
return;
|
||||||
|
|
||||||
|
editorBeatmap.SelectedHitObjects.Clear();
|
||||||
|
editorBeatmap.SelectedHitObjects.AddRange(issue.HitObjects);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Columns = createHeaders();
|
||||||
|
Content = value.Select((g, i) => createContent(i, g)).ToArray().ToRectangular();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
|
||||||
|
selectedIssue.BindValueChanged(issue =>
|
||||||
|
{
|
||||||
|
foreach (var b in BackgroundFlow) b.Selected = b.Item == issue.NewValue;
|
||||||
|
}, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private TableColumn[] createHeaders()
|
||||||
|
{
|
||||||
|
var columns = new List<TableColumn>
|
||||||
|
{
|
||||||
|
new TableColumn(string.Empty, Anchor.CentreLeft, new Dimension(GridSizeMode.AutoSize)),
|
||||||
|
new TableColumn("Type", Anchor.CentreLeft, new Dimension(GridSizeMode.AutoSize, minSize: 60)),
|
||||||
|
new TableColumn("Time", Anchor.CentreLeft, new Dimension(GridSizeMode.AutoSize, minSize: 60)),
|
||||||
|
new TableColumn("Message", Anchor.CentreLeft),
|
||||||
|
new TableColumn("Category", Anchor.CentreRight, new Dimension(GridSizeMode.AutoSize)),
|
||||||
|
};
|
||||||
|
|
||||||
|
return columns.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Drawable[] createContent(int index, Issue issue) => new Drawable[]
|
||||||
|
{
|
||||||
|
new OsuSpriteText
|
||||||
|
{
|
||||||
|
Text = $"#{index + 1}",
|
||||||
|
Font = OsuFont.GetFont(size: TEXT_SIZE, weight: FontWeight.Medium),
|
||||||
|
Margin = new MarginPadding { Right = 10 }
|
||||||
|
},
|
||||||
|
new OsuSpriteText
|
||||||
|
{
|
||||||
|
Text = issue.Template.Type.ToString(),
|
||||||
|
Font = OsuFont.GetFont(size: TEXT_SIZE, weight: FontWeight.Bold),
|
||||||
|
Margin = new MarginPadding { Right = 10 },
|
||||||
|
Colour = issue.Template.Colour
|
||||||
|
},
|
||||||
|
new OsuSpriteText
|
||||||
|
{
|
||||||
|
Text = issue.GetEditorTimestamp(),
|
||||||
|
Font = OsuFont.GetFont(size: TEXT_SIZE, weight: FontWeight.Bold),
|
||||||
|
Margin = new MarginPadding { Right = 10 },
|
||||||
|
},
|
||||||
|
new OsuSpriteText
|
||||||
|
{
|
||||||
|
Text = issue.ToString(),
|
||||||
|
Font = OsuFont.GetFont(size: TEXT_SIZE, weight: FontWeight.Medium)
|
||||||
|
},
|
||||||
|
new OsuSpriteText
|
||||||
|
{
|
||||||
|
Text = issue.Check.Metadata.Category.ToString(),
|
||||||
|
Font = OsuFont.GetFont(size: TEXT_SIZE, weight: FontWeight.Bold),
|
||||||
|
Margin = new MarginPadding(10)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
133
osu.Game/Screens/Edit/Verify/VerifyScreen.cs
Normal file
133
osu.Game/Screens/Edit/Verify/VerifyScreen.cs
Normal file
@ -0,0 +1,133 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System.Linq;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Shapes;
|
||||||
|
using osu.Game.Graphics;
|
||||||
|
using osu.Game.Graphics.Containers;
|
||||||
|
using osu.Game.Graphics.UserInterface;
|
||||||
|
using osu.Game.Rulesets.Edit;
|
||||||
|
using osu.Game.Rulesets.Edit.Checks.Components;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
|
namespace osu.Game.Screens.Edit.Verify
|
||||||
|
{
|
||||||
|
public class VerifyScreen : EditorScreen
|
||||||
|
{
|
||||||
|
[Cached]
|
||||||
|
private Bindable<Issue> selectedIssue = new Bindable<Issue>();
|
||||||
|
|
||||||
|
public VerifyScreen()
|
||||||
|
: base(EditorScreenMode.Verify)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load()
|
||||||
|
{
|
||||||
|
Child = new Container
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Padding = new MarginPadding(20),
|
||||||
|
Child = new GridContainer
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
ColumnDimensions = new[]
|
||||||
|
{
|
||||||
|
new Dimension(),
|
||||||
|
new Dimension(GridSizeMode.Absolute, 200),
|
||||||
|
},
|
||||||
|
Content = new[]
|
||||||
|
{
|
||||||
|
new Drawable[]
|
||||||
|
{
|
||||||
|
new IssueList(),
|
||||||
|
new IssueSettings(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public class IssueList : CompositeDrawable
|
||||||
|
{
|
||||||
|
private IssueTable table;
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private EditorClock clock { get; set; }
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
protected EditorBeatmap Beatmap { get; private set; }
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private Bindable<Issue> selectedIssue { get; set; }
|
||||||
|
|
||||||
|
private IBeatmapVerifier rulesetVerifier;
|
||||||
|
private BeatmapVerifier generalVerifier;
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(OsuColour colours)
|
||||||
|
{
|
||||||
|
generalVerifier = new BeatmapVerifier();
|
||||||
|
rulesetVerifier = Beatmap.BeatmapInfo.Ruleset?.CreateInstance()?.CreateBeatmapVerifier();
|
||||||
|
|
||||||
|
RelativeSizeAxes = Axes.Both;
|
||||||
|
|
||||||
|
InternalChildren = new Drawable[]
|
||||||
|
{
|
||||||
|
new Box
|
||||||
|
{
|
||||||
|
Colour = colours.Gray0,
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
},
|
||||||
|
new OsuScrollContainer
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Child = table = new IssueTable(),
|
||||||
|
},
|
||||||
|
new FillFlowContainer
|
||||||
|
{
|
||||||
|
AutoSizeAxes = Axes.Both,
|
||||||
|
Anchor = Anchor.BottomRight,
|
||||||
|
Origin = Anchor.BottomRight,
|
||||||
|
Margin = new MarginPadding(20),
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
new TriangleButton
|
||||||
|
{
|
||||||
|
Text = "Refresh",
|
||||||
|
Action = refresh,
|
||||||
|
Size = new Vector2(120, 40),
|
||||||
|
Anchor = Anchor.BottomRight,
|
||||||
|
Origin = Anchor.BottomRight,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
|
||||||
|
refresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void refresh()
|
||||||
|
{
|
||||||
|
var issues = generalVerifier.Run(Beatmap);
|
||||||
|
|
||||||
|
if (rulesetVerifier != null)
|
||||||
|
issues = issues.Concat(rulesetVerifier.Run(Beatmap));
|
||||||
|
|
||||||
|
table.Issues = issues
|
||||||
|
.OrderBy(issue => issue.Template.Type)
|
||||||
|
.ThenBy(issue => issue.Check.Metadata.Category);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user