Kill stringly-typed command tokens: Add Source menu passes SourceType enum via x:Static CommandParameter (typos break the build, not silently add an Image); webcam gets its own AddWebcamCommand; AddSource keeps Enum.TryParse fallback; docs updated (65 tests passing)
This commit is contained in:
+5
-4
@@ -4,6 +4,7 @@
|
|||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
xmlns:vm="clr-namespace:ytLive.ViewModels"
|
xmlns:vm="clr-namespace:ytLive.ViewModels"
|
||||||
|
xmlns:models="clr-namespace:ytLive.Models"
|
||||||
xmlns:Helpers="clr-namespace:ytLive.Helpers"
|
xmlns:Helpers="clr-namespace:ytLive.Helpers"
|
||||||
mc:Ignorable="d"
|
mc:Ignorable="d"
|
||||||
Title="{Binding WindowTitle}" Height="720" Width="1280"
|
Title="{Binding WindowTitle}" Height="720" Width="1280"
|
||||||
@@ -249,13 +250,13 @@
|
|||||||
Click="AddSourceButton_Click">
|
Click="AddSourceButton_Click">
|
||||||
<Button.ContextMenu>
|
<Button.ContextMenu>
|
||||||
<ContextMenu DataContext="{Binding PlacementTarget.DataContext, RelativeSource={RelativeSource Self}}">
|
<ContextMenu DataContext="{Binding PlacementTarget.DataContext, RelativeSource={RelativeSource Self}}">
|
||||||
<MenuItem Header="Webcam" Command="{Binding AddSourceCommand}" CommandParameter="webcam"
|
<MenuItem Header="Webcam" Command="{Binding AddWebcamCommand}"
|
||||||
IsEnabled="{Binding CanAddWebcamToActiveScene}"
|
IsEnabled="{Binding CanAddWebcamToActiveScene}"
|
||||||
ToolTip="One webcam at a time — it's already in your stream"/>
|
ToolTip="One webcam at a time — it's already in your stream"/>
|
||||||
<MenuItem Header="Screen" Command="{Binding AddSourceCommand}" CommandParameter="screen"/>
|
<MenuItem Header="Screen" Command="{Binding AddSourceCommand}" CommandParameter="{x:Static models:SourceType.DisplayCapture}"/>
|
||||||
<MenuItem Header="Background" Command="{Binding AddSourceCommand}" CommandParameter="background"/>
|
<MenuItem Header="Background" Command="{Binding AddSourceCommand}" CommandParameter="{x:Static models:SourceType.Background}"/>
|
||||||
<MenuItem Header="Image" Command="{Binding AddImageCommand}"/>
|
<MenuItem Header="Image" Command="{Binding AddImageCommand}"/>
|
||||||
<MenuItem Header="Text" Command="{Binding AddSourceCommand}" CommandParameter="text"/>
|
<MenuItem Header="Text" Command="{Binding AddSourceCommand}" CommandParameter="{x:Static models:SourceType.TextOverlay}"/>
|
||||||
</ContextMenu>
|
</ContextMenu>
|
||||||
</Button.ContextMenu>
|
</Button.ContextMenu>
|
||||||
<Path Data="M12,4 L12,20 M4,12 L20,12" Stroke="#e94560" StrokeThickness="2"
|
<Path Data="M12,4 L12,20 M4,12 L20,12" Stroke="#e94560" StrokeThickness="2"
|
||||||
|
|||||||
@@ -525,6 +525,7 @@ public class MainViewModel : ViewModelBase
|
|||||||
public ICommand RemoveSceneCommand { get; }
|
public ICommand RemoveSceneCommand { get; }
|
||||||
public ICommand ToggleSceneVisibilityCommand { get; }
|
public ICommand ToggleSceneVisibilityCommand { get; }
|
||||||
public ICommand AddSourceCommand { get; }
|
public ICommand AddSourceCommand { get; }
|
||||||
|
public ICommand AddWebcamCommand { get; }
|
||||||
public ICommand AddImageCommand { get; }
|
public ICommand AddImageCommand { get; }
|
||||||
public ICommand RemoveSourceCommand { get; }
|
public ICommand RemoveSourceCommand { get; }
|
||||||
public ICommand ChangeWebcamCommand { get; }
|
public ICommand ChangeWebcamCommand { get; }
|
||||||
@@ -577,7 +578,8 @@ public class MainViewModel : ViewModelBase
|
|||||||
EditSceneCommand = new RelayCommand(scene => BeginEditScene(scene as Scene));
|
EditSceneCommand = new RelayCommand(scene => BeginEditScene(scene as Scene));
|
||||||
RemoveSceneCommand = new RelayCommand(scene => RemoveScene(scene as Scene));
|
RemoveSceneCommand = new RelayCommand(scene => RemoveScene(scene as Scene));
|
||||||
ToggleSceneVisibilityCommand = new RelayCommand(scene => ToggleSceneVisibility(scene as Scene));
|
ToggleSceneVisibilityCommand = new RelayCommand(scene => ToggleSceneVisibility(scene as Scene));
|
||||||
AddSourceCommand = new RelayCommand(type => AddSource(type as string));
|
AddSourceCommand = new RelayCommand(parameter => AddSource(parameter));
|
||||||
|
AddWebcamCommand = new RelayCommand(_ => _ = AddWebcamToActiveSceneAsync());
|
||||||
AddImageCommand = new RelayCommand(_ => AddImage());
|
AddImageCommand = new RelayCommand(_ => AddImage());
|
||||||
RemoveSourceCommand = new RelayCommand(element => RemoveElement(element as SceneElement));
|
RemoveSourceCommand = new RelayCommand(element => RemoveElement(element as SceneElement));
|
||||||
ChangeWebcamCommand = new RelayCommand(_ => _ = ChangeWebcamAsync(), _ => CanChangeWebcam);
|
ChangeWebcamCommand = new RelayCommand(_ => _ = ChangeWebcamAsync(), _ => CanChangeWebcam);
|
||||||
@@ -1060,25 +1062,14 @@ public class MainViewModel : ViewModelBase
|
|||||||
ActiveScene = Scenes.FirstOrDefault();
|
ActiveScene = Scenes.FirstOrDefault();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void AddSource(string? type)
|
private void AddSource(object? parameter)
|
||||||
{
|
{
|
||||||
var scene = ActiveScene;
|
var scene = ActiveScene;
|
||||||
if (scene == null) return;
|
if (scene == null) return;
|
||||||
|
|
||||||
if (string.Equals(type, "webcam", StringComparison.OrdinalIgnoreCase))
|
var sourceType = parameter is SourceType typed
|
||||||
{
|
? typed
|
||||||
_ = AddWebcamToActiveSceneAsync();
|
: Enum.TryParse<SourceType>(parameter?.ToString(), true, out var parsed) ? parsed : SourceType.Image;
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var sourceType = type?.ToLowerInvariant() switch
|
|
||||||
{
|
|
||||||
"screen" => SourceType.DisplayCapture,
|
|
||||||
"window" => SourceType.WindowCapture,
|
|
||||||
"background" => SourceType.Background,
|
|
||||||
"text" => SourceType.TextOverlay,
|
|
||||||
_ => SourceType.Image,
|
|
||||||
};
|
|
||||||
var baseName = sourceType switch
|
var baseName = sourceType switch
|
||||||
{
|
{
|
||||||
SourceType.DisplayCapture => "Screen",
|
SourceType.DisplayCapture => "Screen",
|
||||||
|
|||||||
@@ -95,7 +95,7 @@ C# / WPF (.NET 8) following MVVM:
|
|||||||
### Key patterns
|
### Key patterns
|
||||||
|
|
||||||
- `ViewModelBase.SetProperty<T>()` for property change notifications
|
- `ViewModelBase.SetProperty<T>()` for property change notifications
|
||||||
- `RelayCommand` for all button actions; commands gate on state (e.g. Start only when Offline)
|
- `RelayCommand` for all button actions; commands gate on state (e.g. Start only when Offline). **Typed `CommandParameter`s — no stringly-typed command tokens:** menu items that pick a source type pass the enum value itself (`CommandParameter="{x:Static models:SourceType.DisplayCapture}"`), so a typo breaks the build instead of silently adding an Image; `AddSource` still falls back to `Enum.TryParse<SourceType>(..., true)` for safety. The webcam item is its own `AddWebcamCommand` (it greys out via `CanAddWebcamToActiveScene` and isn't a `SourceType` — webcams are `WebcamSceneConfig`, not `Source` rows)
|
||||||
- ViewModels are constructed in XAML (`<vm:MainViewModel/>` as DataContext)
|
- ViewModels are constructed in XAML (`<vm:MainViewModel/>` as DataContext)
|
||||||
- Services are currently instantiated in MainViewModel's constructor — no DI container yet
|
- Services are currently instantiated in MainViewModel's constructor — no DI container yet
|
||||||
- Layout persists to SQLite (`Microsoft.Data.Sqlite`); scenes/sources/asset bytes stored in the DB, asset identity is a SHA-256 content hash (1:M reuse, no file paths — assets are always available)
|
- Layout persists to SQLite (`Microsoft.Data.Sqlite`); scenes/sources/asset bytes stored in the DB, asset identity is a SHA-256 content hash (1:M reuse, no file paths — assets are always available)
|
||||||
|
|||||||
Reference in New Issue
Block a user