Files
LlamaCasty/ViewModels/MainViewModel.TopBar.cs
T
gramps caf5d6130d feat(topbar): the output stage's three states, and keyboard entry into the bar
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.
2026-11-12 16:35:16 -08:00

119 lines
5.4 KiB
C#

using System.Windows.Input;
using ytLive.Helpers;
namespace ytLive.ViewModels;
/// <summary>Which half of the top bar's dual segment is in force.</summary>
public enum BarWorld
{
/// <summary>Build Scenes — the edit surface. The default at launch (creator ruling 2026-11-12).</summary>
Build,
/// <summary>Record / Go Live — the output surface: which output, and the one action that starts it.</summary>
Output,
}
/// <summary>
/// The top bar's one decision (creator ruling 2026-11-12): <c>[ Build Scenes ][ Record / Go Live ]</c>.
/// Build Scenes <b>is</b> edit mode — there is no separate EDIT toggle any more, so "may I edit?" and
/// "which world am I in?" cannot disagree.
///
/// <para>Clicking the highlighted segment is not a no-op: it re-enters that world, which matters while a
/// session runs because Build Scenes is the only way back to the layers (see
/// <see cref="LockEditWhileLive"/>).</para>
/// </summary>
public partial class MainViewModel
{
private BarWorld _barWorld = BarWorld.Build;
public BarWorld BarWorld => _barWorld;
public bool IsBuildWorld => _barWorld == BarWorld.Build;
public bool IsOutputWorld => _barWorld == BarWorld.Output;
/// <summary>The output stage belongs to the Record / Go Live world, and only while nothing is
/// running — a live session cannot change its output, so its switch is not offered.</summary>
public bool ShowOutputStage => IsOutputWorld && IsOffline && !IsRecording;
// ─── The output stage's three states (creator, builds 728/729) ───
// 1. nothing chosen: [YouTube] [Local MP4]
// 2. YouTube chosen: [YouTube(red)] [Live Stream] [Private Stream] (Private = the test run)
// 3. Local MP4 chosen: [Local MP4(red)] [Start Recording] -> [End Recording] while recording
public bool ShowOutputChoice => ShowOutputStage && !RecordPillOn && !OnAirPillOn;
public bool ShowYouTubeSelected => ShowOutputStage && OnAirPillOn;
public bool ShowLocalMp4Selected => ShowOutputStage && RecordPillOn;
/// <summary>The YouTube chip shows while nothing is chosen and while YouTube IS the choice; once
/// Local MP4 is chosen the MP4 chip replaces it in the left slot (creator, build 729).</summary>
public bool YouTubeChipVisible => ShowOutputChoice || ShowYouTubeSelected;
public bool LocalMp4ChipVisible => ShowOutputChoice || ShowLocalMp4Selected;
public bool ShowStartRecordingAction => ShowLocalMp4Selected && !IsRecording;
public bool ShowEndRecordingAction => ShowLocalMp4Selected && IsRecording;
public ICommand SelectYouTubeCommand { get; private set; } = null!;
public ICommand SelectLocalMp4Command { get; private set; } = null!;
public ICommand StartRecordingCommand { get; private set; } = null!;
public ICommand TestStreamCommand { get; private set; } = null!;
public ICommand GoLiveCommand { get; private set; } = null!;
public ICommand EnterBuildWorldCommand { get; private set; } = null!;
public ICommand EnterOutputWorldCommand { get; private set; } = null!;
private void InitializeWorldCommands()
{
SelectYouTubeCommand = new RelayCommand(_ => OnAirPillOn = true, _ => ShowOutputChoice);
SelectLocalMp4Command = new RelayCommand(_ => RecordPillOn = true, _ => ShowOutputChoice);
StartRecordingCommand = new RelayCommand(_ => BeginRecordOnly(), _ => ShowStartRecordingAction);
// A signed-out press starts the sign-in flow instead of sitting dead (no-dead-ends rule).
TestStreamCommand = new RelayCommand(_ =>
{
if (!IsConnected) { _ = SignInAsync(); return; }
BeginTestStream();
}, _ => ShowYouTubeSelected);
GoLiveCommand = new RelayCommand(_ =>
{
if (!IsConnected) { _ = SignInAsync(); return; }
StartSession();
}, _ => ShowYouTubeSelected);
EnterBuildWorldCommand = new RelayCommand(_ => EnterWorld(BarWorld.Build));
EnterOutputWorldCommand = new RelayCommand(_ => EnterWorld(BarWorld.Output));
}
/// <summary>Everything the output stage shows or hides, in one place so no caller can forget
/// one of them.</summary>
internal void RaiseOutputStage()
{
OnPropertyChanged(nameof(ShowOutputStage));
OnPropertyChanged(nameof(ShowOutputChoice));
OnPropertyChanged(nameof(ShowYouTubeSelected));
OnPropertyChanged(nameof(YouTubeChipVisible));
OnPropertyChanged(nameof(LocalMp4ChipVisible));
OnPropertyChanged(nameof(ShowLocalMp4Selected));
OnPropertyChanged(nameof(ShowStartRecordingAction));
OnPropertyChanged(nameof(ShowEndRecordingAction));
}
/// <summary>Switches worlds. Re-entering the current one still re-asserts its edit state, which is
/// what lets a creator take EDIT back mid-show by clicking Build Scenes.</summary>
internal void EnterWorld(BarWorld world)
{
_barWorld = world;
// Entering a world starts with NOTHING chosen inside it (creator, build 729): the output
// switches are a fresh decision each time, not a remembered one.
if (world == BarWorld.Output)
{
RecordPillOn = false;
OnAirPillOn = false;
}
OnPropertyChanged(nameof(BarWorld));
OnPropertyChanged(nameof(IsBuildWorld));
OnPropertyChanged(nameof(IsOutputWorld));
EditingActive = world == BarWorld.Build;
OnPropertyChanged(nameof(ShowOutputStage));
RaiseOutputStage();
}
}