Transitions in edit mode and on output: program transitions run while recording or streaming; the preview animates the outgoing picture (fade/move fade, wipe shrinks) in every mode

Test: PreviewTransitionTests (with the transition, wipe, and brand logo tests).
This commit is contained in:
2026-10-27 10:20:53 -07:00
parent 841c905c06
commit 7da287244b
4 changed files with 121 additions and 1 deletions
+2
View File
@@ -71,6 +71,8 @@
Visibility="{Binding BackgroundVisible, Converter={StaticResource BoolToVis}}"/>
<Image Source="{Binding ActiveBackgroundImage}" Stretch="UniformToFill"/>
</Grid>
<Image x:Name="TransitionOverlay" Width="1920" Height="1080" Stretch="Fill"
IsHitTestVisible="False" Visibility="Collapsed"/>
<Canvas x:Name="OverlayCanvas" Width="1920" Height="1080">
<ItemsControl ItemsSource="{Binding StagedScene.Elements}"
Width="1920" Height="1080">
+46
View File
@@ -36,14 +36,60 @@ public partial class PreviewPane : UserControl
private void PreviewPane_DataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
{
if (e.OldValue is MainViewModel oldVm)
{
oldVm.PropertyChanged -= ViewModel_PropertyChanged;
oldVm.PreviewTransition -= PlayPreviewTransition;
}
if (e.NewValue is MainViewModel newVm)
{
newVm.PropertyChanged += ViewModel_PropertyChanged;
newVm.PreviewTransition += PlayPreviewTransition;
UpdateZoomBox();
}
}
/// <summary>Animates the outgoing picture away over the new scene: a fade for Fade and Move,
/// a right-to-left shrink for Wipe. Cut plays nothing.</summary>
private void PlayPreviewTransition(ytLive.Services.TransitionType type, double milliseconds)
{
TransitionOverlay.BeginAnimation(System.Windows.UIElement.OpacityProperty, null);
TransitionOverlay.Clip = null;
if (type == ytLive.Services.TransitionType.Cut || milliseconds <= 0)
{
TransitionOverlay.Visibility = Visibility.Collapsed;
return;
}
var snapshot = new System.Windows.Media.Imaging.RenderTargetBitmap(1920, 1080, 96, 96,
System.Windows.Media.PixelFormats.Pbgra32);
snapshot.Render(PreviewCanvas);
TransitionOverlay.Source = snapshot;
TransitionOverlay.Opacity = 1;
TransitionOverlay.Visibility = Visibility.Visible;
var duration = TimeSpan.FromMilliseconds(milliseconds);
if (type == ytLive.Services.TransitionType.Wipe)
{
var clip = new System.Windows.Media.RectangleGeometry(new Rect(0, 0, 1920, 1080));
TransitionOverlay.Clip = clip;
var reveal = new System.Windows.Media.Animation.RectAnimation(new Rect(0, 0, 0, 1080), duration)
{
FillBehavior = System.Windows.Media.Animation.FillBehavior.Stop,
};
reveal.Completed += (_, _) => TransitionOverlay.Visibility = Visibility.Collapsed;
clip.BeginAnimation(System.Windows.Media.RectangleGeometry.RectProperty, reveal);
}
else
{
var fade = new System.Windows.Media.Animation.DoubleAnimation(1, 0, duration)
{
FillBehavior = System.Windows.Media.Animation.FillBehavior.Stop,
};
fade.Completed += (_, _) => TransitionOverlay.Visibility = Visibility.Collapsed;
TransitionOverlay.BeginAnimation(System.Windows.UIElement.OpacityProperty, fade);
}
}
private void ViewModel_PropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e)
{
if (e.PropertyName == nameof(MainViewModel.IsZoomActive) || e.PropertyName == nameof(MainViewModel.ZoomState) || e.PropertyName == nameof(MainViewModel.ZoomShape))