diff --git a/osu.Game.Rulesets.Karaoke/Edit/ImportLyric/GenerateTimeTag/GenerateTimeTagSubScreen.cs b/osu.Game.Rulesets.Karaoke/Edit/ImportLyric/GenerateTimeTag/GenerateTimeTagSubScreen.cs index fdadbabec..288e923fb 100644 --- a/osu.Game.Rulesets.Karaoke/Edit/ImportLyric/GenerateTimeTag/GenerateTimeTagSubScreen.cs +++ b/osu.Game.Rulesets.Karaoke/Edit/ImportLyric/GenerateTimeTag/GenerateTimeTagSubScreen.cs @@ -65,7 +65,10 @@ protected void AskForAutoGenerateTimeTag() DialogOverlay.Push(new UseAutoGenerateTimeTagPopupDialog(ok => { if (ok) + { TimeTagManager.AutoGenerateTimeTags(); + TimeTagManager.MoveCursor(CursorAction.First); + } })); } diff --git a/osu.Game.Rulesets.Karaoke/Edit/Lyrics/Components/LyricControl.cs b/osu.Game.Rulesets.Karaoke/Edit/Lyrics/Components/LyricControl.cs index 9bf7507cb..80be160ea 100644 --- a/osu.Game.Rulesets.Karaoke/Edit/Lyrics/Components/LyricControl.cs +++ b/osu.Game.Rulesets.Karaoke/Edit/Lyrics/Components/LyricControl.cs @@ -33,10 +33,14 @@ public LyricControl(Lyric lyric) }; } - [BackgroundDependencyLoader] - private void load(IFrameBasedClock framedClock) + [BackgroundDependencyLoader(true)] + private void load(IFrameBasedClock framedClock, TimeTagManager timeTagManager) { drawableLyric.Clock = framedClock; + timeTagManager?.BindableCursorPosition.BindValueChanged(e => + { + drawableLyric?.UpdateTimeTagCursoe(e.NewValue); + }, true); } public class DrawableEditorLyric : DrawableLyric @@ -44,19 +48,42 @@ public class DrawableEditorLyric : DrawableLyric private const int time_tag_spacing = 4; private readonly Container timeTagContainer; + private readonly Container timeTagCursorContainer; public DrawableEditorLyric(Lyric lyric) : base(lyric) { - AddInternal(timeTagContainer = new Container + AddRangeInternal(new[] { - RelativeSizeAxes = Axes.Both + timeTagContainer = new Container + { + RelativeSizeAxes = Axes.Both + }, + timeTagCursorContainer = new Container + { + RelativeSizeAxes = Axes.Both + } }); DisplayRuby = true; DisplayRomaji = true; } + public void UpdateTimeTagCursoe(TimeTag cursor) + { + timeTagCursorContainer.Clear(); + if (TimeTagsBindable.Value.Contains(cursor)) + { + var spacing = timeTagPosition(cursor); + timeTagCursorContainer.Add(new DrawableTimeTagCursor(cursor) + { + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + X = spacing + }); + } + } + protected override void LoadComplete() { base.LoadComplete(); @@ -110,21 +137,28 @@ protected void UpdateTimeTags() foreach (var timeTag in timeTags) { - var index = Math.Min(timeTag.Index.Index, HitObject.Text.Length - 1); - var percentage = timeTag.Index.State == TimeTagIndex.IndexState.Start ? 0 : 1; - var position = karaokeText.GetPercentageWidth(index, index + 1, percentage); - - var duplicatedTagAmount = timeTags.SkipWhile(t => t != timeTag).Count(x => x.Index == timeTag.Index) - 1; - var spacing = duplicatedTagAmount * time_tag_spacing * (timeTag.Index.State == TimeTagIndex.IndexState.Start ? 1 : -1); - + var spacing = timeTagPosition(timeTag); timeTagContainer.Add(new DrawableTimeTag(timeTag) { Anchor = Anchor.BottomLeft, Origin = Anchor.BottomLeft, - X = position + spacing + X = spacing }); } } + + private float timeTagPosition(TimeTag timeTag) + { + var index = Math.Min(timeTag.Index.Index, HitObject.Text.Length - 1); + var isStart = timeTag.Index.State == TimeTagIndex.IndexState.Start; + var percentage = isStart ? 0 : 1; + var position = karaokeText.GetPercentageWidth(index, index + 1, percentage); + + var timeTags = isStart ? TimeTagsBindable.Value.Reverse() : TimeTagsBindable.Value; + var duplicatedTagAmount = timeTags.SkipWhile(t => t != timeTag).Count(x => x.Index == timeTag.Index) - 1; + var spacing = duplicatedTagAmount * time_tag_spacing * (isStart ? 1 : -1); + return position + spacing; + } } } } diff --git a/osu.Game.Rulesets.Karaoke/Edit/Lyrics/Components/TimeTags/DrawableTimeTag.cs b/osu.Game.Rulesets.Karaoke/Edit/Lyrics/Components/TimeTags/DrawableTimeTag.cs index c1beaeacf..f7b8292da 100644 --- a/osu.Game.Rulesets.Karaoke/Edit/Lyrics/Components/TimeTags/DrawableTimeTag.cs +++ b/osu.Game.Rulesets.Karaoke/Edit/Lyrics/Components/TimeTags/DrawableTimeTag.cs @@ -5,6 +5,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; +using osu.Framework.Input.Events; using osu.Game.Graphics; using osu.Game.Rulesets.Karaoke.Graphics.Shapes; using osu.Game.Rulesets.Karaoke.Objects; @@ -22,6 +23,9 @@ public class DrawableTimeTag : CompositeDrawable private readonly TimeTag timeTag; + [Resolved(canBeNull: true)] + private TimeTagManager timeTagManager { get; set; } + public DrawableTimeTag(TimeTag timeTag) { this.timeTag = timeTag; @@ -36,6 +40,11 @@ public DrawableTimeTag(TimeTag timeTag) }; } + protected override bool OnClick(ClickEvent e) + { + return timeTagManager?.MoveCursorToTargetPosition(timeTag) ?? false; + } + [BackgroundDependencyLoader] private void load(OsuColour colours) { diff --git a/osu.Game.Rulesets.Karaoke/Edit/Lyrics/Components/TimeTags/DrawableTimeTagCursor.cs b/osu.Game.Rulesets.Karaoke/Edit/Lyrics/Components/TimeTags/DrawableTimeTagCursor.cs new file mode 100644 index 000000000..ba7926838 --- /dev/null +++ b/osu.Game.Rulesets.Karaoke/Edit/Lyrics/Components/TimeTags/DrawableTimeTagCursor.cs @@ -0,0 +1,45 @@ +// Copyright (c) andy840119 . Licensed under the GPL 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.Sprites; +using osu.Game.Graphics; +using osu.Game.Rulesets.Karaoke.Graphics.Shapes; +using osu.Game.Rulesets.Karaoke.Objects; +using osuTK; +using System; + +namespace osu.Game.Rulesets.Karaoke.Edit.Lyrics.Components.TimeTags +{ + public class DrawableTimeTagCursor : CompositeDrawable + { + /// + /// Height of major bar line triangles. + /// + private const float triangle_width = 4; + + private readonly TimeTag timeTag; + + public DrawableTimeTagCursor(TimeTag timeTag) + { + this.timeTag = timeTag; + + InternalChild = new RightTriangle + { + Name = "Time tag triangle", + Anchor = Anchor.TopCentre, + Origin = Anchor.Centre, + Size = new Vector2(triangle_width), + Scale = new Vector2(timeTag.Index.State == TimeTagIndex.IndexState.Start ? 1 : -1, 1) + }; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + InternalChild.Colour = timeTag.Time.HasValue ? colours.YellowDarker : colours.Gray3; + } + } +} diff --git a/osu.Game.Rulesets.Karaoke/Edit/Lyrics/LyricEditor.cs b/osu.Game.Rulesets.Karaoke/Edit/Lyrics/LyricEditor.cs index fec5233ed..a8ea20269 100644 --- a/osu.Game.Rulesets.Karaoke/Edit/Lyrics/LyricEditor.cs +++ b/osu.Game.Rulesets.Karaoke/Edit/Lyrics/LyricEditor.cs @@ -4,10 +4,12 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Input.Events; using osu.Game.Rulesets.Karaoke.Objects; using osu.Game.Rulesets.Objects; using osu.Game.Screens.Edit; using osu.Game.Skinning; +using osuTK.Input; namespace osu.Game.Rulesets.Karaoke.Edit.Lyrics { @@ -16,6 +18,9 @@ public class LyricEditor : Container [Resolved] private EditorBeatmap beatmap { get; set; } + [Resolved(canBeNull: true)] + private TimeTagManager timeTagManager { get; set; } + private readonly KaraokeLyricEditorSkin skin; private readonly DrawableLyricEditList container; @@ -44,6 +49,32 @@ protected override void LoadComplete() beatmap.HitObjectAdded += addHitObject; beatmap.HitObjectRemoved += removeHitObject; + + timeTagManager?.MoveCursor(CursorAction.First); + } + + protected override bool OnKeyDown(KeyDownEvent e) + { + if (timeTagManager == null) + return false; + + switch (e.Key) + { + case Key.Up: + return timeTagManager.MoveCursor(CursorAction.MoveUp); + case Key.Down: + return timeTagManager.MoveCursor(CursorAction.MoveDown); + case Key.Left: + return timeTagManager.MoveCursor(CursorAction.MoveLeft); + case Key.Right: + return timeTagManager.MoveCursor(CursorAction.MoveRight); + case Key.PageUp: + return timeTagManager.MoveCursor(CursorAction.First); + case Key.PageDown: + return timeTagManager.MoveCursor(CursorAction.Last); + default: + return base.OnKeyDown(e); + } } private void addHitObject(HitObject hitObject) diff --git a/osu.Game.Rulesets.Karaoke/Edit/Lyrics/LyricEditorScreen.cs b/osu.Game.Rulesets.Karaoke/Edit/Lyrics/LyricEditorScreen.cs index b62acd4e8..d4e86ced8 100644 --- a/osu.Game.Rulesets.Karaoke/Edit/Lyrics/LyricEditorScreen.cs +++ b/osu.Game.Rulesets.Karaoke/Edit/Lyrics/LyricEditorScreen.cs @@ -32,9 +32,13 @@ public class LyricEditorScreen : EditorScreenWithTimeline, ICanAcceptFiles [Resolved(CanBeNull = true)] private KaraokeHitObjectComposer composer { get; set; } + [Cached] + protected readonly TimeTagManager TranslateManager; + public LyricEditorScreen() : base(EditorScreenMode.Compose) { + Content.Add(TranslateManager = new TimeTagManager()); } Task ICanAcceptFiles.Import(params string[] paths) diff --git a/osu.Game.Rulesets.Karaoke/Edit/Lyrics/TimeTagManager.cs b/osu.Game.Rulesets.Karaoke/Edit/Lyrics/TimeTagManager.cs index 49232687b..e5c105dfd 100644 --- a/osu.Game.Rulesets.Karaoke/Edit/Lyrics/TimeTagManager.cs +++ b/osu.Game.Rulesets.Karaoke/Edit/Lyrics/TimeTagManager.cs @@ -2,8 +2,9 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; -using osu.Framework.Graphics.Sprites; using osu.Game.Rulesets.Karaoke.Edit.Generator.TimeTags.Ja; using osu.Game.Rulesets.Karaoke.Edit.Generator.TimeTags.Zh; using osu.Game.Rulesets.Karaoke.Objects; @@ -13,6 +14,10 @@ namespace osu.Game.Rulesets.Karaoke.Edit.Lyrics { + /// + /// Handle view or edit time-tag in lyrics. + /// Notice that is not strictly needed, just not showing time-tag if not regist this manager. + /// public class TimeTagManager : Component { [Resolved] @@ -21,6 +26,8 @@ public class TimeTagManager : Component [Resolved(CanBeNull = true)] private IEditorChangeHandler changeHandler { get; set; } + public Bindable BindableCursorPosition { get; set; } = new Bindable(); + /// /// Will auto-detect each 's and apply on them. /// @@ -43,6 +50,103 @@ public void AutoGenerateTimeTags() changeHandler?.EndChange(); } + public bool MoveCursor(CursorAction action) + { + var currentTimeTag = BindableCursorPosition.Value; + + TimeTag nextTimeTag = null; + switch (action) + { + case CursorAction.MoveUp: + nextTimeTag = getPreviousLyricTimeTag(currentTimeTag); + break; + case CursorAction.MoveDown: + nextTimeTag = getNextLyricTimeTag(currentTimeTag); + break; + case CursorAction.MoveLeft: + nextTimeTag = getPreviousTimeTag(currentTimeTag); + break; + case CursorAction.MoveRight: + nextTimeTag = getNextTimeTag(currentTimeTag); + break; + case CursorAction.First: + nextTimeTag = getFirstTimeTag(currentTimeTag); + break; + case CursorAction.Last: + nextTimeTag = getLastTimeTag(currentTimeTag); + break; + } + + if (nextTimeTag == null) + return false; + + moveCursorTo(nextTimeTag); + return true; + } + + public bool MoveCursorToTargetPosition(TimeTag timeTag) + { + if (timeTagInLyric(timeTag) == null) + return false; + + moveCursorTo(timeTag); + return true; + } + + private Lyric timeTagInLyric(TimeTag timeTag) + { + if (timeTag == null) + return null; + + return beatmap.HitObjects.OfType().FirstOrDefault(x => x.TimeTags?.Contains(timeTag) ?? false); + } + + private TimeTag getPreviousLyricTimeTag(TimeTag timeTag) + { + var lyrics = beatmap.HitObjects.OfType().ToList(); + var currentLyric = timeTagInLyric(timeTag); + return lyrics.GetPrevious(currentLyric)?.TimeTags?.FirstOrDefault(x => x.Index >= timeTag.Index); + } + + public TimeTag getNextLyricTimeTag(TimeTag timeTag) + { + var lyrics = beatmap.HitObjects.OfType().ToList(); + var currentLyric = timeTagInLyric(timeTag); + return lyrics.GetNext(currentLyric)?.TimeTags?.FirstOrDefault(x => x.Index >= timeTag.Index); + } + + private TimeTag getPreviousTimeTag(TimeTag timeTag) + { + var timeTags = beatmap.HitObjects.OfType().SelectMany(x => x.TimeTags).ToArray(); + return timeTags.GetPrevious(timeTag); + } + + public TimeTag getNextTimeTag(TimeTag timeTag) + { + var timeTags = beatmap.HitObjects.OfType().SelectMany(x => x.TimeTags).ToArray(); + return timeTags.GetNext(timeTag); + } + + private TimeTag getFirstTimeTag(TimeTag timeTag) + { + var timeTags = beatmap.HitObjects.OfType().SelectMany(x => x.TimeTags).ToArray(); + return timeTags.FirstOrDefault(); + } + + public TimeTag getLastTimeTag(TimeTag timeTag) + { + var timeTags = beatmap.HitObjects.OfType().SelectMany(x => x.TimeTags).ToArray(); + return timeTags.LastOrDefault(); + } + + private void moveCursorTo(TimeTag timeTag) + { + if (timeTag == null) + return; + + BindableCursorPosition.Value = timeTag; + } + public class TimeTagGeneratorSelector { private readonly Lazy jaTimeTagGenerator; @@ -82,4 +186,19 @@ public TimeTag[] GenerateTimeTags(Lyric lyric) } } } + + public enum CursorAction + { + MoveUp, + + MoveDown, + + MoveLeft, + + MoveRight, + + First, + + Last, + } }