fix(preview): draw video layers, unlink Play from Loop, geometry last
Three reports from the creator, plus two bugs the real clip exposed. 1. A video layer was INVISIBLE in the preview. The pane's per-layer Image is Collapsed by default and switched on by a whitelist of DataTriggers; MediaSource matched none, so the clip decoded into a bitmap that nothing displayed. Same defect the alert box hit on 2026-09-26 and the scoreboard after it — now recorded at the top of docs/ai/ui-surface.md with a test that asserts the bound Image is Visible (`MediaPreviewDisplayTests`). 2. Play is no longer tied to Loop: the button is always enabled (creator ruling — Loop must not toggle Play). Loop still restarts a finished clip, which is the behaviour that was broken. 3. Geometry is now the LAST section of the properties list for every draggable element, with opacity at the top; the web source's duplicate geometry block is gone (it now uses the shared one, which had been sitting above the media panel). 4. The physical mouse-drag test is deleted. It injected real cursor movement, hijacked whatever machine ran it, and failed whenever the desktop was busy. The arithmetic it covered now lives in `Helpers/DragReorderMath.cs` — pure, and covered by `DragReorderMathTests` (threshold, up/down moves, no-op and out-of-range). The two persistence tests were already synthetic. 5. Real-clip check (creator's `test_greenscreen_moving_subject.mp4`, 1920×1080): the detector REJECTED it — a fully saturated green sits at value 1.0 and the detector's 0.98 brightness ceiling threw it away. White highlights are excluded by the saturation floor, which is the property that actually separates them, so the ceiling is gone. With it fixed, the clip is detected as green (high confidence), and keyed through our own pipe 97.8% of pixels go transparent. Measured: keying with the canonical #00B140 keys 0.0% of this clip — which is why the key colour comes from the measured screen. Tests: 567 passed, 0 failed, clean build 0 warnings. The gate's scope check flags `Assets/Videos/test_greenscreen_moving_subject.mp4` — the creator's working copy, deliberately not committed; the fixture lives under ytLive.Tests/Fixtures.
This commit is contained in:
+29
-48
@@ -474,35 +474,6 @@
|
||||
Style="{StaticResource PropertyHint}" Margin="10,2,0,0"/>
|
||||
</StackPanel>
|
||||
|
||||
<!-- Webcam geometry (TASK 57): under the vertical tier the camera
|
||||
keeps its OWN placement, so these fields edit the LIVE one —
|
||||
the placement the current tier shows. Gated on the VM's
|
||||
IsWebcamSelected (the same condition as the mirror/shape row
|
||||
above) so the webcam's border/background section stays the
|
||||
one panel bound to SelectedElement.IsWebcam. -->
|
||||
<StackPanel Visibility="{Binding SelectedElement.IsDraggable, Converter={StaticResource BoolToVis}}" Margin="0,0,0,8">
|
||||
<TextBlock Text="Geometry" Style="{StaticResource PropertyHeader}"/>
|
||||
<!-- TASK 61: the four fields share ONE fixed width — wide enough
|
||||
for the largest geometry value (1920, plus a typed sign) and
|
||||
no wider, so Y/H no longer stretch to fill the row. -->
|
||||
<DockPanel Margin="10,0,0,4" LastChildFill="False">
|
||||
<TextBlock DockPanel.Dock="Left" Text="X" Style="{StaticResource PropertyFieldLabel}" Width="16"/>
|
||||
<TextBox Text="{Binding SelectedElement.X, UpdateSourceTrigger=PropertyChanged, StringFormat=F0}"
|
||||
Style="{StaticResource YtTextBoxCompact}" Width="44"/>
|
||||
<TextBlock DockPanel.Dock="Left" Text=" Y" Style="{StaticResource PropertyFieldLabel}" Width="20" Margin="8,0,0,0"/>
|
||||
<TextBox Text="{Binding SelectedElement.Y, UpdateSourceTrigger=PropertyChanged, StringFormat=F0}"
|
||||
Style="{StaticResource YtTextBoxCompact}" Width="44"/>
|
||||
</DockPanel>
|
||||
<DockPanel Margin="10,0,0,4" LastChildFill="False">
|
||||
<TextBlock DockPanel.Dock="Left" Text="W" Style="{StaticResource PropertyFieldLabel}" Width="16"/>
|
||||
<TextBox Text="{Binding SelectedElement.Width, UpdateSourceTrigger=PropertyChanged, StringFormat=F0}"
|
||||
Style="{StaticResource YtTextBoxCompact}" Width="44"/>
|
||||
<TextBlock DockPanel.Dock="Left" Text=" H" Style="{StaticResource PropertyFieldLabel}" Width="20" Margin="8,0,0,0"/>
|
||||
<TextBox Text="{Binding SelectedElement.Height, UpdateSourceTrigger=PropertyChanged, StringFormat=F0}"
|
||||
Style="{StaticResource YtTextBoxCompact}" Width="44"/>
|
||||
</DockPanel>
|
||||
</StackPanel>
|
||||
|
||||
<!-- Webcam border config -->
|
||||
<StackPanel Visibility="{Binding SelectedElement.IsWebcam, Converter={StaticResource BoolToVis}}" Margin="0,0,0,8">
|
||||
<TextBlock Text="Border Color" Style="{StaticResource PropertyHeader}"/>
|
||||
@@ -690,23 +661,6 @@
|
||||
|
||||
<!-- Web source config -->
|
||||
<StackPanel Visibility="{Binding SelectedElement.IsWebSource, Converter={StaticResource BoolToVis}}" Margin="0,0,0,8">
|
||||
<TextBlock Text="Geometry" Style="{StaticResource PropertyHeader}"/>
|
||||
<DockPanel Margin="0,0,0,4">
|
||||
<TextBlock DockPanel.Dock="Left" Text="X" Style="{StaticResource PropertyFieldLabel}" Width="16"/>
|
||||
<TextBox Text="{Binding SelectedElement.X, UpdateSourceTrigger=PropertyChanged, StringFormat=F0}"
|
||||
Style="{StaticResource YtTextBoxCompact}"/>
|
||||
<TextBlock DockPanel.Dock="Left" Text=" Y" Style="{StaticResource PropertyFieldLabel}" Width="20" Margin="8,0,0,0"/>
|
||||
<TextBox Text="{Binding SelectedElement.Y, UpdateSourceTrigger=PropertyChanged, StringFormat=F0}"
|
||||
Style="{StaticResource YtTextBoxCompact}"/>
|
||||
</DockPanel>
|
||||
<DockPanel Margin="0,0,0,4">
|
||||
<TextBlock DockPanel.Dock="Left" Text="W" Style="{StaticResource PropertyFieldLabel}" Width="16"/>
|
||||
<TextBox Text="{Binding SelectedElement.Width, UpdateSourceTrigger=PropertyChanged, StringFormat=F0}"
|
||||
Style="{StaticResource YtTextBoxCompact}"/>
|
||||
<TextBlock DockPanel.Dock="Left" Text=" H" Style="{StaticResource PropertyFieldLabel}" Width="20" Margin="8,0,0,0"/>
|
||||
<TextBox Text="{Binding SelectedElement.Height, UpdateSourceTrigger=PropertyChanged, StringFormat=F0}"
|
||||
Style="{StaticResource YtTextBoxCompact}"/>
|
||||
</DockPanel>
|
||||
<TextBlock Text="URI" Style="{StaticResource PropertyHeader}"/>
|
||||
<DockPanel Margin="10,0,0,0">
|
||||
<Button DockPanel.Dock="Right" Style="{StaticResource IconButton}"
|
||||
@@ -733,9 +687,8 @@
|
||||
<Button DockPanel.Dock="Right" Content="▶ Play"
|
||||
Command="{Binding ReplayMediaCommand}"
|
||||
CommandParameter="{Binding SelectedElement}"
|
||||
IsEnabled="{Binding SelectedElement.CanPlayMedia}"
|
||||
Style="{StaticResource PropertyButton}" Padding="8,2" FontSize="11"
|
||||
ToolTip="Play the clip in the preview. While Loop is on the clip runs continuously, so there is nothing to start."/>
|
||||
ToolTip="Play this clip in the preview from the beginning"/>
|
||||
<TextBlock Text="Video File" Style="{StaticResource PropertyHeader}" VerticalAlignment="Center"/>
|
||||
</DockPanel>
|
||||
<Grid Margin="10,0,0,2">
|
||||
@@ -1085,6 +1038,34 @@
|
||||
Margin="0,4,0,0"
|
||||
Visibility="{Binding BackgroundSizeMismatch, Converter={StaticResource BoolToVis}}"/>
|
||||
</StackPanel>
|
||||
<!-- Geometry is ALWAYS the last section (creator ruling 2026-11-05):
|
||||
opacity sits at the top of the properties list and X/Y/W/H at the
|
||||
bottom, for every draggable element, so the eye can find them in
|
||||
the same place whatever layer is selected. For the webcam under
|
||||
the vertical tier this edits the LIVE placement (TASK 57). -->
|
||||
<StackPanel Visibility="{Binding SelectedElement.IsDraggable, Converter={StaticResource BoolToVis}}" Margin="0,0,0,8">
|
||||
<TextBlock Text="Geometry" Style="{StaticResource PropertyHeader}"/>
|
||||
<!-- TASK 61: the four fields share ONE fixed width — wide enough
|
||||
for the largest geometry value (1920, plus a typed sign) and
|
||||
no wider, so Y/H no longer stretch to fill the row. -->
|
||||
<DockPanel Margin="10,0,0,4" LastChildFill="False">
|
||||
<TextBlock DockPanel.Dock="Left" Text="X" Style="{StaticResource PropertyFieldLabel}" Width="16"/>
|
||||
<TextBox Text="{Binding SelectedElement.X, UpdateSourceTrigger=PropertyChanged, StringFormat=F0}"
|
||||
Style="{StaticResource YtTextBoxCompact}" Width="44"/>
|
||||
<TextBlock DockPanel.Dock="Left" Text=" Y" Style="{StaticResource PropertyFieldLabel}" Width="20" Margin="8,0,0,0"/>
|
||||
<TextBox Text="{Binding SelectedElement.Y, UpdateSourceTrigger=PropertyChanged, StringFormat=F0}"
|
||||
Style="{StaticResource YtTextBoxCompact}" Width="44"/>
|
||||
</DockPanel>
|
||||
<DockPanel Margin="10,0,0,4" LastChildFill="False">
|
||||
<TextBlock DockPanel.Dock="Left" Text="W" Style="{StaticResource PropertyFieldLabel}" Width="16"/>
|
||||
<TextBox Text="{Binding SelectedElement.Width, UpdateSourceTrigger=PropertyChanged, StringFormat=F0}"
|
||||
Style="{StaticResource YtTextBoxCompact}" Width="44"/>
|
||||
<TextBlock DockPanel.Dock="Left" Text=" H" Style="{StaticResource PropertyFieldLabel}" Width="20" Margin="8,0,0,0"/>
|
||||
<TextBox Text="{Binding SelectedElement.Height, UpdateSourceTrigger=PropertyChanged, StringFormat=F0}"
|
||||
Style="{StaticResource YtTextBoxCompact}" Width="44"/>
|
||||
</DockPanel>
|
||||
</StackPanel>
|
||||
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
|
||||
@@ -3,6 +3,7 @@ using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using ytLive.Helpers;
|
||||
using ytLive.Models;
|
||||
using ytLive.ViewModels;
|
||||
|
||||
@@ -178,8 +179,9 @@ public partial class LeftPanel : UserControl
|
||||
var position = e.GetPosition(listBox);
|
||||
if (!_isDragging)
|
||||
{
|
||||
if (Math.Abs(position.X - _dragStartPoint.X) < SystemParameters.MinimumHorizontalDragDistance &&
|
||||
Math.Abs(position.Y - _dragStartPoint.Y) < SystemParameters.MinimumVerticalDragDistance)
|
||||
if (!DragReorderMath.ExceededThreshold(
|
||||
position.X - _dragStartPoint.X, position.Y - _dragStartPoint.Y,
|
||||
SystemParameters.MinimumHorizontalDragDistance, SystemParameters.MinimumVerticalDragDistance))
|
||||
return;
|
||||
_isDragging = true;
|
||||
listBox.CaptureMouse();
|
||||
@@ -193,12 +195,10 @@ public partial class LeftPanel : UserControl
|
||||
return;
|
||||
|
||||
var targetIndex = listBox.Items.IndexOf(targetItem);
|
||||
if (targetIndex < 0) return;
|
||||
var landed = DragReorderMath.TryMove(items, _dragIndex, targetIndex);
|
||||
if (landed < 0) return;
|
||||
|
||||
var dragged = items[_dragIndex];
|
||||
items.RemoveAt(_dragIndex);
|
||||
items.Insert(targetIndex, dragged);
|
||||
_dragIndex = targetIndex;
|
||||
_dragIndex = landed;
|
||||
_lastHoveredItem = targetItem;
|
||||
_dragReordered = true;
|
||||
}
|
||||
|
||||
@@ -167,6 +167,17 @@
|
||||
<Setter Property="Visibility" Value="Visible"/>
|
||||
<Setter Property="Stretch" Value="Fill"/>
|
||||
</DataTrigger>
|
||||
|
||||
<!-- A video file layer is a frame source too. Without this trigger its Image stayed
|
||||
Collapsed and the clip was invisible in the preview while the decode ran — the
|
||||
same defect the alert box hit on 2026-09-26. -->
|
||||
<MultiDataTrigger>
|
||||
<MultiDataTrigger.Conditions>
|
||||
<Condition Binding="{Binding IsMediaSource}" Value="True"/>
|
||||
<Condition Binding="{Binding IsRectShape}" Value="True"/>
|
||||
</MultiDataTrigger.Conditions>
|
||||
<Setter Property="Visibility" Value="Visible"/>
|
||||
</MultiDataTrigger>
|
||||
<MultiDataTrigger>
|
||||
<MultiDataTrigger.Conditions>
|
||||
<Condition Binding="{Binding IsWebcam}" Value="True"/>
|
||||
|
||||
Reference in New Issue
Block a user