Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 58 additions & 0 deletions OpenUtau.Core/Commands/MoveTempoChangeCommand.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
using System;
using System.Collections.Generic;
using System.Linq;
using OpenUtau.Core.Ustx;

namespace OpenUtau.Core {
public class MoveTempoChangeCommand : AddTempoChangeCommand {
private readonly UTempo tempo;
private readonly int oldTick;
private readonly int newTick;

public MoveTempoChangeCommand(UProject project, UTempo tempo, int newTick)
: this(project, tempo, tempo.position, newTick) { }

private MoveTempoChangeCommand(
UProject project,
UTempo tempo,
int oldTick,
int newTick) : base(project) {
this.tempo = tempo;
this.oldTick = oldTick;
this.newTick = newTick;
}

public override void Execute() => MoveTempo(oldTick, newTick);

public override void Unexecute() => MoveTempo(newTick, oldTick);

private void MoveTempo(int fromTick, int toTick) {
var currentTempo = project.tempos.Contains(tempo) && tempo.position == fromTick
? tempo
: project.tempos.FirstOrDefault(candidate => candidate.position == fromTick);
if (currentTempo == null) {
throw new InvalidOperationException(
$"Cannot find tempo change at {fromTick} to move to {toTick}.");
}
currentTempo.position = toTick;
}

public override bool CanMerge(IList<UCommand> commands) {
var moves = commands.OfType<MoveTempoChangeCommand>().ToList();
return moves.Count == commands.Count &&
moves.All(command => command.tempo == tempo);
}

public override UCommand Merge(IList<UCommand> commands) {
var moves = commands.Cast<MoveTempoChangeCommand>().ToList();
return new MoveTempoChangeCommand(
project,
tempo,
moves.First().oldTick,
moves.Last().newTick);
}

public override string ToString() =>
$"Move tempo change {tempo.bpm} from {oldTick} to {newTick}";
}
}
64 changes: 64 additions & 0 deletions OpenUtau.Test/Core/Commands/MoveTempoChangeCommandTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
using System.Collections.Generic;
using OpenUtau.Core.Ustx;
using Xunit;

namespace OpenUtau.Core {
public class MoveTempoChangeCommandTest {
[Fact]
public void ExecuteAndUnexecute() {
var project = new UProject();
var tempo = new UTempo(480, 140);
project.tempos.Add(tempo);
var command = new MoveTempoChangeCommand(project, tempo, 720);

command.Execute();
Assert.Equal(720, tempo.position);

command.Unexecute();
Assert.Equal(480, tempo.position);

command.Execute();
Assert.Equal(720, tempo.position);
}

[Fact]
public void UnexecuteAfterTempoIsRecreated() {
var project = new UProject();
var tempo = new UTempo(480, 140);
project.tempos.Add(tempo);
var command = new MoveTempoChangeCommand(project, tempo, 720);
command.Execute();

project.tempos.Remove(tempo);
var recreatedTempo = new UTempo(720, 140);
project.tempos.Add(recreatedTempo);

command.Unexecute();
Assert.Equal(480, recreatedTempo.position);

command.Execute();
Assert.Equal(720, recreatedTempo.position);
}

[Fact]
public void MergeKeepsFirstAndLastPositions() {
var project = new UProject();
var tempo = new UTempo(480, 140);
project.tempos.Add(tempo);
var firstMove = new MoveTempoChangeCommand(project, tempo, 600);
firstMove.Execute();
var secondMove = new MoveTempoChangeCommand(project, tempo, 720);
secondMove.Execute();
var commands = new List<UCommand> { firstMove, secondMove };

Assert.True(secondMove.CanMerge(commands));
var merged = secondMove.Merge(commands);

merged.Unexecute();
Assert.Equal(480, tempo.position);

merged.Execute();
Assert.Equal(720, tempo.position);
}
}
}
44 changes: 41 additions & 3 deletions OpenUtau/Controls/TickBackground.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,20 @@
using Avalonia.Media;
using Avalonia.Media.Immutable;
using OpenUtau.App.ViewModels;
using OpenUtau.Core.Ustx;
using ReactiveUI;

namespace OpenUtau.App.Controls {
class TempoMarkerHighlightEvent {
public object DataContext { get; }
public UTempo? Tempo { get; }

public TempoMarkerHighlightEvent(object dataContext, UTempo? tempo) {
DataContext = dataContext;
Tempo = tempo;
}
}

class TickBackground : TemplatedControl {
private static readonly IDashStyle DashStyle = new ImmutableDashStyle(new double[] { 2, 4 }, 0);

Expand Down Expand Up @@ -46,6 +57,11 @@ class TickBackground : TemplatedControl {
nameof(ShowBar),
o => o.ShowBar,
(o, v) => o.ShowBar = v);
public static readonly DirectProperty<TickBackground, UTempo?> HighlightedTempoProperty =
AvaloniaProperty.RegisterDirect<TickBackground, UTempo?>(
nameof(HighlightedTempo),
o => o.HighlightedTempo,
(o, v) => o.HighlightedTempo = v);

public int Resolution {
get => _resolution;
Expand Down Expand Up @@ -76,6 +92,13 @@ public bool ShowBar {
get => _showBar;
set => SetAndRaise(ShowBarProperty, ref _showBar, value);
}
public UTempo? HighlightedTempo {
get => _highlightedTempo;
private set => SetAndRaise(
HighlightedTempoProperty,
ref _highlightedTempo,
value);
}

private int _resolution = 480;
private double _tickWidth;
Expand All @@ -84,6 +107,7 @@ public bool ShowBar {
private int _snapDiv;
private ObservableCollection<int>? _snapTicks;
private bool _showBar = true;
private UTempo? _highlightedTempo;

private Pen penBar;
private Pen penBeatUnit;
Expand All @@ -99,6 +123,12 @@ public TickBackground() {
.Subscribe(e => InvalidateVisual());
MessageBus.Current.Listen<TimeAxisChangedEvent>()
.Subscribe(e => InvalidateVisual());
MessageBus.Current.Listen<TempoMarkerHighlightEvent>()
.Subscribe(e => {
if (ReferenceEquals(e.DataContext, DataContext)) {
HighlightedTempo = e.Tempo;
}
});
}

protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) {
Expand All @@ -117,7 +147,8 @@ protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs chang
change.Property == TickWidthProperty ||
change.Property == TickOffsetProperty ||
change.Property == SnapDivProperty ||
change.Property == ShowBarProperty) {
change.Property == ShowBarProperty ||
change.Property == HighlightedTempoProperty) {
InvalidateVisual();
}
}
Expand Down Expand Up @@ -184,9 +215,16 @@ public override void Render(DrawingContext context) {

if (ShowBar) {
foreach (var tempo in project.tempos) {
bool highlighted = ReferenceEquals(tempo, HighlightedTempo);
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);
var markerPen = highlighted
? ThemeManager.AccentPen2Thickness2
: penDanshed;
var textBrush = highlighted
? ThemeManager.AccentBrush2
: ThemeManager.BarNumberBrush;
context.DrawLine(markerPen, new Point(x, 0), new Point(x, 24));
var textLayout = TextLayoutCache.Get(tempo.bpm.ToString("#0.00"), textBrush, 10);
using (var state = context.PushTransform(Matrix.CreateTranslation(x + 3, 0))) {
textLayout.Draw(context, new Point());
}
Expand Down
Loading