From 5d93487d1b10217289b4b823b24040a18f44ffaf Mon Sep 17 00:00:00 2001 From: Sugita Akira Date: Tue, 16 Jun 2026 20:59:45 -0700 Subject: [PATCH] Fix piano roll UI bugs - Remove time axis from expression area - Fix portrait positioning - Fix portrait covered by expression track background - Fix expression track background alternating color - Auto hide notes properties scroll bar --- OpenUtau/Controls/NotePropertiesControl.axaml | 4 +- OpenUtau/Controls/PianoRoll.axaml | 40 ++++++------ OpenUtau/Controls/PianoRoll.axaml.cs | 12 ++++ OpenUtau/Controls/TickBackground.cs | 62 ++++++++++--------- OpenUtau/ViewModels/NotesViewModel.cs | 38 ++++++++---- OpenUtau/Views/MainWindow.axaml | 2 +- 6 files changed, 96 insertions(+), 62 deletions(-) diff --git a/OpenUtau/Controls/NotePropertiesControl.axaml b/OpenUtau/Controls/NotePropertiesControl.axaml index 8a9701a53..70a24c56b 100644 --- a/OpenUtau/Controls/NotePropertiesControl.axaml +++ b/OpenUtau/Controls/NotePropertiesControl.axaml @@ -20,8 +20,8 @@ - - + + diff --git a/OpenUtau/Controls/PianoRoll.axaml b/OpenUtau/Controls/PianoRoll.axaml index caa98e9f8..fdc205387 100644 --- a/OpenUtau/Controls/PianoRoll.axaml +++ b/OpenUtau/Controls/PianoRoll.axaml @@ -116,6 +116,7 @@ Fill="{DynamicResource NeutralAccentBrushSemi}" Data="M -6.5 0 L 6.5 0 L 6.5 3 L 0 9 L -6.5 3 Z"/> + - + ShowBar="True"/> + + + + + + + + - - o.SnapTicks, (o, v) => o.SnapTicks = v); - public static readonly DirectProperty ShowBarNumberProperty = + public static readonly DirectProperty ShowBarProperty = AvaloniaProperty.RegisterDirect( - nameof(ShowBarNumber), - o => o.ShowBarNumber, - (o, v) => o.ShowBarNumber = v); + nameof(ShowBar), + o => o.ShowBar, + (o, v) => o.ShowBar = v); public int Resolution { get => _resolution; @@ -72,9 +72,9 @@ public ObservableCollection? SnapTicks { get => _snapTicks; set => SetAndRaise(SnapTicksProperty, ref _snapTicks, value); } - public bool ShowBarNumber { - get => _showBarNumber; - set => SetAndRaise(ShowBarNumberProperty, ref _showBarNumber, value); + public bool ShowBar { + get => _showBar; + set => SetAndRaise(ShowBarProperty, ref _showBar, value); } private int _resolution = 480; @@ -83,7 +83,7 @@ public bool ShowBarNumber { private int _tickOrigin; private int _snapDiv; private ObservableCollection? _snapTicks; - private bool _showBarNumber; + private bool _showBar = true; private Pen penBar; private Pen penBeatUnit; @@ -116,7 +116,8 @@ protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs chang change.Property == TickOriginProperty || change.Property == TickWidthProperty || change.Property == TickOffsetProperty || - change.Property == SnapDivProperty) { + change.Property == SnapDivProperty || + change.Property == ShowBarProperty) { InvalidateVisual(); } } @@ -143,14 +144,15 @@ public override void Render(DrawingContext context) { SnapTicks?.Clear(); while (barTick <= rightTick) { SnapTicks?.Add(barTick); - // Bar lines and numbers. double x = Math.Round(barTick * TickWidth - pixelOffset) + 0.5; double y = -0.5; - var textLayout = TextLayoutCache.Get((bar + 1).ToString(), ThemeManager.BarNumberBrush, 10); - using (var state = context.PushTransform(Matrix.CreateTranslation(x + 3, 10))) { - textLayout.Draw(context, new Point()); + if (ShowBar) { + var textLayout = TextLayoutCache.Get((bar + 1).ToString(), ThemeManager.BarNumberBrush, 10); + using (var state = context.PushTransform(Matrix.CreateTranslation(x + 3, 10))) { + textLayout.Draw(context, new Point()); + } + context.DrawLine(penBar, new Point(x, y), new Point(x, Bounds.Height + 0.5f)); } - context.DrawLine(penBar, new Point(x, y), new Point(x, Bounds.Height + 0.5f)); // Lines between bars. var timeSig = project.timeAxis.TimeSignatureAtBar(bar); int nextBarTick = project.timeAxis.BarBeatToTickPos(bar + 1, 0); @@ -171,7 +173,7 @@ public override void Render(DrawingContext context) { project.timeAxis.TickPosToBarBeat(tick, out int snapBar, out int snapBeat, out int snapRemainingTicks); var pen = snapRemainingTicks != 0 ? penDanshed : penBeatUnit; x = Math.Round(tick * TickWidth - pixelOffset) + 0.5; - y = 24; + y = ShowBar ? 24 : 0; context.DrawLine(pen, new Point(x, y), new Point(x, Bounds.Height + 0.5f)); } } @@ -180,22 +182,24 @@ public override void Render(DrawingContext context) { } SnapTicks?.Add(barTick); - foreach (var tempo in project.tempos) { - double x = Math.Round(tempo.position * TickWidth - pixelOffset) + 0.5; - context.DrawLine(penDanshed, new Point(x, 0), new Point(x, 24)); - var textLayout = TextLayoutCache.Get(tempo.bpm.ToString("#0.00"), ThemeManager.BarNumberBrush, 10); - using (var state = context.PushTransform(Matrix.CreateTranslation(x + 3, 0))) { - textLayout.Draw(context, new Point()); + if (ShowBar) { + foreach (var tempo in project.tempos) { + double x = Math.Round(tempo.position * TickWidth - pixelOffset) + 0.5; + context.DrawLine(penDanshed, new Point(x, 0), new Point(x, 24)); + var textLayout = TextLayoutCache.Get(tempo.bpm.ToString("#0.00"), ThemeManager.BarNumberBrush, 10); + using (var state = context.PushTransform(Matrix.CreateTranslation(x + 3, 0))) { + textLayout.Draw(context, new Point()); + } } - } - foreach (var timeSig in project.timeSignatures) { - int tick = project.timeAxis.BarBeatToTickPos(timeSig.barPosition, 0); - var barTextLayout = TextLayoutCache.Get((timeSig.barPosition + 1).ToString(), ThemeManager.BarNumberBrush, 10); - double x = Math.Round(tick * TickWidth - pixelOffset) + 0.5 + barTextLayout.Width + 4; - var textLayout = TextLayoutCache.Get($"{timeSig.beatPerBar}/{timeSig.beatUnit}", ThemeManager.BarNumberBrush, 10); - using (var state = context.PushTransform(Matrix.CreateTranslation(x + 3, 10))) { - textLayout.Draw(context, new Point()); + foreach (var timeSig in project.timeSignatures) { + int tick = project.timeAxis.BarBeatToTickPos(timeSig.barPosition, 0); + var barTextLayout = TextLayoutCache.Get((timeSig.barPosition + 1).ToString(), ThemeManager.BarNumberBrush, 10); + double x = Math.Round(tick * TickWidth - pixelOffset) + 0.5 + barTextLayout.Width + 4; + var textLayout = TextLayoutCache.Get($"{timeSig.beatPerBar}/{timeSig.beatUnit}", ThemeManager.BarNumberBrush, 10); + using (var state = context.PushTransform(Matrix.CreateTranslation(x + 3, 10))) { + textLayout.Draw(context, new Point()); + } } } } diff --git a/OpenUtau/ViewModels/NotesViewModel.cs b/OpenUtau/ViewModels/NotesViewModel.cs index 0ee465700..7e80353ca 100644 --- a/OpenUtau/ViewModels/NotesViewModel.cs +++ b/OpenUtau/ViewModels/NotesViewModel.cs @@ -153,10 +153,24 @@ public NotesViewModel() { }); this.WhenAnyValue(x => x.ExpBounds, x => x.PrimaryKey) .Subscribe(t => { - if (t.Item2 != null && Project.expressions.TryGetValue(t.Item2, out var descriptor)) { - if (descriptor.type == UExpressionType.Options && descriptor.options.Length > 0) { - ExpTrackHeight = t.Item1.Height / descriptor.options.Length; + UExpressionDescriptor? descriptor = null; + if (t.Item2 != null) { + UExpressionDescriptor trackDesc = default!; + bool hasTrackDesc = Part != null && Project.tracks[Part.trackNo] + .TryGetExpDescriptor(Project, t.Item2, out trackDesc); + if (hasTrackDesc) { + descriptor = trackDesc; + } else if (Project.expressions.TryGetValue(t.Item2, out var projDesc)) { + descriptor = projDesc; + } + } + if (descriptor != null) { + if (descriptor.type == UExpressionType.Options) { + int numOptions = Math.Max(descriptor.options.Length, 1); + ExpTrackHeight = t.Item1.Height / numOptions; ExpShadowOpacity = 0; + } else { + ExpTrackHeight = 0; } ShowCurveToolbar = descriptor.type == UExpressionType.Curve; } else { @@ -312,9 +326,9 @@ private void UpdateSnapDiv() { SnapDivText = $"(1/{div})"; } - private void UpdateKey(){ + private void UpdateKey() { Key = userKey; - KeyText = "1="+MusicMath.KeysInOctave[userKey].Item1; + KeyText = "1=" + MusicMath.KeysInOctave[userKey].Item1; } public void OnXZoomed(Point position, double delta) { @@ -451,7 +465,7 @@ private Bitmap ResizePortrait(Bitmap Portrait, int PortraitHeight) { targetHeight = PortraitHeight; } int targetWidth = (int)Math.Round(targetHeight * Portrait.Size.Width / Portrait.Size.Height); - if(targetWidth == 0){ + if (targetWidth == 0) { targetWidth = 1; } return Portrait.CreateScaledBitmap(new PixelSize(targetWidth, targetHeight)); @@ -504,7 +518,7 @@ private void LoadPortrait(UPart? part, UProject? project) { Portrait = null; portraitSource = null; } else { - using (var stream = new MemoryStream(data)) { + using (var stream = new MemoryStream(data)) { Portrait = ResizePortrait(new Bitmap(stream), singer.PortraitHeight); portraitSource = singer.Portrait; } @@ -599,18 +613,18 @@ public void MoveSelection(int delta) { if (Selection.Move(delta)) { MessageBus.Current.SendMessage(new NotesSelectionEvent(Selection)); ScrollIntoView(Selection.Head!); - }; + } } public void ExtendSelection(int delta) { if (Selection.Resize(delta)) { MessageBus.Current.SendMessage(new NotesSelectionEvent(Selection)); ScrollIntoView(Selection.Head!); - }; + } } public void ExtendSelection(UNote note) { if (Selection.SelectTo(note)) { MessageBus.Current.SendMessage(new NotesSelectionEvent(Selection)); - }; + } } public void MoveCursor(int delta) { @@ -783,7 +797,7 @@ public void MergeSelectedNotes() { notes.Sort((a, b) => a.position.CompareTo(b.position)); //Ignore slur lyrics var mergedLyrics = String.Join("", notes.Select(x => x.lyric).Where(l => !l.StartsWith("+"))); - if(mergedLyrics == ""){ //If all notes are slur, the merged note is single slur note + if (mergedLyrics == "") { //If all notes are slur, the merged note is single slur note mergedLyrics = notes[0].lyric; } DocManager.Inst.StartUndoGroup("command.note.edit"); @@ -893,7 +907,7 @@ UNote toPlainNote(UNote note) { public async void PasteSelectedParams(Window window) { if (Part != null && DocManager.Inst.NotesClipboard != null && DocManager.Inst.NotesClipboard.Count > 0) { var selectedNotes = Selection.ToList(); - if(selectedNotes.Count == 0) { + if (selectedNotes.Count == 0) { return; } diff --git a/OpenUtau/Views/MainWindow.axaml b/OpenUtau/Views/MainWindow.axaml index 1e16d445c..3a76ca4f4 100644 --- a/OpenUtau/Views/MainWindow.axaml +++ b/OpenUtau/Views/MainWindow.axaml @@ -327,7 +327,7 @@ TickOffset="{Binding TracksViewModel.TickOffset}" SnapDiv="{Binding TracksViewModel.SnapDiv}" SnapTicks="{Binding TracksViewModel.SnapTicks}" - ShowBarNumber="True"/> + ShowBar="True"/>