Build 729 (creator): - "press <esc> to return" loses the word "press"; the label is "Record / Go-Live". - The bar takes focus on "Build scenes" and Tab cycles between the two halves only (KeyboardNavigation.TabNavigation="Cycle"), never off into the window. - Entering Record / Go-Live starts with NOTHING chosen — the output switches are a fresh decision, not a remembered one. - Three states: nothing chosen [YouTube][Local MP4]; YouTube chosen [YouTube(red)][Live Stream][Private Stream]; Local MP4 chosen [Local MP4(red)][Start Recording] → [End Recording] while recording. Only the chosen output is red; the action chips never highlight. Choosing Local MP4 takes the left slot and the YouTube chip disappears with it, which is what "slides to the left, replacing it" describes. "Private Stream" is the private-visibility stream — the same thing this app has always called the test stream (creator's clarification, so the wording is deliberate, not a new broadcast type). Output chips are one style with a Tag-driven trigger (OutputChip + OutputChipIcon), so a chip's red face and its glyph colour can't drift apart; the one-alpha hit-area colour is a token, because DesignTokensTests rightly refuses literals in theme files. 680 pass, 0 warnings. Scope justification: Assets/starting-background.jpg is outside this unit — the creator's replacement art, still awaiting their visual check.
241 lines
18 KiB
XML
241 lines
18 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"
|
|
xmlns:helpers="clr-namespace:ytLive.Helpers"
|
|
x:Name="TopBarRoot">
|
|
<Grid>
|
|
<Grid.ColumnDefinitions>
|
|
<!-- 352 = the layers panel's right edge in the work area below: window margin 8 +
|
|
360px column = 368, minus this bar's own 16px padding. EDIT is right-aligned in
|
|
this column so the two edges line up exactly (creator ruling 2026-11-01). -->
|
|
<ColumnDefinition Width="352"/>
|
|
<ColumnDefinition Width="*"/>
|
|
<ColumnDefinition Width="Auto"/>
|
|
</Grid.ColumnDefinitions>
|
|
|
|
<!-- BRAND + EDIT. The gear moved to the preview's right rail, first tab (2026-11-01):
|
|
app-level actions live with the other pull-outs now. -->
|
|
<Grid Grid.Column="0">
|
|
<Grid.ColumnDefinitions>
|
|
<ColumnDefinition Width="*"/>
|
|
<ColumnDefinition Width="Auto"/>
|
|
</Grid.ColumnDefinitions>
|
|
|
|
<!-- LOGO / BRAND — left-click opens the in-app About overlay; right-click opens the app
|
|
menu (Report Bug / Feature Request / About). App Settings is no longer in this
|
|
menu: the gear rail tab opens the Settings drawer directly (creator ruling
|
|
2026-11-01). -->
|
|
<Button Grid.Column="0" Background="Transparent" BorderThickness="0" Cursor="Hand"
|
|
HorizontalAlignment="Left"
|
|
VerticalAlignment="Center" ToolTip="About LlamaCasty — right-click for the menu"
|
|
Command="{Binding OpenAboutCommand}"
|
|
PreviewMouseRightButtonDown="BrandWordmark_PreviewMouseRightButtonDown">
|
|
<Button.ContextMenu>
|
|
<ContextMenu DataContext="{Binding PlacementTarget.DataContext, RelativeSource={RelativeSource Self}}">
|
|
<MenuItem Header="Report Bug" Command="{Binding OpenBugCommand}"/>
|
|
<MenuItem Header="Feature Request" Command="{Binding OpenFeatureCommand}"/>
|
|
<MenuItem Header="About" Command="{Binding OpenAboutCommand}"/>
|
|
</ContextMenu>
|
|
</Button.ContextMenu>
|
|
<Button.Template>
|
|
<ControlTemplate TargetType="Button">
|
|
<ContentPresenter/>
|
|
</ControlTemplate>
|
|
</Button.Template>
|
|
<!-- Wordmark: "LlamaCas" + "ty" in YouTube red (creator ruling 2026-10-30 — never
|
|
re-colour it). The Run elements MUST stay adjacent: whitespace between inline
|
|
Runs renders as a space and splits the name into "LlamaCas ty". -->
|
|
<TextBlock FontSize="22" FontWeight="Bold" VerticalAlignment="Center">
|
|
<Run Text="LlamaCas" Foreground="{StaticResource Brush.OnSurface}"/><Run Text="ty" Foreground="{StaticResource Brush.Youtube}"/><Run
|
|
x:Name="BuildIdStamp" Text="{x:Static helpers:BuildStamp.Label}" FontSize="9"
|
|
BaselineAlignment="Superscript" Foreground="{StaticResource Brush.OnSurfaceVariant}"
|
|
ToolTipService.ToolTip="{x:Static helpers:BuildStamp.Label}"/><Run Text=" " FontSize="4"/>
|
|
</TextBlock>
|
|
</Button>
|
|
|
|
</Grid>
|
|
|
|
<!-- Session surface (top bar redesign): the bar is ONE surface rendered by
|
|
the FIRST decision — Record or Stream. Each world is the whole bar; the
|
|
other world never exists on screen. While running, the bar drops to a
|
|
reality line + End so nothing mid-session can be re-armed. -->
|
|
<StackPanel Grid.Column="1" Orientation="Horizontal"
|
|
HorizontalAlignment="Center" VerticalAlignment="Center">
|
|
<!-- THE decision (creator ruling 2026-11-12): [ Build Scenes ][ Record / Go Live ].
|
|
Build Scenes is the new edit mode — there is no separate EDIT button, so the
|
|
question "which world am I in?" and "can I edit?" cannot disagree. Clicking the
|
|
highlighted half re-enters it, which is how a creator gets back to the layers
|
|
during a session. -->
|
|
<!-- The segmented mode switcher (creator mock 2026-11-12): pill track, two equal halves,
|
|
active half on a lighter pill with off-white bold text and a coral icon. Hover tints
|
|
only the inactive half. -->
|
|
<Border Background="{StaticResource Brush.SegmentTrack}" CornerRadius="12" Padding="4"
|
|
VerticalAlignment="Center" Margin="0,0,12,0"
|
|
KeyboardNavigation.TabNavigation="Cycle" KeyboardNavigation.DirectionalNavigation="Cycle"
|
|
Visibility="{Binding IsBuildWorld, Converter={StaticResource BoolToVis}}">
|
|
<Grid>
|
|
<Grid.ColumnDefinitions>
|
|
<ColumnDefinition Width="*"/>
|
|
<ColumnDefinition Width="*"/>
|
|
</Grid.ColumnDefinitions>
|
|
<Button x:Name="BuildSegmentButton" Grid.Column="0" Style="{StaticResource SegmentHalf}"
|
|
Tag="{Binding IsBuildWorld}" Command="{Binding EnterBuildWorldCommand}"
|
|
ToolTip="Edit layers, scenes and webcam placement">
|
|
<StackPanel Orientation="Horizontal">
|
|
<Path Style="{StaticResource SegmentIcon}" Tag="{Binding IsBuildWorld}"
|
|
Data="M3,4h8v6H3V4zM13,4h8v6h-8V4zM3,12h8v8H3V12zM13,12h8v8h-8V12z"/>
|
|
<TextBlock Text="Build scenes" FontSize="12" Margin="6,0,0,0"
|
|
VerticalAlignment="Center"
|
|
Foreground="{Binding Foreground, RelativeSource={RelativeSource AncestorType=Button}}"/>
|
|
</StackPanel>
|
|
</Button>
|
|
<Button x:Name="OutputSegmentButton" Grid.Column="1" Style="{StaticResource SegmentHalf}"
|
|
Tag="{Binding IsOutputWorld}" Command="{Binding EnterOutputWorldCommand}"
|
|
ToolTip="Choose the output and start the session">
|
|
<StackPanel Orientation="Horizontal">
|
|
<Path Style="{StaticResource SegmentIcon}" Tag="{Binding IsOutputWorld}"
|
|
Data="M12,2a10,10 0 1,0 0,20a10,10 0 1,0 0,-20zM12,4a8,8 0 1,1 0,16a8,8 0 1,1 0,-16zM12,7.5a4.5,4.5 0 1,1 0,9a4.5,4.5 0 1,1 0,-9z"/>
|
|
<TextBlock Text="Record / Go-Live" FontSize="12" Margin="6,0,0,0"
|
|
VerticalAlignment="Center"
|
|
Foreground="{Binding Foreground, RelativeSource={RelativeSource AncestorType=Button}}"/>
|
|
</StackPanel>
|
|
</Button>
|
|
</Grid>
|
|
</Border>
|
|
<!-- IDLE: the mode switch + the action(s) of the armed world. The three
|
|
controls share one height (build 77 feedback) so they read as one
|
|
family — segments host, Start/Go Live, and Test all sit at 32. -->
|
|
<!-- THE OUTPUT STAGE (creator, builds 728/729). Three states:
|
|
1. nothing chosen: [YouTube] [Local MP4]
|
|
2. YouTube chosen: [YouTube(red)] [Live Stream] [Private Stream]
|
|
3. Local MP4 chosen: [Local MP4(red)] [Start Recording] -> [End Recording]
|
|
"Private Stream" is the private-visibility stream, which is what this app has always
|
|
called the test stream. The action chips are never highlighted; only the chosen output
|
|
is red. -->
|
|
<StackPanel Orientation="Horizontal" VerticalAlignment="Center"
|
|
Visibility="{Binding ShowOutputStage, Converter={StaticResource BoolToVis}}">
|
|
<TextBlock Text="Output:" FontSize="11" VerticalAlignment="Center"
|
|
Foreground="{StaticResource Brush.OnSurfaceVariant}" Margin="0,0,8,0"/>
|
|
<StackPanel Orientation="Vertical" VerticalAlignment="Center">
|
|
<Border Background="{StaticResource Brush.SegmentTrack}" CornerRadius="12" Padding="4"
|
|
VerticalAlignment="Center">
|
|
<StackPanel Orientation="Horizontal">
|
|
<!-- The chosen output, or the two choices while none is chosen. -->
|
|
<Button Style="{StaticResource OutputChip}" Tag="{Binding OnAirPillOn}"
|
|
Command="{Binding SelectYouTubeCommand}"
|
|
Visibility="{Binding YouTubeChipVisible, Converter={StaticResource BoolToVis}}"
|
|
ToolTip="Stream to YouTube">
|
|
<StackPanel Orientation="Horizontal">
|
|
<Path Style="{StaticResource OutputChipIcon}" Tag="{Binding OnAirPillOn}" Data="M23.498 6.186a3.016 3.016 0 0 0-2.122-2.136C19.505 3.545 12 3.545 12 3.545s-7.505 0-9.377.505A3.017 3.017 0 0 0 .502 6.186C0 8.07 0 12 0 12s0 3.93.502 5.814a3.016 3.016 0 0 0 2.122 2.136c1.871.505 9.376.505 9.376.505s7.505 0 9.377-.505a3.015 3.015 0 0 0 2.122-2.136C24 15.93 24 12 24 12s0-3.93-.502-5.814zM9.545 15.568V8.432L15.818 12l-6.273 3.568z"/>
|
|
<TextBlock Text="YouTube" FontSize="12" Margin="6,0,0,0" VerticalAlignment="Center"
|
|
Foreground="{Binding Foreground, RelativeSource={RelativeSource AncestorType=Button}}"/>
|
|
</StackPanel>
|
|
</Button>
|
|
<Button Style="{StaticResource OutputChip}" Tag="{Binding RecordPillOn}"
|
|
Command="{Binding SelectLocalMp4Command}"
|
|
Visibility="{Binding LocalMp4ChipVisible, Converter={StaticResource BoolToVis}}"
|
|
ToolTip="Record to a local MP4">
|
|
<StackPanel Orientation="Horizontal">
|
|
<Path Style="{StaticResource OutputChipIcon}" Tag="{Binding RecordPillOn}" Data="M12,2a10,10 0 1,0 0,20a10,10 0 1,0 0,-20zM12,4a8,8 0 1,1 0,16a8,8 0 1,1 0,-16zM12,7.5a4.5,4.5 0 1,1 0,9a4.5,4.5 0 1,1 0,-9z"/>
|
|
<TextBlock Text="Local MP4" FontSize="12" Margin="6,0,0,0" VerticalAlignment="Center"
|
|
Foreground="{Binding Foreground, RelativeSource={RelativeSource AncestorType=Button}}"/>
|
|
</StackPanel>
|
|
</Button>
|
|
<!-- YouTube's two actions: the real stream and the private (test) one. -->
|
|
<Button Style="{StaticResource OutputChip}" Command="{Binding GoLiveCommand}"
|
|
Visibility="{Binding ShowYouTubeSelected, Converter={StaticResource BoolToVis}}"
|
|
ToolTip="Go live on YouTube">
|
|
<StackPanel Orientation="Horizontal">
|
|
<Path Style="{StaticResource OutputChipIcon}" Data="M8,5v14l11,-7z"/>
|
|
<TextBlock Text="Live Stream" FontSize="12" Margin="6,0,0,0" VerticalAlignment="Center"
|
|
Foreground="{StaticResource Brush.SegmentTextInactive}"/>
|
|
</StackPanel>
|
|
</Button>
|
|
<Button Style="{StaticResource OutputChip}" Command="{Binding TestStreamCommand}"
|
|
Visibility="{Binding ShowYouTubeSelected, Converter={StaticResource BoolToVis}}"
|
|
ToolTip="Private test stream — nothing public is published">
|
|
<StackPanel Orientation="Horizontal">
|
|
<Path Style="{StaticResource OutputChipIcon}" Data="M7,10V7a5,5 0 0,1 10,0v3h1a1,1 0 0,1 1,1v9a1,1 0 0,1 -1,1H6a1,1 0 0,1 -1,-1v-9a1,1 0 0,1 1,-1h1zM9,10h6V7a3,3 0 0,0 -6,0v3z"/>
|
|
<TextBlock Text="Private Stream" FontSize="12" Margin="6,0,0,0" VerticalAlignment="Center"
|
|
Foreground="{StaticResource Brush.SegmentTextInactive}"/>
|
|
</StackPanel>
|
|
</Button>
|
|
<!-- Local MP4's one action, which becomes End while it records. -->
|
|
<Button Style="{StaticResource OutputChip}" Command="{Binding StartRecordingCommand}"
|
|
Visibility="{Binding ShowStartRecordingAction, Converter={StaticResource BoolToVis}}">
|
|
<StackPanel Orientation="Horizontal">
|
|
<Path Style="{StaticResource OutputChipIcon}" Data="M12,2a10,10 0 1,0 0,20a10,10 0 1,0 0,-20zM12,4a8,8 0 1,1 0,16a8,8 0 1,1 0,-16zM12,7.5a4.5,4.5 0 1,1 0,9a4.5,4.5 0 1,1 0,-9z"/>
|
|
<TextBlock Text="Start Recording" FontSize="12" Margin="6,0,0,0" VerticalAlignment="Center"
|
|
Foreground="{StaticResource Brush.SegmentTextInactive}"/>
|
|
</StackPanel>
|
|
</Button>
|
|
<Button Style="{StaticResource OutputChip}" Command="{Binding EndStreamCommand}"
|
|
Visibility="{Binding ShowEndRecordingAction, Converter={StaticResource BoolToVis}}">
|
|
<StackPanel Orientation="Horizontal">
|
|
<Path Style="{StaticResource OutputChipIcon}" Data="M6,6h12v12H6z"/>
|
|
<TextBlock Text="End Recording" FontSize="12" Margin="6,0,0,0" VerticalAlignment="Center"
|
|
Foreground="{StaticResource Brush.SegmentTextInactive}"/>
|
|
</StackPanel>
|
|
</Button>
|
|
</StackPanel>
|
|
</Border>
|
|
<TextBlock Text="<esc> to return" FontSize="10" HorizontalAlignment="Center"
|
|
Foreground="{StaticResource Brush.OnSurfaceVariant}" Margin="0,2,0,0"/>
|
|
</StackPanel>
|
|
<TextBlock Text="One output at a time" FontSize="11" VerticalAlignment="Center"
|
|
Foreground="{StaticResource Brush.OnSurfaceVariant}" Margin="8,0,0,0"/>
|
|
</StackPanel>
|
|
|
|
<!-- RUNNING: one reality line (dot + word + elapsed) and the stop -->
|
|
<StackPanel Orientation="Horizontal"
|
|
Visibility="{Binding RunningVisible, Converter={StaticResource BoolToVis}}">
|
|
<Ellipse Width="12" Height="12" VerticalAlignment="Center"
|
|
Fill="{Binding RunningDotBrush}"/>
|
|
<TextBlock Text="{Binding RunningText}" FontSize="16" FontWeight="Bold"
|
|
Foreground="White" Margin="8,0,0,0" VerticalAlignment="Center"/>
|
|
<TextBlock Text="{Binding LiveElapsedText}" Foreground="White" FontSize="14"
|
|
FontFamily="Consolas" VerticalAlignment="Center" Margin="8,0,0,0"/>
|
|
<!-- TASK 32·2: measured facts only — elapsed seconds and which attempt. No countdown:
|
|
YouTube's cutoff is not a constant we can promise. -->
|
|
<TextBlock x:Name="ReconnectDetail" Text="{Binding ReconnectDetailText}"
|
|
Foreground="{StaticResource Brush.Attention}" FontSize="13"
|
|
FontFamily="Consolas" VerticalAlignment="Center" Margin="10,0,0,0"
|
|
Visibility="{Binding IsReconnecting, Converter={StaticResource BoolToVis}}"/>
|
|
<Button Content="{Binding EndButtonText}" Style="{StaticResource YtButton}"
|
|
Background="{StaticResource Brush.Primary}" Margin="14,0,0,0"
|
|
Command="{Binding EndStreamCommand}"/>
|
|
</StackPanel>
|
|
</StackPanel>
|
|
|
|
<!-- Account zone: lives only in the Stream world. Signed out → Sign In;
|
|
signed in → a big avatar of the streaming account (build 77 feedback:
|
|
"make the user icon much bigger"). Record world has no YouTube
|
|
identity at all. -->
|
|
<StackPanel Grid.Column="2" Orientation="Horizontal" VerticalAlignment="Center">
|
|
<Button Content="Sign In" Style="{StaticResource YtButtonSecondary}"
|
|
Margin="0,0,8,0" Command="{Binding SignInCommand}"
|
|
ToolTip="Sign in to YouTube to go live."
|
|
Visibility="{Binding ShowSignInButton, Converter={StaticResource BoolToVis}}"/>
|
|
<Button Style="{StaticResource IconButton}" Margin="0,0,0,0"
|
|
ToolTip="{Binding AccountDisplayName}"
|
|
Visibility="{Binding ShowAvatarButton, Converter={StaticResource BoolToVis}}">
|
|
<Button.ContextMenu>
|
|
<ContextMenu>
|
|
<MenuItem Header="Change Account" Command="{Binding ChangeAccountCommand}"/>
|
|
<Separator/>
|
|
<MenuItem Header="Logout" Command="{Binding LogoutCommand}"/>
|
|
</ContextMenu>
|
|
</Button.ContextMenu>
|
|
<Border Width="40" Height="40" CornerRadius="20" Background="{StaticResource Brush.SurfaceContainer}"
|
|
ClipToBounds="True" BorderBrush="{StaticResource Brush.Outline}" BorderThickness="1">
|
|
<Grid>
|
|
<Image x:Name="AvatarImage" Stretch="UniformToFill"/>
|
|
<TextBlock x:Name="AvatarInitial" Foreground="{StaticResource Brush.OnSurface}" FontSize="20"
|
|
FontWeight="Bold" HorizontalAlignment="Center" VerticalAlignment="Center"
|
|
Visibility="Collapsed"/>
|
|
</Grid>
|
|
</Border>
|
|
</Button>
|
|
</StackPanel>
|
|
</Grid>
|
|
</UserControl> |