From 72ca2f1657d014eea55189cd5ab8af9cb2e85cfc Mon Sep 17 00:00:00 2001 From: Maiko Date: Sat, 20 Jun 2026 00:34:42 +0900 Subject: [PATCH 1/3] Add status bar and tips text (wip) --- OpenUtau/Controls/PianoRoll.axaml | 7 ++- OpenUtau/Controls/PianoRoll.axaml.cs | 10 ++++ OpenUtau/ViewModels/PianoRollViewModel.cs | 70 +++++++++++++++++++++++ 3 files changed, 85 insertions(+), 2 deletions(-) diff --git a/OpenUtau/Controls/PianoRoll.axaml b/OpenUtau/Controls/PianoRoll.axaml index fdc205387..03c614718 100644 --- a/OpenUtau/Controls/PianoRoll.axaml +++ b/OpenUtau/Controls/PianoRoll.axaml @@ -22,7 +22,7 @@ - + @@ -669,7 +669,10 @@ - + + + + diff --git a/OpenUtau/Controls/PianoRoll.axaml.cs b/OpenUtau/Controls/PianoRoll.axaml.cs index 1d5a660fe..ed0a2e49d 100644 --- a/OpenUtau/Controls/PianoRoll.axaml.cs +++ b/OpenUtau/Controls/PianoRoll.axaml.cs @@ -601,6 +601,9 @@ public void KeyboardPointerMoved(object sender, PointerEventArgs args) { var element = (TrackBackground)sender; keyboardPlayState.Update(args.Pointer, args.GetPosition(element)); } + Cursor = null; + ViewModel.SetStatusBarText("Keyboard"); + args.Handled = true; } public void KeyboardPointerReleased(object sender, PointerReleasedEventArgs args) { @@ -657,6 +660,9 @@ public void TimelinePointerMoved(object sender, PointerEventArgs args) { int tick = left + ViewModel.NotesViewModel.Part?.position ?? 0; ViewModel.PlaybackViewModel?.MovePlayPos(tick); } + Cursor = null; + ViewModel.SetStatusBarText("Timeline"); + args.Handled = true; } public void TimelinePointerReleased(object sender, PointerReleasedEventArgs args) { @@ -937,6 +943,7 @@ private void NotesCanvasRightPointerPressed(Control control, PointerPoint point, public void NotesCanvasPointerMoved(object sender, PointerEventArgs args) { var control = (Control)sender; var point = args.GetCurrentPoint(control); + ViewModel.SetStatusBarText("NotesCanvas"); args.Handled = true; if (ValueTipCanvas != null) { valueTipPointerPosition = args.GetCurrentPoint(ValueTipCanvas!).Position; @@ -1114,6 +1121,7 @@ public void ExpCanvasPointerPressed(object sender, PointerPressedEventArgs args) public void ExpCanvasPointerMoved(object sender, PointerEventArgs args) { var control = (Control)sender; var point = args.GetCurrentPoint(control); + ViewModel.SetStatusBarText("ExpCanvas"); args.Handled = true; if (ValueTipCanvas != null) { valueTipPointerPosition = args.GetCurrentPoint(ValueTipCanvas!).Position; @@ -1238,6 +1246,7 @@ public async void PhonemeCanvasPointerPressed(object sender, PointerPressedEvent } public void PhonemeCanvasPointerMoved(object sender, PointerEventArgs args) { + ViewModel.SetStatusBarText("PhonemeCanvas"); args.Handled = true; if (ViewModel?.NotesViewModel?.Part == null) { return; @@ -1285,6 +1294,7 @@ public void PhonemeCanvasPointerReleased(object sender, PointerReleasedEventArgs public void BackgroundPointerMoved(object sender, PointerEventArgs args) { Cursor = null; + ViewModel.SetStatusBarText("Background"); args.Handled = true; } diff --git a/OpenUtau/ViewModels/PianoRollViewModel.cs b/OpenUtau/ViewModels/PianoRollViewModel.cs index 7864e86ab..340de6d08 100644 --- a/OpenUtau/ViewModels/PianoRollViewModel.cs +++ b/OpenUtau/ViewModels/PianoRollViewModel.cs @@ -98,6 +98,7 @@ public bool ShowPhonemizerTags { public Dictionary LegacyPluginShortcuts { get; private set; } = new Dictionary(); + [Reactive] public string StatusBarText { get; set; } = string.Empty; [Reactive] public double Progress { get; set; } [Reactive] public bool CanUndo { get; set; } = false; [Reactive] public bool CanRedo { get; set; } = false; @@ -219,6 +220,75 @@ public PianoRollViewModel() { DocManager.Inst.AddSubscriber(this); } + public void SetStatusBarText(string pointer) { + switch (pointer) { + case "Keyboard": + StatusBarText = "Click to play a sound"; + break; + case "Timeline": + StatusBarText = "Timeline"; + break; + case "NotesCanvas": + switch (EditTool.CurrentTool) { + case EditTools.CursorTool: + StatusBarText = "選択"; + break; + case EditTools.PenTool: + StatusBarText = "Ctrlを押して選択"; + break; + case EditTools.PenPlusTool: + StatusBarText = "ペンプラスツール"; + break; + case EditTools.EraserTool: + break; + case EditTools.DrawPitchTool: + break; + case EditTools.OverwritePitchTool: + break; + case EditTools.DrawLinePitchTool: + break; + case EditTools.OverwriteLinePitchTool: + break; + case EditTools.KnifeTool: + break; + default: + break; + } + break; + case "PhonemeCanvas": + StatusBarText = "PhonemeCanvas"; + break; + case "ExpCanvas": + switch (CurveViewModel.CurveTool) { + case CurveTools.CurveSelectTool: + StatusBarText = "選択"; + break; + case CurveTools.CurvePenTool: + StatusBarText = "Ctrlを押して選択"; + break; + case CurveTools.CurveEraserTool: + StatusBarText = "ペンプラスツール"; + break; + default: + break; + } + break; + case "Background": + StatusBarText = string.Empty; + break; + default: + StatusBarText = string.Empty; + break; + } + } + private string GetStatusText(string[] keys) { + var strings = new List(); + foreach (string key in keys) { + strings.Add(ThemeManager.GetString(key)); + } + return string.Join(", ", strings); + } + private void SetUndoState() { CanUndo = DocManager.Inst.GetUndoState(out string? undoNameKey); if (!string.IsNullOrWhiteSpace(undoNameKey)) { From 1f609ea838b1744f6724f19fe91109cd32629beb Mon Sep 17 00:00:00 2001 From: Maiko Date: Sat, 20 Jun 2026 04:06:47 +0900 Subject: [PATCH 2/3] Add status bar and organize usage tips --- OpenUtau/Controls/PianoRoll.axaml | 33 +++++----- OpenUtau/Controls/PianoRoll.axaml.cs | 3 + OpenUtau/Strings/Strings.axaml | 70 ++++++++++----------- OpenUtau/ViewModels/PianoRollViewModel.cs | 74 ++++++++++++++++------- 4 files changed, 106 insertions(+), 74 deletions(-) diff --git a/OpenUtau/Controls/PianoRoll.axaml b/OpenUtau/Controls/PianoRoll.axaml index 03c614718..9504a2b70 100644 --- a/OpenUtau/Controls/PianoRoll.axaml +++ b/OpenUtau/Controls/PianoRoll.axaml @@ -378,48 +378,45 @@ - - + + - - + + - - + + - - + + - + - - + + - + @@ -603,10 +600,10 @@ - - + + - + Enable Phonemizer Follows Parent Phonemizer + + , + Click to play sound + Click to move playhead + Scroll to zoom in/out horizontally + Double click a phoneme to edit + Drag to edit timing and envelope Alias Color @@ -514,40 +521,6 @@ Warning: this option removes custom presets. View Vibrato (U) View Waveform (W) View Expressions (L) - Line Draw Pitch Tool (5) - Left click to draw (draw straight line) - Right click to reset - Hold Ctrl to select - Hold Alt to smoothen - Draw Pitch Tool (4) - Left click to draw - Right click to reset - Hold Ctrl to select - Hold Alt to smoothen - Eraser Tool (3) - Knife Tool (5) - Overwrite Pitch Tool (4) - Left click to draw (overwrites vibrato or mod+) - Right click to reset - Hold Ctrl to select - Hold Alt to smoothen - Overwrite Line Pitch Tool (5) - Left click to draw - (draw straight line overwrites the vibrato or mod+) - Hold Ctrl to draw sine curve - Hold Alt + Shift to tune S-curve - Hold Alt to draw S-curve - Right click to reset - Hold Ctrl to select - Hold Alt to smoothen - Pen Plus Tool (Ctrl + 2) - Left click to draw - Right click to delete - Hold Ctrl to select - Pen Tool (2) - Left click to draw - Hold Ctrl to select - Selection Tool (1) Attack time delta Overlap Preutter @@ -781,6 +754,35 @@ General Space: Play ◀ Scroll here to zoom horizontally ▶ Scroll here to zoom vertically ▶ + + Selection Tool + Pen Tool + Pen Plus Tool + Eraser Tool + Draw Pitch Tool + Overwrite Pitch Tool + Draw Line Pitch Tool + Overwrite Line Pitch Tool + Knife Tool + Drag to select + Drag to create note + Click to delete note + Drag to draw + Drag to draw (overwrites vibrato or mod+) + Drag to draw straight line + Drag to draw straight line (overwrites vibrato or mod+) + Drag to reset + Click to split note + Click to set expression + Right click to deselect + Right click to delete note + Right drag to reset + Right click to reset + Hold Alt to smoothen + Hold Ctrl to select + Hold Shift to draw horizontal line + Hold Shift to set same expressions + Hold Shift + Ctrl to draw straight line Duplicate Track Duplicate Track Settings diff --git a/OpenUtau/ViewModels/PianoRollViewModel.cs b/OpenUtau/ViewModels/PianoRollViewModel.cs index 340de6d08..046eac5c0 100644 --- a/OpenUtau/ViewModels/PianoRollViewModel.cs +++ b/OpenUtau/ViewModels/PianoRollViewModel.cs @@ -5,6 +5,7 @@ using Avalonia.Input; using Avalonia.Threading; using DynamicData.Binding; +using NAudio.CoreAudioApi; using OpenUtau.App.Controls; using OpenUtau.Classic; using OpenUtau.Core; @@ -82,6 +83,18 @@ public bool ShowPhonemizerTags { [Reactive] public int PenToolIndex { get; set; } = Preferences.Default.EditTool.PenToolVariation; [Reactive] public int DrawPitchToolIndex { get; set; } = Preferences.Default.EditTool.DrawPitchToolVariation; [Reactive] public int DrawLinePitchToolIndex { get; set; } = Preferences.Default.EditTool.DrawLinePitchToolVariation; + public string SelectionToolTip => GetOperationHint(["tools.selection", "tools.tips.leftdragselect", "tools.tips.rightdeselect"], "\n "); + public string PenToolTip => GetOperationHint(["tools.pen", "tools.tips.leftdragcreate", "tools.tips.rightdeselect", "tools.tips.ctrlselect"], "\n "); + public string PenPlusToolTip => GetOperationHint(["tools.penplus", "tools.tips.leftdragcreate", "tools.tips.rightdelete", "tools.tips.ctrlselect"], "\n "); + public string EraserToolTip => GetOperationHint(["tools.eraser", "tools.tips.leftdelete", "tools.tips.rightdeselect", "tools.tips.ctrlselect"], "\n "); + public string DrawPitchToolTip => GetOperationHint(["tools.drawpitch", "tools.tips.leftdragdraw", "tools.tips.rightdragreset", "tools.tips.altsmoothen", "tools.tips.ctrlselect"], "\n "); + public string OverwritePitchToolTip => GetOperationHint(["tools.overwritepitch", "tools.tips.leftdragdrawoverwrite", "tools.tips.rightdragreset", "tools.tips.altsmoothen", "tools.tips.ctrlselect"], "\n "); + public string DrawLinePitchToolTip => GetOperationHint(["tools.drawlinepitch", "tools.tips.leftdragdrawline", "tools.tips.rightdragreset", "tools.tips.altsmoothen", "tools.tips.ctrlselect"], "\n "); + public string OverwriteLinePitchToolTip => GetOperationHint(["tools.overwritelinepitch", "tools.tips.leftdragdrawlineoverwrite", "tools.tips.rightdragreset", "tools.tips.altsmoothen", "tools.tips.ctrlselect"], "\n "); + public string KnifeToolTip => GetOperationHint(["tools.knife", "tools.tips.leftsplit", "tools.tips.rightdeselect", "tools.tips.ctrlselect"], "\n "); + public string CurveSelectionToolTip => GetOperationHint(["tools.selection", "tools.tips.leftdragselect", "tools.tips.rightdeselect"], "\n "); + public string CurvePenToolTip => GetOperationHint(["tools.pen", "tools.tips.leftdragdraw", "tools.tips.rightdragreset", "tools.tips.shifthorizontal", "tools.tips.shiftctrlline"], "\n "); + public string CurveEraserToolTip => GetOperationHint(["tools.eraser", "tools.tips.leftdragreset", "tools.tips.rightdeselect"], "\n "); public ObservableCollectionExtended LegacyPlugins { get; private set; } = new ObservableCollectionExtended(); @@ -221,72 +234,89 @@ public PianoRollViewModel() { } public void SetStatusBarText(string pointer) { + string separator = ThemeManager.GetString("operation.separator"); switch (pointer) { case "Keyboard": - StatusBarText = "Click to play a sound"; + StatusBarText = GetOperationHint(["operation.clickplaysound"], separator); break; case "Timeline": - StatusBarText = "Timeline"; + StatusBarText = GetOperationHint(["operation.clickplayhead", "operation.scroolzoom"], separator); break; case "NotesCanvas": switch (EditTool.CurrentTool) { case EditTools.CursorTool: - StatusBarText = "選択"; + StatusBarText = GetOperationHint(["tools.tips.leftdragselect", "tools.tips.rightdeselect"], separator); break; case EditTools.PenTool: - StatusBarText = "Ctrlを押して選択"; + StatusBarText = GetOperationHint(["tools.tips.leftdragcreate", "tools.tips.rightdeselect", "tools.tips.ctrlselect"], separator); break; case EditTools.PenPlusTool: - StatusBarText = "ペンプラスツール"; + StatusBarText = GetOperationHint(["tools.tips.leftdragcreate", "tools.tips.rightdelete", "tools.tips.ctrlselect"], separator); break; case EditTools.EraserTool: + StatusBarText = GetOperationHint(["tools.tips.leftdelete", "tools.tips.rightdeselect", "tools.tips.ctrlselect"], separator); break; case EditTools.DrawPitchTool: + StatusBarText = GetOperationHint(["tools.tips.leftdragdraw", "tools.tips.rightdragreset", "tools.tips.altsmoothen", "tools.tips.ctrlselect"], separator); break; case EditTools.OverwritePitchTool: + StatusBarText = GetOperationHint(["tools.tips.leftdragdrawoverwrite", "tools.tips.rightdragreset", "tools.tips.altsmoothen", "tools.tips.ctrlselect"], separator); break; case EditTools.DrawLinePitchTool: + StatusBarText = GetOperationHint(["tools.tips.leftdragdrawline", "tools.tips.rightdragreset", "tools.tips.altsmoothen", "tools.tips.ctrlselect"], separator); break; case EditTools.OverwriteLinePitchTool: + StatusBarText = GetOperationHint(["tools.tips.leftdragdrawlineoverwrite", "tools.tips.rightdragreset", "tools.tips.altsmoothen", "tools.tips.ctrlselect"], separator); break; case EditTools.KnifeTool: + StatusBarText = GetOperationHint(["tools.tips.leftsplit", "tools.tips.rightdeselect", "tools.tips.ctrlselect"], separator); break; default: break; } break; case "PhonemeCanvas": - StatusBarText = "PhonemeCanvas"; + StatusBarText = GetOperationHint(["operation.doubleeditphoneme", "operation.timingenvelope"], separator); break; case "ExpCanvas": - switch (CurveViewModel.CurveTool) { - case CurveTools.CurveSelectTool: - StatusBarText = "選択"; - break; - case CurveTools.CurvePenTool: - StatusBarText = "Ctrlを押して選択"; - break; - case CurveTools.CurveEraserTool: - StatusBarText = "ペンプラスツール"; - break; - default: - break; + var vm = NotesViewModel; + if (vm.Project == null + || vm.Part == null + || vm.Project.tracks.Count <= vm.Part.trackNo + || !vm.Project.tracks[vm.Part.trackNo].TryGetExpDescriptor(vm.Project, vm.PrimaryKey, out var exp)) { + StatusBarText = string.Empty; + break; + } + if (exp.type == UExpressionType.Curve) { + switch (CurveViewModel.CurveTool) { + case CurveTools.CurveSelectTool: + StatusBarText = GetOperationHint(["tools.tips.leftdragselect", "tools.tips.rightdeselect"], separator); + break; + case CurveTools.CurvePenTool: + StatusBarText = GetOperationHint(["tools.tips.leftdragdraw", "tools.tips.rightdragreset", "tools.tips.shifthorizontal", "tools.tips.shiftctrlline"], separator); + break; + case CurveTools.CurveEraserTool: + StatusBarText = GetOperationHint(["tools.tips.leftdragreset", "tools.tips.rightdeselect"], separator); + break; + default: + break; + } + } else { + StatusBarText = GetOperationHint(["tools.tips.leftexp", "tools.tips.rightreset", "tools.tips.shiftsameexp"], separator); } break; case "Background": - StatusBarText = string.Empty; - break; default: StatusBarText = string.Empty; break; } } - private string GetStatusText(string[] keys) { + private string GetOperationHint(string[] keys, string separator) { var strings = new List(); foreach (string key in keys) { strings.Add(ThemeManager.GetString(key)); } - return string.Join(", ", strings); + return string.Join(separator, strings); } private void SetUndoState() { From ef5d27fb07b217b3a02033df118e7b7a0a5f50ab Mon Sep 17 00:00:00 2001 From: Maiko Date: Sat, 20 Jun 2026 04:16:34 +0900 Subject: [PATCH 3/3] minor fix --- OpenUtau/Strings/Strings.axaml | 36 +++++++++++------------ OpenUtau/ViewModels/PianoRollViewModel.cs | 1 - 2 files changed, 18 insertions(+), 19 deletions(-) diff --git a/OpenUtau/Strings/Strings.axaml b/OpenUtau/Strings/Strings.axaml index 330eeb11a..a48d9be71 100644 --- a/OpenUtau/Strings/Strings.axaml +++ b/OpenUtau/Strings/Strings.axaml @@ -383,11 +383,11 @@ Warning: this option removes custom presets. Phonemizer Follows Parent Phonemizer - , - Click to play sound Click to move playhead - Scroll to zoom in/out horizontally + Click to play sound Double click a phoneme to edit + Scroll to zoom in/out horizontally + , Drag to edit timing and envelope Alias @@ -755,34 +755,34 @@ General ◀ Scroll here to zoom horizontally ▶ Scroll here to zoom vertically ▶ - Selection Tool - Pen Tool - Pen Plus Tool - Eraser Tool - Draw Pitch Tool - Overwrite Pitch Tool Draw Line Pitch Tool - Overwrite Line Pitch Tool + Draw Pitch Tool + Eraser Tool Knife Tool - Drag to select - Drag to create note + Overwrite Line Pitch Tool + Overwrite Pitch Tool + Pen Tool + Pen Plus Tool + Selection Tool + Hold Alt to smoothen + Hold Ctrl to select Click to delete note + Drag to create note Drag to draw - Drag to draw (overwrites vibrato or mod+) Drag to draw straight line Drag to draw straight line (overwrites vibrato or mod+) + Drag to draw (overwrites vibrato or mod+) Drag to reset - Click to split note + Drag to select Click to set expression - Right click to deselect + Click to split note Right click to delete note + Right click to deselect Right drag to reset Right click to reset - Hold Alt to smoothen - Hold Ctrl to select + Hold Shift + Ctrl to draw straight line Hold Shift to draw horizontal line Hold Shift to set same expressions - Hold Shift + Ctrl to draw straight line Duplicate Track Duplicate Track Settings diff --git a/OpenUtau/ViewModels/PianoRollViewModel.cs b/OpenUtau/ViewModels/PianoRollViewModel.cs index 046eac5c0..8e9fe9b4f 100644 --- a/OpenUtau/ViewModels/PianoRollViewModel.cs +++ b/OpenUtau/ViewModels/PianoRollViewModel.cs @@ -5,7 +5,6 @@ using Avalonia.Input; using Avalonia.Threading; using DynamicData.Binding; -using NAudio.CoreAudioApi; using OpenUtau.App.Controls; using OpenUtau.Classic; using OpenUtau.Core;