Layers panel and scene strip: scene layers header with icons, per-type layer icons, PROGRAM badge and selected/live orange borders, scene name and hotkey overlays, Starting as default scene
- LeftPanel: SCENE LAYERS header with layer count; per-type blue icons; properties header gear; layers list min/max height; properties placeholder; list and properties indent - SceneThumbnailStrip: names/hotkeys over the tile; PROGRAM badge with blinking red dot on the selected tile; orange border on selected and live tiles; fixed the element border override that hid the style border; reflag tiles on load - LiveScene is now set on go-live, record start, and live transitions (it was never assigned, so IsLive never fired) - Startup stages the scene named Starting - DESIGN.md: color roles (red = YouTube/on-air/record, orange #ff8c3a = attention, amber = game audio/Super Chat) - Staged live tile uses background art instead of the green LIVE placeholder Not pushed.
This commit is contained in:
@@ -155,14 +155,10 @@ public partial class MainViewModel
|
||||
? OnAirImage
|
||||
: SceneThumbnailImage(scene);
|
||||
if (img == null)
|
||||
{
|
||||
var stagedLive = isStaged
|
||||
&& SceneCatalog.Is(scene.Name, SceneCatalog.Live);
|
||||
img = stagedLive ? null : LoadBackgroundImage(scene);
|
||||
}
|
||||
img = LoadBackgroundImage(scene);
|
||||
if (img != null)
|
||||
scene.Snapshot = img;
|
||||
else if (scene.Snapshot == null || (isStaged && OnAirImage == null))
|
||||
else if (scene.Snapshot == null)
|
||||
scene.Snapshot = CreatePlaceholderSnapshot(scene.Name);
|
||||
}
|
||||
}
|
||||
@@ -443,6 +439,7 @@ public partial class MainViewModel
|
||||
}
|
||||
SeedOnAirImage();
|
||||
OnPropertyChanged(nameof(ShowSourcesEmptyHint));
|
||||
OnPropertyChanged(nameof(LayerCount));
|
||||
}
|
||||
|
||||
// ─── Scene strip tile artwork (creator ruling 2026-10-10) ───
|
||||
|
||||
@@ -45,6 +45,7 @@ public partial class MainViewModel
|
||||
OnPropertyChanged(nameof(ShowEmptySceneHint));
|
||||
OnPropertyChanged(nameof(ShowPreviewPlaceholder));
|
||||
OnPropertyChanged(nameof(ShowSourcesEmptyHint));
|
||||
OnPropertyChanged(nameof(LayerCount));
|
||||
OnPropertyChanged(nameof(CanChangeBackground));
|
||||
OnPropertyChanged(nameof(CanAddWebcam));
|
||||
OnPropertyChanged(nameof(CanAddYouTubeChat));
|
||||
@@ -102,7 +103,10 @@ public partial class MainViewModel
|
||||
if (scene.IsHidden || scene == StagedScene) return;
|
||||
StagedScene = scene;
|
||||
if (IsLive)
|
||||
{
|
||||
LiveScene = scene;
|
||||
_transition.Start(SelectedTransitionType, TransitionDurationMs, null);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -186,6 +190,7 @@ public partial class MainViewModel
|
||||
}
|
||||
|
||||
public bool ShowEmptySceneHint => StagedScene != null && StagedScene.Elements.Count == 0;
|
||||
public int LayerCount => StagedScene?.Elements.Count(e => !e.IsZoomLayer) ?? 0;
|
||||
public bool ShowSourcesEmptyHint => StagedScene == null || StagedScene.Elements.Count == 0;
|
||||
|
||||
public bool IsCountdownAvailable => StagedScene != null
|
||||
|
||||
@@ -85,6 +85,7 @@ public partial class MainViewModel
|
||||
|
||||
OnPropertyChanged(nameof(ShowEmptySceneHint));
|
||||
OnPropertyChanged(nameof(ShowSourcesEmptyHint));
|
||||
OnPropertyChanged(nameof(LayerCount));
|
||||
UpdateActiveBackground();
|
||||
}
|
||||
|
||||
@@ -227,6 +228,7 @@ public partial class MainViewModel
|
||||
SelectedElement = source;
|
||||
OnPropertyChanged(nameof(ShowEmptySceneHint));
|
||||
OnPropertyChanged(nameof(ShowSourcesEmptyHint));
|
||||
OnPropertyChanged(nameof(LayerCount));
|
||||
UpdateActiveBackground();
|
||||
}
|
||||
|
||||
@@ -260,6 +262,7 @@ public partial class MainViewModel
|
||||
|
||||
OnPropertyChanged(nameof(ShowEmptySceneHint));
|
||||
OnPropertyChanged(nameof(ShowSourcesEmptyHint));
|
||||
OnPropertyChanged(nameof(LayerCount));
|
||||
UpdateActiveBackground();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,6 +65,7 @@ public partial class MainViewModel : ViewModelBase
|
||||
ResetHealth(StreamStatus.Offline);
|
||||
_audioMixer.StartLive(EncoderOptions.DefaultAudioPipeName);
|
||||
IsRecording = true;
|
||||
LiveScene = StagedScene;
|
||||
await _framePump.StartAsync();
|
||||
}
|
||||
|
||||
@@ -222,6 +223,7 @@ public partial class MainViewModel : ViewModelBase
|
||||
// only once frames flow. The liveChatId (snippet.liveChatId) does not exist
|
||||
// before that (2026-09-25 diagnosis), so chat resolution runs AFTER the pump
|
||||
// starts. Chat is non-fatal — if it fails, the stream continues without chat.
|
||||
LiveScene = StagedScene;
|
||||
await _framePump.StartAsync(); // never throws; failures log + surface via Failed
|
||||
|
||||
string? liveChatId = null;
|
||||
@@ -324,6 +326,7 @@ public partial class MainViewModel : ViewModelBase
|
||||
|
||||
StreamStatus = StreamStatus.Offline;
|
||||
IsRecording = false;
|
||||
LiveScene = null;
|
||||
// TASK 41: a stopped test broadcast forgets its drawer session (idempotent)
|
||||
// and drops the TEST face — a test never outlives its run.
|
||||
TestSession.EndTestSilently();
|
||||
|
||||
@@ -375,6 +375,7 @@ public partial class MainViewModel
|
||||
|
||||
OnPropertyChanged(nameof(ShowEmptySceneHint));
|
||||
OnPropertyChanged(nameof(ShowSourcesEmptyHint));
|
||||
OnPropertyChanged(nameof(LayerCount));
|
||||
UpdateActiveBackground();
|
||||
|
||||
// PreviewBitmapChanged fires only on the camera's first frame, so a config
|
||||
|
||||
@@ -438,7 +438,7 @@ public partial class MainViewModel : ViewModelBase
|
||||
{
|
||||
_isLoading = false;
|
||||
}
|
||||
StagedScene = Scenes.FirstOrDefault();
|
||||
StagedScene = Scenes.FirstOrDefault(s => SceneCatalog.Is(s.Name, SceneCatalog.Starting)) ?? Scenes.FirstOrDefault();
|
||||
UpdateActiveBackground();
|
||||
UpdateActiveChatBoxPreviews();
|
||||
UpdateActiveAlertBoxPreviews();
|
||||
|
||||
Reference in New Issue
Block a user