diff --git a/osu.Game.Rulesets.Karaoke.Tests/Beatmaps/Stages/Classic/ClassicLyricTimingInfoTest.cs b/osu.Game.Rulesets.Karaoke.Tests/Beatmaps/Stages/Classic/ClassicLyricTimingInfoTest.cs index fa47298a3..eaa7cbd61 100644 --- a/osu.Game.Rulesets.Karaoke.Tests/Beatmaps/Stages/Classic/ClassicLyricTimingInfoTest.cs +++ b/osu.Game.Rulesets.Karaoke.Tests/Beatmaps/Stages/Classic/ClassicLyricTimingInfoTest.cs @@ -153,6 +153,21 @@ public void TestClearLyricFromMapping() #region Query + [Test] + public void TestGetTimingPointOrder() + { + var timingInfo = new ClassicLyricTimingInfo(); + timingInfo.Timings.AddRange(new[] { new ClassicLyricTimingPoint { Time = 1000 } }); + + var existTimingPoint = timingInfo.Timings.First(); + int? existTimingPointOrder = timingInfo.GetTimingPointOrder(existTimingPoint); + Assert.AreEqual(1, existTimingPointOrder); + + var notExistTimingPoint = new ClassicLyricTimingPoint { Time = 1000 }; + int? notExistTimingPointOrder = timingInfo.GetTimingPointOrder(notExistTimingPoint); + Assert.IsNull(notExistTimingPointOrder); + } + [Test] public void TestGetLyricTimingPoints() { diff --git a/osu.Game.Rulesets.Karaoke.Tests/Screens/Edit/Stages/Classic/ClassicStageScreenTestScene.cs b/osu.Game.Rulesets.Karaoke.Tests/Screens/Edit/Stages/Classic/ClassicStageScreenTestScene.cs index 37e3a6d8f..c57d484d6 100644 --- a/osu.Game.Rulesets.Karaoke.Tests/Screens/Edit/Stages/Classic/ClassicStageScreenTestScene.cs +++ b/osu.Game.Rulesets.Karaoke.Tests/Screens/Edit/Stages/Classic/ClassicStageScreenTestScene.cs @@ -8,6 +8,7 @@ using osu.Game.Overlays; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Karaoke.Beatmaps; +using osu.Game.Rulesets.Karaoke.Beatmaps.Stages.Classic; using osu.Game.Rulesets.Karaoke.Screens.Edit.Stages.Classic; using osu.Game.Rulesets.Karaoke.Tests.Beatmaps; using osu.Game.Screens.Edit; @@ -49,6 +50,9 @@ protected virtual KaraokeBeatmap CreateBeatmap() if (new KaraokeBeatmapConverter(beatmap, new KaraokeRuleset()).Convert() is not KaraokeBeatmap karaokeBeatmap) throw new ArgumentNullException(nameof(karaokeBeatmap)); + // add classic stage info for testing purpose. + karaokeBeatmap.StageInfos.Add(new ClassicStageInfo()); + return karaokeBeatmap; } } diff --git a/osu.Game.Rulesets.Karaoke.Tests/Screens/Edit/Stages/Classic/TestSceneClassicStageEditor.cs b/osu.Game.Rulesets.Karaoke.Tests/Screens/Edit/Stages/Classic/TestSceneClassicStageEditor.cs index 32001cb9f..135838e00 100644 --- a/osu.Game.Rulesets.Karaoke.Tests/Screens/Edit/Stages/Classic/TestSceneClassicStageEditor.cs +++ b/osu.Game.Rulesets.Karaoke.Tests/Screens/Edit/Stages/Classic/TestSceneClassicStageEditor.cs @@ -7,6 +7,7 @@ using osu.Game.Overlays; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Karaoke.Beatmaps; +using osu.Game.Rulesets.Karaoke.Beatmaps.Stages.Classic; using osu.Game.Rulesets.Karaoke.Screens.Edit.Stages.Classic; using osu.Game.Rulesets.Karaoke.Tests.Beatmaps; using osu.Game.Screens.Edit; @@ -29,6 +30,10 @@ public TestSceneClassicStageEditor() { var beatmap = new TestKaraokeBeatmap(new KaraokeRuleset().RulesetInfo); var karaokeBeatmap = new KaraokeBeatmapConverter(beatmap, new KaraokeRuleset()).Convert() as KaraokeBeatmap; + + // add classic stage info for testing purpose. + karaokeBeatmap!.StageInfos.Add(new ClassicStageInfo()); + editorBeatmap = new EditorBeatmap(karaokeBeatmap); } diff --git a/osu.Game.Rulesets.Karaoke/Beatmaps/KaraokeBeatmap.cs b/osu.Game.Rulesets.Karaoke/Beatmaps/KaraokeBeatmap.cs index 223eff717..73497a912 100644 --- a/osu.Game.Rulesets.Karaoke/Beatmaps/KaraokeBeatmap.cs +++ b/osu.Game.Rulesets.Karaoke/Beatmaps/KaraokeBeatmap.cs @@ -26,6 +26,8 @@ public class KaraokeBeatmap : Beatmap public int TotalColumns { get; set; } = 9; + public TStageInfo? GetStageInfo() => StageInfos.OfType().FirstOrDefault(); + public override IEnumerable GetStatistics() { int singers = SingerInfo.GetAllSingers().Count(); diff --git a/osu.Game.Rulesets.Karaoke/Beatmaps/Stages/Classic/ClassicLyricTimingInfo.cs b/osu.Game.Rulesets.Karaoke/Beatmaps/Stages/Classic/ClassicLyricTimingInfo.cs index 01cc75150..ed578f479 100644 --- a/osu.Game.Rulesets.Karaoke/Beatmaps/Stages/Classic/ClassicLyricTimingInfo.cs +++ b/osu.Game.Rulesets.Karaoke/Beatmaps/Stages/Classic/ClassicLyricTimingInfo.cs @@ -20,7 +20,7 @@ public class ClassicLyricTimingInfo private readonly Bindable timingVersion = new(); // todo: should be readonly. - public BindableList Timings = new BindableList(); + public BindableList Timings = new(); [JsonIgnore] public List SortedTimings { get; private set; } = new(); @@ -55,12 +55,12 @@ public ClassicLyricTimingInfo() break; } - onPageChanged(); + onTimingChanged(); - void timeValueChanged(ValueChangedEvent e) => onPageChanged(); + void timeValueChanged(ValueChangedEvent e) => onTimingChanged(); }; - void onPageChanged() + void onTimingChanged() { SortedTimings = Timings.OrderBy(x => x.Time).ToList(); timingVersion.Value++; @@ -175,6 +175,12 @@ public void ClearLyricFromMapping(Lyric lyric) #region Query + public int? GetTimingPointOrder(ClassicLyricTimingPoint point) + { + int index = SortedTimings.IndexOf(point); + return index == -1 ? null : index + 1; + } + public IEnumerable GetLyricTimingPoints(Lyric lyric) { if (!Mappings.TryGetValue(lyric.ID, out int[]? ids)) diff --git a/osu.Game.Rulesets.Karaoke/Screens/Edit/Stages/Classic/Stage/IStageEditorStateProvider.cs b/osu.Game.Rulesets.Karaoke/Screens/Edit/Stages/Classic/Stage/IStageEditorStateProvider.cs index 7daaaa342..48124d88a 100644 --- a/osu.Game.Rulesets.Karaoke/Screens/Edit/Stages/Classic/Stage/IStageEditorStateProvider.cs +++ b/osu.Game.Rulesets.Karaoke/Screens/Edit/Stages/Classic/Stage/IStageEditorStateProvider.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Bindables; +using osu.Game.Rulesets.Karaoke.Beatmaps.Stages.Classic; namespace osu.Game.Rulesets.Karaoke.Screens.Edit.Stages.Classic.Stage; @@ -18,4 +19,6 @@ public interface IStageEditorStateProvider StageEditorEditMode EditMode => BindableEditMode.Value; void ChangeEditMode(StageEditorEditMode mode); + + ClassicStageInfo StageInfo { get; } } diff --git a/osu.Game.Rulesets.Karaoke/Screens/Edit/Stages/Classic/Stage/Settings/StageSettings.cs b/osu.Game.Rulesets.Karaoke/Screens/Edit/Stages/Classic/Stage/Settings/StageSettings.cs index 767539a45..22ced18a4 100644 --- a/osu.Game.Rulesets.Karaoke/Screens/Edit/Stages/Classic/Stage/Settings/StageSettings.cs +++ b/osu.Game.Rulesets.Karaoke/Screens/Edit/Stages/Classic/Stage/Settings/StageSettings.cs @@ -61,6 +61,7 @@ private void load(OverlayColourProvider colourProvider, IStageEditorStateProvide StageEditorEditMode.Edit => new Drawable[] { new StageEditorEditModeSection(StageEditorEditCategory.Timing), + new TimingPointsSection(), }, StageEditorEditMode.Verify => new Drawable[] { diff --git a/osu.Game.Rulesets.Karaoke/Screens/Edit/Stages/Classic/Stage/Settings/TimingPointsSection.cs b/osu.Game.Rulesets.Karaoke/Screens/Edit/Stages/Classic/Stage/Settings/TimingPointsSection.cs new file mode 100644 index 000000000..1b355b8bf --- /dev/null +++ b/osu.Game.Rulesets.Karaoke/Screens/Edit/Stages/Classic/Stage/Settings/TimingPointsSection.cs @@ -0,0 +1,87 @@ +// Copyright (c) andy840119 . Licensed under the GPL Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Diagnostics.CodeAnalysis; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Localisation; +using osu.Game.Extensions; +using osu.Game.Rulesets.Karaoke.Beatmaps.Stages.Classic; +using osu.Game.Rulesets.Karaoke.Edit.ChangeHandlers.Beatmaps; +using osu.Game.Screens.Edit; + +namespace osu.Game.Rulesets.Karaoke.Screens.Edit.Stages.Classic.Stage.Settings; + +public partial class TimingPointsSection : EditorSection +{ + protected override LocalisableString Title => "Timings"; + + public TimingPointsSection() + { + Add(new SectionPageInfoEditor()); + } + + private partial class SectionPageInfoEditor : SectionTimingInfoItemsEditor + { + [BackgroundDependencyLoader] + private void load(IStageEditorStateProvider stageEditorStateProvider) + { + Items.BindTo(stageEditorStateProvider.StageInfo.LyricTimingInfo.Timings); + } + + protected override DrawableTimingInfoItem CreateTimingInfoDrawable(ClassicLyricTimingPoint item) => new DrawableTimingPoint(item); + + protected override EditorSectionButton? CreateCreateNewItemButton() => new CreateNewTimingPointButton(); + + private partial class DrawableTimingPoint : DrawableTimingInfoItem + { + private readonly IBindable timingPointsVersion = new Bindable(); + + [Resolved, AllowNull] + private IBeatmapClassicStageChangeHandler beatmapClassicStageChangeHandler { get; set; } + + public DrawableTimingPoint(ClassicLyricTimingPoint item) + : base(item) + { + } + + protected override void RemoveItem(ClassicLyricTimingPoint item) + { + beatmapClassicStageChangeHandler.RemoveTimingPoint(item); + } + + [BackgroundDependencyLoader] + private void load(IStageEditorStateProvider stageEditorStateProvider) + { + timingPointsVersion.BindTo(stageEditorStateProvider.StageInfo.LyricTimingInfo.TimingVersion); + timingPointsVersion.BindValueChanged(_ => + { + int? order = stageEditorStateProvider.StageInfo.LyricTimingInfo.GetTimingPointOrder(Item); + double time = Item.Time; + + ChangeDisplayOrder((int)time); + Text = $"#{order} {time.ToEditorFormattedString()}"; + }, true); + } + } + + private partial class CreateNewTimingPointButton : EditorSectionButton + { + [Resolved, AllowNull] + private IBeatmapClassicStageChangeHandler beatmapClassicStageChangeHandler { get; set; } + + [Resolved, AllowNull] + private EditorClock clock { get; set; } + + public CreateNewTimingPointButton() + { + Text = "Create new timing"; + Action = () => + { + double currentTime = clock.CurrentTime; + beatmapClassicStageChangeHandler.AddTimingPoint(x => x.Time = currentTime); + }; + } + } + } +} diff --git a/osu.Game.Rulesets.Karaoke/Screens/Edit/Stages/Classic/Stage/StageScreen.cs b/osu.Game.Rulesets.Karaoke/Screens/Edit/Stages/Classic/Stage/StageScreen.cs index 94e7c805d..49729e5a6 100644 --- a/osu.Game.Rulesets.Karaoke/Screens/Edit/Stages/Classic/Stage/StageScreen.cs +++ b/osu.Game.Rulesets.Karaoke/Screens/Edit/Stages/Classic/Stage/StageScreen.cs @@ -1,11 +1,17 @@ // Copyright (c) andy840119 . Licensed under the GPL Licence. // See the LICENCE file in the repository root for full licence text. +using System; +using System.Diagnostics.CodeAnalysis; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Game.Rulesets.Karaoke.Beatmaps; +using osu.Game.Rulesets.Karaoke.Beatmaps.Stages.Classic; +using osu.Game.Rulesets.Karaoke.Edit.ChangeHandlers.Beatmaps; using osu.Game.Rulesets.Karaoke.Screens.Edit.Stages.Classic.Stage.Settings; +using osu.Game.Screens.Edit; namespace osu.Game.Rulesets.Karaoke.Screens.Edit.Stages.Classic.Stage; @@ -18,12 +24,22 @@ public partial class StageScreen : ClassicStageScreen, IStageEditorStateProvider private readonly Bindable bindableCategory = new(); private readonly Bindable bindableEditMode = new(); + [Cached(typeof(IBeatmapClassicStageChangeHandler))] + private readonly BeatmapClassicStageChangeHandler beatmapClassicStageChangeHandler; + [Cached(typeof(IStageEditorVerifier))] private readonly StageEditorVerifier stageEditorVerifier; + [Resolved, AllowNull] + private EditorBeatmap editorBeatmap { get; set; } + + public ClassicStageInfo StageInfo => (editorBeatmap.PlayableBeatmap as KaraokeBeatmap)!.GetStageInfo() + ?? throw new InvalidOperationException(); + public StageScreen() : base(ClassicStageEditorScreenMode.Stage) { + AddInternal(beatmapClassicStageChangeHandler = new BeatmapClassicStageChangeHandler()); AddInternal(stageEditorVerifier = new StageEditorVerifier()); Child = new GridContainer