feat(dock): the LIP-SYNC bay — offset, readout, zero detent

Unit 8. Re-houses the shipped TASK 22 offset in the dock:
- slider −200…+200 ms, snapping to 5 ms ticks (the existing property still clamps and persists)
- readable readout: "0 ms (aligned)" when right, signed otherwise ("+50 ms", "−120 ms")
- presets 0 / +50 / +100; 0 IS the detent — a button is easier to hit than a snap, and "the lip-sync
  is right" deserves one click.

Guard: SyncBayTests pins the binding, the readout, the presets and the snapping, and that the
offset stays the persisted property (no second source of truth).
This commit is contained in:
2026-10-30 13:16:19 -07:00
parent 7339df5fcd
commit a6f63f56f3
4 changed files with 85 additions and 6 deletions
+36 -4
View File
@@ -170,10 +170,42 @@
</Grid>
</Border>
<Border Grid.Column="3" Background="{StaticResource Brush.SurfaceContainerLow}" CornerRadius="{StaticResource Radius.Sm}" Margin="4,0,0,0" Padding="10,6">
<StackPanel>
<TextBlock Text="LIP-SYNC" FontSize="10" FontWeight="SemiBold" Foreground="{StaticResource Brush.OnSurfaceVariant}"/>
<TextBlock x:Name="SyncBayBody" Text="—" FontSize="11" Foreground="{StaticResource Brush.OnSurfaceDisabled}"/>
</StackPanel>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid Grid.Row="0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Border Background="{StaticResource Brush.SurfaceContainerHighest}" CornerRadius="{StaticResource Radius.Sm}" Padding="5,1">
<TextBlock Text="LIP-SYNC" FontSize="9" FontWeight="Bold" Foreground="{StaticResource Brush.OnSurfaceVariant}"/>
</Border>
<TextBlock Grid.Column="1" Margin="6,0,0,0" FontSize="10" VerticalAlignment="Center"
FontFamily="{StaticResource FontFamily.Mono}" TextAlignment="Right"
Foreground="{StaticResource Brush.Tertiary}"
Text="{Binding SyncOffsetLabel}"/>
</Grid>
<StackPanel Grid.Row="1" VerticalAlignment="Bottom">
<!-- A detent marker would need a custom track; the 0 ms preset is the
detent as a button, which is also easier to hit than a snap. -->
<Slider Minimum="-200" Maximum="200" SmallChange="5" LargeChange="25"
TickFrequency="5" IsSnapToTickEnabled="True"
Value="{Binding AudioSyncOffsetMs, Mode=TwoWay}" VerticalAlignment="Center"/>
<StackPanel Orientation="Horizontal" Margin="0,2,0,0" HorizontalAlignment="Right">
<Button Content="0" Width="26" Height="16" FontSize="9" Padding="0" Margin="0,0,4,0"
Style="{StaticResource YtButtonSecondary}" Click="SyncPreset_Click" Tag="0"/>
<Button Content="+50" Width="30" Height="16" FontSize="9" Padding="0" Margin="0,0,4,0"
Style="{StaticResource YtButtonSecondary}" Click="SyncPreset_Click" Tag="50"/>
<Button Content="+100" Width="34" Height="16" FontSize="9" Padding="0"
Style="{StaticResource YtButtonSecondary}" Click="SyncPreset_Click" Tag="100"/>
</StackPanel>
</StackPanel>
</Grid>
</Border>
</Grid>
+7
View File
@@ -39,6 +39,13 @@ public partial class AudioDock : UserControl
vm.OpenMicPickerCommand.Execute(null);
}
private void SyncPreset_Click(object sender, RoutedEventArgs e)
{
if (DataContext is not ViewModels.MainViewModel vm) return;
if (sender is Button { Tag: string tag } && int.TryParse(tag, out var ms))
vm.SetSyncOffset(ms);
}
private void TraxPlayPause_Click(object sender, RoutedEventArgs e)
{
if (DataContext is ViewModels.MainViewModel vm) vm.TraxLeftClick();