diff --git a/OpenUtau.Core/Util/Preferences.cs b/OpenUtau.Core/Util/Preferences.cs index f14fe185d..121d00cc5 100644 --- a/OpenUtau.Core/Util/Preferences.cs +++ b/OpenUtau.Core/Util/Preferences.cs @@ -216,8 +216,6 @@ public class SerializablePreferences { public bool LockUnselectedNotesPitch = true; public bool LockUnselectedNotesVibrato = true; public bool LockUnselectedNotesExpressions = true; - public bool LyricLivePreview = true; - public bool LyricApplySelectionOnly = true; public bool VoicebankPublishUseIgnore = true; public string VoicebankPublishIgnores = @"#Adobe Audition *.pkf diff --git a/OpenUtau/Controls/LyricsPanel.axaml b/OpenUtau/Controls/LyricsPanel.axaml new file mode 100644 index 000000000..01542f038 --- /dev/null +++ b/OpenUtau/Controls/LyricsPanel.axaml @@ -0,0 +1,78 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/OpenUtau/Controls/LyricsPanel.axaml.cs b/OpenUtau/Controls/LyricsPanel.axaml.cs new file mode 100644 index 000000000..7d615de46 --- /dev/null +++ b/OpenUtau/Controls/LyricsPanel.axaml.cs @@ -0,0 +1,70 @@ +using Avalonia.Controls; +using Avalonia.Input; +using Avalonia.Interactivity; +using OpenUtau.App.ViewModels; +using OpenUtau.Core; + +namespace OpenUtau.App.Controls { + public partial class LyricsPanel : UserControl { + private LyricsViewModel? viewModel; + + public LyricsPanel() { + InitializeComponent(); + IsVisible = false; + + LyricsBox.AddHandler(GotFocusEvent, TextBoxGotFocus, RoutingStrategies.Tunnel | RoutingStrategies.Bubble); + LyricsBox.AddHandler(KeyDownEvent, TextBoxKeyDown, RoutingStrategies.Tunnel | RoutingStrategies.Bubble); + LyricsBox.AddHandler(LostFocusEvent, TextBoxLostFocus, RoutingStrategies.Tunnel | RoutingStrategies.Bubble); + } + + public void Show(LyricsViewModel viewModel) { + DataContext = this.viewModel = viewModel; + IsVisible = true; + } + + private void TextBoxGotFocus(object? sender, RoutedEventArgs e) { + if (viewModel == null) return; + DocManager.Inst.StartUndoGroup("command.note.lyric"); + viewModel.IsFocused = true; + } + + private void TextBoxKeyDown(object? sender, KeyEventArgs e) { + if (viewModel == null || !LyricsBox.IsFocused) return; + switch (e.Key) { + case Key.Enter: + //If Shift+Enter, insert line break (default textbox behavior). + if (e.KeyModifiers == KeyModifiers.Shift) { + return; + } + this.Focus(); + e.Handled = true; + break; + case Key.Escape: + this.Focus(); + Close(); + e.Handled = true; + break; + default: + if (e.Key == Key.Z && e.KeyModifiers != KeyModifiers.None || e.Key == Key.Y && e.KeyModifiers != KeyModifiers.None) { // Todo: Supports shortcut remapping + // Finish lyrics editing and use the original shortcut + this.Focus(); + } + break; + } + } + + private void TextBoxLostFocus(object? sender, RoutedEventArgs e) { + if (viewModel == null) return; + viewModel.IsFocused = false; + DocManager.Inst.EndUndoGroup(); + } + + public void OnClose(object sender, RoutedEventArgs args) { + Close(); + } + private void Close() { + IsVisible = false; + viewModel = null; + } + } +} diff --git a/OpenUtau/Controls/PianoRoll.axaml b/OpenUtau/Controls/PianoRoll.axaml index fdc205387..204176167 100644 --- a/OpenUtau/Controls/PianoRoll.axaml +++ b/OpenUtau/Controls/PianoRoll.axaml @@ -551,11 +551,15 @@ + Margin="12,12,12,70"> + + + diff --git a/OpenUtau/Controls/PianoRoll.axaml.cs b/OpenUtau/Controls/PianoRoll.axaml.cs index 1d5a660fe..38d80bff6 100644 --- a/OpenUtau/Controls/PianoRoll.axaml.cs +++ b/OpenUtau/Controls/PianoRoll.axaml.cs @@ -413,9 +413,22 @@ void SearchNote() { if (ViewModel.NotesViewModel.Part == null || ViewModel.NotesViewModel.Part.notes.Count == 0) { return; } + LyricsPanel.IsVisible = false; SearchBar.Show(ViewModel.NotesViewModel); } + void OnMenuEditLyrics(object? sender, RoutedEventArgs e) { + EditLyrics(); + } + + void EditLyrics() { + if (ViewModel.NotesViewModel.Part == null) { + return; + } + SearchBar.IsVisible = false; + LyricsPanel.Show(ViewModel.NotesViewModel.LyricsViewModel); + } + void ReplaceLyrics() { if (ViewModel.NotesViewModel.Part == null) { return; @@ -440,32 +453,6 @@ void ReplaceLyrics() { dialog.ShowDialog(RootWindow); } - void OnMenuEditLyrics(object? sender, RoutedEventArgs e) { - EditLyrics(); - } - - void EditLyrics() { - if (ViewModel.NotesViewModel.Part == null) { - return; - } - if (ViewModel.NotesViewModel.Part.notes.Count < 1) { - _ = MessageBox.Show( - RootWindow, - ThemeManager.GetString("lyrics.nonote"), - ThemeManager.GetString("lyrics.caption"), - MessageBox.MessageBoxButtons.Ok); - return; - } - - var vm = new LyricsViewModel(); - var (notes, selection) = ViewModel.NotesViewModel.PrepareInsertLyrics(); - vm.Start(ViewModel.NotesViewModel.Part, notes, selection); - var dialog = new LyricsDialog() { - DataContext = vm, - }; - dialog.ShowDialog(RootWindow); - } - void OnMenuNoteDefaults(object sender, RoutedEventArgs args) { EditNoteDefaults(); } diff --git a/OpenUtau/OpenUtau.csproj b/OpenUtau/OpenUtau.csproj index 85bf69c8b..4ecdac41c 100644 --- a/OpenUtau/OpenUtau.csproj +++ b/OpenUtau/OpenUtau.csproj @@ -99,6 +99,9 @@ + + LyricsPanel.axaml + NotePropertyExpression.axaml diff --git a/OpenUtau/Strings/Strings.axaml b/OpenUtau/Strings/Strings.axaml index 0be61d251..d901975b6 100644 --- a/OpenUtau/Strings/Strings.axaml +++ b/OpenUtau/Strings/Strings.axaml @@ -242,15 +242,8 @@ Do you want to continue by splitting at the nearest position after current playh Chinese Cantonese - Apply - Apply only to selected notes - Cancel - Edit Lyrics - Live preview - There is no note! - Reset - Select some notes first! Separators + Skip symbols such as "R" or "-" After : Before : diff --git a/OpenUtau/ViewModels/LyricsViewModel.cs b/OpenUtau/ViewModels/LyricsViewModel.cs index abeecb92d..a9265aa68 100644 --- a/OpenUtau/ViewModels/LyricsViewModel.cs +++ b/OpenUtau/ViewModels/LyricsViewModel.cs @@ -1,100 +1,95 @@ using System; using System.Linq; +using System.Reactive.Linq; using OpenUtau.Core; using OpenUtau.Core.Ustx; using OpenUtau.Core.Util; using ReactiveUI; using ReactiveUI.Fody.Helpers; +using SharpCompress; namespace OpenUtau.App.ViewModels { - class LyricsViewModel : ViewModelBase { - [Reactive] public string? Text { get; set; } = string.Empty; + public class LyricsViewModel : ViewModelBase { + [Reactive] public string Text { get; set; } = string.Empty; [Reactive] public int CurrentCount { get; set; } [Reactive] public int TotalCount { get; set; } - [Reactive] public bool LivePreview { get; set; } = Preferences.Default.LyricLivePreview; - [Reactive] public bool ApplySelection { get; set; } = Preferences.Default.LyricApplySelectionOnly; + [Reactive] public bool SkipSymbols { get; set; } = true; - private UVoicePart? part; - private UNote[]? notes; - private UNote[]? selection; - private string[]? startLyrics; + public bool IsFocused { get; set; } = false; - public LyricsViewModel() { - this.WhenAnyValue(x => x.LivePreview, - x => x.Text) - .Subscribe(t => { - Preview(t.Item1); - }); - this.WhenAnyValue(x => x.ApplySelection) + private NotesViewModel notesViewModel; + private UNote[] notes = []; + private UNote[] selection = []; + + public LyricsViewModel(NotesViewModel notesVm) { + notesViewModel = notesVm; + + this.WhenAnyValue(x => x.SkipSymbols) .Subscribe(a => { - UpdateTotalCount(); - Preview(LivePreview); + FilterNotes(); }); - } - - public void Start(UVoicePart part, UNote[] notes, UNote[] selection) { - this.part = part; - this.notes = notes; - this.selection = selection; - if (selection.Length < 1) { - ApplySelection = false; - } - UpdateTotalCount(); - CurrentCount = TotalCount; - Text = SplitLyrics.Join(startLyrics!); - DocManager.Inst.StartUndoGroup("command.note.lyric"); + MessageBus.Current.Listen() + .Subscribe(e => { + if (e.tempSelectedNotes.Length > 0) { + selection = []; + } else { + selection = e.selectedNotes.ToArray(); + } + FilterNotes(); + }); + selection = notesViewModel.Selection.ToArray(); + FilterNotes(); } - private void Preview(bool update) { - var notes = ApplySelection ? selection : this.notes; - if (startLyrics == null || notes == null || part == null) { - return; + private void FilterNotes() { + if (IsFocused) { + DocManager.Inst.EndUndoGroup(); + IsFocused = false; } - DocManager.Inst.RollBackUndoGroup(); - var lyrics = SplitLyrics.Split(Text); - CurrentCount = lyrics.Count; - if (update) { - for (int i = 0; i < lyrics.Count && i < notes.Length; ++i) { - if (notes[i].lyric != lyrics[i]) { - DocManager.Inst.ExecuteCmd(new ChangeNoteLyricCommand(part, notes[i], lyrics[i])); - } + if (notesViewModel == null || notesViewModel.Part == null) { + notes = []; + Text = string.Empty; + TotalCount = 0; + CurrentCount = 0; + } else if (selection.Length == 0) { + notes = []; + CurrentCount = SplitLyrics.Split(Text).Count; + if (TotalCount == 0) return; + Text = string.Empty; + TotalCount = 0; + CurrentCount = 0; + } else { + if (SkipSymbols) { + notes = selection.Where(n => n.lyric != "R" && n.lyric != "-" && n.lyric != "+~").ToArray(); + } else { + notes = selection.ToArray(); } + Text = SplitLyrics.Join(notes.Select(n => n.lyric)); + TotalCount = notes.Length; + CurrentCount = SplitLyrics.Split(Text).Count; } } - private void UpdateTotalCount() { - if (ApplySelection) { - TotalCount = selection?.Length ?? 0; - startLyrics = selection?.Select(n => n.lyric).ToArray(); - } else { - TotalCount = notes?.Length ?? 0; - startLyrics = notes?.Select(n => n.lyric).ToArray(); - } - } + public void ApplyLyrics() { + var lyrics = SplitLyrics.Split(Text); + CurrentCount = lyrics.Count; - public void Reset() { - if (startLyrics == null) { + if (notesViewModel == null || notesViewModel.Part == null || !IsFocused || notes.Length == 0 || lyrics.Count == 0) { return; } - DocManager.Inst.RollBackUndoGroup(); - Text = SplitLyrics.Join(startLyrics); - } - public void Cancel() { - DocManager.Inst.RollBackUndoGroup(); - DocManager.Inst.EndUndoGroup(); - Preferences.Default.LyricLivePreview = LivePreview; - Preferences.Default.LyricApplySelectionOnly = ApplySelection; - Preferences.Save(); + int count = Math.Min(lyrics.Count, notes.Length); + DocManager.Inst.ExecuteCmd(new ChangeNoteLyricCommand(notesViewModel.Part, notes.Take(count).ToArray(), lyrics.Take(count).ToArray())); } - public void Finish() { - Preview(true); - DocManager.Inst.EndUndoGroup(); - Preferences.Default.LyricLivePreview = LivePreview; - Preferences.Default.LyricApplySelectionOnly = ApplySelection; - Preferences.Save(); + public string? GetFirstLyric() { + var split = SplitLyrics.Split(Text); + if (string.IsNullOrWhiteSpace(split.FirstOrDefault())) return null; + var lyric = split[0]; + split.RemoveAt(0); + Text = SplitLyrics.Join(split); + return lyric; } } } diff --git a/OpenUtau/ViewModels/NotesViewModel.cs b/OpenUtau/ViewModels/NotesViewModel.cs index 7e80353ca..320656483 100644 --- a/OpenUtau/ViewModels/NotesViewModel.cs +++ b/OpenUtau/ViewModels/NotesViewModel.cs @@ -101,6 +101,7 @@ public class NotesViewModel : ViewModelBase, ICmdSubscriber { private readonly ObservableAsPropertyHelper smallChangeY; public readonly NoteSelectionViewModel Selection = new NoteSelectionViewModel(); + public readonly LyricsViewModel LyricsViewModel; internal NotesViewModelHitTest HitTest; private int _lastNoteLength = 480; @@ -110,6 +111,8 @@ public class NotesViewModel : ViewModelBase, ICmdSubscriber { private int userKey => Project.key; public NotesViewModel() { + LyricsViewModel = new LyricsViewModel(this); + SnapDivs = new List(); SetSnapUnitCommand = ReactiveCommand.Create(div => { userSnapDiv = div; @@ -209,6 +212,10 @@ public NotesViewModel() { CommandParameter = index, })); }); + this.WhenAnyValue(x => x.LyricsViewModel.Text) + .Subscribe(text => { + LyricsViewModel.ApplyLyrics(); + }); ShowTips = Preferences.Default.ShowTips; IsSnapOn = true; diff --git a/OpenUtau/Views/LyricsDialog.axaml b/OpenUtau/Views/LyricsDialog.axaml deleted file mode 100644 index c84346fbb..000000000 --- a/OpenUtau/Views/LyricsDialog.axaml +++ /dev/null @@ -1,72 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -