diff --git a/OpenUtau/Controls/PianoRoll.axaml b/OpenUtau/Controls/PianoRoll.axaml
index fdc205387..9504a2b70 100644
--- a/OpenUtau/Controls/PianoRoll.axaml
+++ b/OpenUtau/Controls/PianoRoll.axaml
@@ -22,7 +22,7 @@
-
+
@@ -378,48 +378,45 @@
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
+
-
-
+
+
-
+
@@ -603,10 +600,10 @@
-
-
+
+
-
+
-
+
+
+
+
diff --git a/OpenUtau/Controls/PianoRoll.axaml.cs b/OpenUtau/Controls/PianoRoll.axaml.cs
index 1d5a660fe..1ef17beeb 100644
--- a/OpenUtau/Controls/PianoRoll.axaml.cs
+++ b/OpenUtau/Controls/PianoRoll.axaml.cs
@@ -397,16 +397,19 @@ void SetPenToolIcon() {
penTool.Classes.Remove("penTool");
penTool.Classes.Remove("penPlusTool");
penTool.Classes.Add(ViewModel.EditTool.PenToolVariation == 1 ? "penPlusTool" : "penTool");
+ ToolTip.SetTip(penTool, ViewModel.EditTool.PenToolVariation == 1 ? ViewModel.PenPlusToolTip : ViewModel.PenToolTip);
}
void SetDrawPitchToolIcon() {
drawPitchTool.Classes.Remove("drawPitchTool");
drawPitchTool.Classes.Remove("overwritePitchTool");
drawPitchTool.Classes.Add(ViewModel.EditTool.DrawPitchToolVariation == 1 ? "overwritePitchTool" : "drawPitchTool");
+ ToolTip.SetTip(drawPitchTool, ViewModel.EditTool.DrawPitchToolVariation == 1 ? ViewModel.OverwritePitchToolTip : ViewModel.DrawPitchToolTip);
}
void SetDrawLinePitchToolIcon() {
drawLinePitchTool.Classes.Remove("drawLinePitchTool");
drawLinePitchTool.Classes.Remove("overwriteLinePitchTool");
drawLinePitchTool.Classes.Add(ViewModel.EditTool.DrawLinePitchToolVariation == 1 ? "overwriteLinePitchTool" : "drawLinePitchTool");
+ ToolTip.SetTip(drawLinePitchTool, ViewModel.EditTool.DrawLinePitchToolVariation == 1 ? ViewModel.OverwriteLinePitchToolTip : ViewModel.DrawLinePitchToolTip);
}
void SearchNote() {
@@ -601,6 +604,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 +663,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 +946,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 +1124,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 +1249,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 +1297,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/Strings/Strings.axaml b/OpenUtau/Strings/Strings.axaml
index 0be61d251..a48d9be71 100644
--- a/OpenUtau/Strings/Strings.axaml
+++ b/OpenUtau/Strings/Strings.axaml
@@ -382,6 +382,13 @@ Warning: this option removes custom presets.
Enable
Phonemizer
Follows Parent Phonemizer
+
+ Click to move playhead
+ Click to play sound
+ Double click a phoneme to edit
+ Scroll to zoom in/out horizontally
+ ,
+ 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 ▶
+
+ Draw Line Pitch Tool
+ Draw Pitch Tool
+ Eraser Tool
+ Knife Tool
+ 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 straight line
+ Drag to draw straight line (overwrites vibrato or mod+)
+ Drag to draw (overwrites vibrato or mod+)
+ Drag to reset
+ Drag to select
+ Click to set expression
+ Click to split note
+ Right click to delete note
+ Right click to deselect
+ Right drag to reset
+ Right click to reset
+ Hold Shift + Ctrl to draw straight line
+ Hold Shift to draw horizontal line
+ Hold Shift to set same expressions
Duplicate Track
Duplicate Track Settings
diff --git a/OpenUtau/ViewModels/PianoRollViewModel.cs b/OpenUtau/ViewModels/PianoRollViewModel.cs
index 7864e86ab..8e9fe9b4f 100644
--- a/OpenUtau/ViewModels/PianoRollViewModel.cs
+++ b/OpenUtau/ViewModels/PianoRollViewModel.cs
@@ -82,6 +82,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();
@@ -98,6 +110,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 +232,92 @@ public PianoRollViewModel() {
DocManager.Inst.AddSubscriber(this);
}
+ public void SetStatusBarText(string pointer) {
+ string separator = ThemeManager.GetString("operation.separator");
+ switch (pointer) {
+ case "Keyboard":
+ StatusBarText = GetOperationHint(["operation.clickplaysound"], separator);
+ break;
+ case "Timeline":
+ StatusBarText = GetOperationHint(["operation.clickplayhead", "operation.scroolzoom"], separator);
+ break;
+ case "NotesCanvas":
+ switch (EditTool.CurrentTool) {
+ case EditTools.CursorTool:
+ StatusBarText = GetOperationHint(["tools.tips.leftdragselect", "tools.tips.rightdeselect"], separator);
+ break;
+ case EditTools.PenTool:
+ StatusBarText = GetOperationHint(["tools.tips.leftdragcreate", "tools.tips.rightdeselect", "tools.tips.ctrlselect"], separator);
+ break;
+ case EditTools.PenPlusTool:
+ 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 = GetOperationHint(["operation.doubleeditphoneme", "operation.timingenvelope"], separator);
+ break;
+ case "ExpCanvas":
+ 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":
+ default:
+ StatusBarText = string.Empty;
+ break;
+ }
+ }
+ private string GetOperationHint(string[] keys, string separator) {
+ var strings = new List();
+ foreach (string key in keys) {
+ strings.Add(ThemeManager.GetString(key));
+ }
+ return string.Join(separator, strings);
+ }
+
private void SetUndoState() {
CanUndo = DocManager.Inst.GetUndoState(out string? undoNameKey);
if (!string.IsNullOrWhiteSpace(undoNameKey)) {