89fee6ca4a
Take two (2026-09-01) three confirmed defects, all from creator feedback + the
new resolver logging:
1. NO WEBCAM IN OUTPUT: WebcamSceneConfig.WebcamId carries the identity GUID;
CameraManager keys sessions by DEVICE id — GetLatestFrame(guid) returned null
forever, the compositor silently dropped the layer while the preview (bitmap
path) looked fine. The resolver logging added in 8dcaee0 caught it red-handed.
DeviceKeyForWebcam maps identity->device through the _webcam singleton (identity
mismatch / unknown ids pass through). Test: WebcamOutputKeyTests.
2. START BUTTON VANISHED AFTER STOP: my pills-clear ruling met the old
ShowPrimaryStartButton gate (needed IsConnected or a lit REC pill) — signed-out,
pills-off = blank top bar, no session reachable. Start is now ALWAYS the idle
face; unarmed Start records (local needs no account — no dead-end no-op); the
Sign In button folds into Start's context menu ('Sign in to YouTube', shown while
disconnected) with a dedicated SignInCommand. SessionTeardownTests extended:
stopped session must leave a reachable Start.
3. TOP BAR ORDER (creator spec): [sign light] REC [pill] [sign light] ON-AIR [pill]
— each reality lamp now sits in front of its own intent switch (was: two pills,
then two orphaned dots). Sign labels keep the shared StatusSignText style.
Tests: WebcamOutputKey, SessionTeardown, FramePump, WebcamMenuGate, GlobalHotkey,
BroadcastPullOut — 14/14 across the six classes, clean build 0 warnings.
114 lines
6.7 KiB
XML
114 lines
6.7 KiB
XML
<UserControl x:Class="ytLive.Controls.TopBar"
|
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
|
x:Name="TopBarRoot">
|
|
<!-- LOGO / BRAND — clicking opens the in-app About overlay -->
|
|
<Grid>
|
|
<Grid.ColumnDefinitions>
|
|
<ColumnDefinition Width="Auto"/>
|
|
<ColumnDefinition Width="*"/>
|
|
<ColumnDefinition Width="Auto"/>
|
|
</Grid.ColumnDefinitions>
|
|
|
|
<!-- Logo / Brand — clicking opens the in-app About overlay -->
|
|
<Button Grid.Column="0" Background="Transparent" BorderThickness="0" Cursor="Hand"
|
|
VerticalAlignment="Center" Margin="0,0,24,0" ToolTip="About LlamaCasty"
|
|
Command="{Binding OpenAboutCommand}">
|
|
<Button.Template>
|
|
<ControlTemplate TargetType="Button">
|
|
<ContentPresenter/>
|
|
</ControlTemplate>
|
|
</Button.Template>
|
|
<TextBlock FontSize="22" FontWeight="Bold">
|
|
<Run Text="LlamaCas" Foreground="#e0e0e0"/><Run Text="ty" Foreground="#e94560"/>
|
|
</TextBlock>
|
|
</Button>
|
|
|
|
<!-- Session controls (creator order 2026-09-01): [sign light] LABEL [pill] per
|
|
output — each reality lamp sits in front of its own intent switch. -->
|
|
<StackPanel Grid.Column="1" Orientation="Horizontal"
|
|
HorizontalAlignment="Center" VerticalAlignment="Center">
|
|
<!-- REC group: local recording needs no sign-in -->
|
|
<Ellipse Width="12" Height="12" VerticalAlignment="Center"
|
|
Fill="{Binding RecDotBrush}" Opacity="{Binding RecDotOpacity}"/>
|
|
<TextBlock Text="REC" Style="{StaticResource StatusSignText}"/>
|
|
<ToggleButton Style="{StaticResource PillToggle}" VerticalAlignment="Center"
|
|
Margin="6,0,16,0"
|
|
IsChecked="{Binding RecordPillOn}"/>
|
|
<!-- ON-AIR group: greyed out until signed in -->
|
|
<Ellipse Width="12" Height="12" VerticalAlignment="Center"
|
|
Fill="{Binding OnAirBrush}"/>
|
|
<TextBlock Text="ON-AIR" Style="{StaticResource StatusSignText}"/>
|
|
<ToggleButton Style="{StaticResource PillToggle}" VerticalAlignment="Center"
|
|
Margin="6,0,0,0"
|
|
IsChecked="{Binding OnAirPillOn}"
|
|
IsEnabled="{Binding CanToggleOnAir}"
|
|
ToolTip="{Binding AccountStatusLightToolTip}"/>
|
|
<Border Background="#8f1f1f" CornerRadius="3" Padding="6,1" Margin="8,0,0,0"
|
|
VerticalAlignment="Center"
|
|
Visibility="{Binding IsLivePrivate, Converter={StaticResource BoolToVis}}">
|
|
<TextBlock Text="PRIVATE" FontSize="11" FontWeight="Bold" Foreground="White"/>
|
|
</Border>
|
|
<TextBlock Text="{Binding LiveElapsedText}" Foreground="White" FontSize="14"
|
|
FontFamily="Consolas" VerticalAlignment="Center" Margin="14,0,0,0"
|
|
Visibility="{Binding IsLive, Converter={StaticResource BoolToVis}}"/>
|
|
</StackPanel>
|
|
|
|
<!-- Three-state top bar: logged-out / idle / live -->
|
|
<StackPanel Grid.Column="2" Orientation="Horizontal" VerticalAlignment="Center">
|
|
<!-- Status light -->
|
|
<Ellipse Width="8" Height="8" Margin="0,0,8,0" VerticalAlignment="Center"
|
|
ToolTip="{Binding AccountStatusLightToolTip}">
|
|
<Ellipse.Style>
|
|
<Style TargetType="Ellipse">
|
|
<Setter Property="Fill" Value="#ef4444"/>
|
|
<Style.Triggers>
|
|
<DataTrigger Binding="{Binding IsConnected}" Value="True">
|
|
<Setter Property="Fill" Value="#22c55e"/>
|
|
</DataTrigger>
|
|
<DataTrigger Binding="{Binding IsLive}" Value="True">
|
|
<Setter Property="Fill" Value="#e94560"/>
|
|
</DataTrigger>
|
|
</Style.Triggers>
|
|
</Style>
|
|
</Ellipse.Style>
|
|
</Ellipse>
|
|
<!-- Avatar (connected only); shows the channel initial when
|
|
the profile image is missing or fails to download -->
|
|
<Border Width="26" Height="26" CornerRadius="13" Background="#16213e" ClipToBounds="True"
|
|
Margin="0,0,8,0" VerticalAlignment="Center"
|
|
ToolTip="{Binding AccountDisplayName}"
|
|
Visibility="{Binding IsConnected, Converter={StaticResource BoolToVis}}">
|
|
<Grid>
|
|
<Image x:Name="AvatarImage" Stretch="UniformToFill"/>
|
|
<TextBlock x:Name="AvatarInitial" Foreground="#e0e0e0" FontSize="13"
|
|
FontWeight="Bold" HorizontalAlignment="Center" VerticalAlignment="Center"
|
|
Visibility="Collapsed"/>
|
|
</Grid>
|
|
</Border>
|
|
<!-- Primary Start button: the always-present idle action (sign-in moved
|
|
into its context menu — a bare bar after stopping was a dead end) -->
|
|
<Button Content="Start" Style="{StaticResource YtButton}"
|
|
Command="{Binding StartStreamCommand}"
|
|
Visibility="{Binding ShowPrimaryStartButton, Converter={StaticResource BoolToVis}}">
|
|
<Button.ContextMenu>
|
|
<ContextMenu>
|
|
<MenuItem Header="Sign in to YouTube" Command="{Binding SignInCommand}"
|
|
Visibility="{Binding IsConnected, Converter={StaticResource InverseBoolToVis}}"/>
|
|
<Separator/>
|
|
<MenuItem Header="Choose Record Folder" Command="{Binding ChooseRecordFolderCommand}"/>
|
|
<Separator/>
|
|
<MenuItem Header="Change Account" Command="{Binding ChangeAccountCommand}"/>
|
|
<Separator/>
|
|
<MenuItem Header="Logout" Command="{Binding LogoutCommand}"/>
|
|
</ContextMenu>
|
|
</Button.ContextMenu>
|
|
</Button>
|
|
<!-- End button (live OR recording) — the only button whose
|
|
label changes; "Stop Recording" while recording -->
|
|
<Button Content="{Binding EndButtonText}" Style="{StaticResource YtButton}"
|
|
Background="#333" Command="{Binding EndStreamCommand}"
|
|
Visibility="{Binding ShowEndStreamButton, Converter={StaticResource BoolToVis}}"/>
|
|
</StackPanel>
|
|
</Grid>
|
|
</UserControl> |