2026-05-08 07:19:31 -04:00
|
|
|
using System.Windows;
|
|
|
|
|
using TeamsISO.App.Services;
|
2026-05-07 11:39:46 -04:00
|
|
|
using TeamsISO.Engine.Controller;
|
|
|
|
|
using TeamsISO.Engine.Domain;
|
|
|
|
|
|
|
|
|
|
namespace TeamsISO.App.ViewModels;
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
feat(ui): rebuild MainWindow with Stone-theme design system
Adopts the design language from Dammyjay93/interface-design: warm Stone neutrals, accent orange (#EA580C), borders-only depth (no shadows), 8px spacing grid, all-caps section labels, mono typography for machine names and timecodes.
Themes/StoneTheme.xaml is the single source of truth for design tokens (color brushes, typography styles, spacing) plus restyled control templates (Button, TextBox, ComboBox, ComboBoxItem, CheckBox, DataGrid + DataGridColumnHeader / DataGridRow / DataGridCell, ScrollBar). MainWindow consumes the tokens and is laid out with a header (title + status pill), section-headed Settings sidebar (Output Format / NDI Network / Display), card-wrapped participant list, and a mono status footer.
Settings sidebar surfaces the new NDI group configuration (discovery + output) and a Hide-(Local) checkbox. The latter filters the user's own self-preview from the participants list at the MainViewModel layer (HideLocalSelf=true by default) so operators don't accidentally route their own preview as an ISO. Apply Changes round-trips both FrameProcessingSettings and NdiGroupSettings through the controller in one go.
2026-05-07 23:58:02 -04:00
|
|
|
/// Bindings for the global settings panel: framerate, resolution, aspect, audio,
|
|
|
|
|
/// NDI groups (discovery + output), and the participant-list hide-Local toggle.
|
2026-05-07 11:39:46 -04:00
|
|
|
/// </summary>
|
|
|
|
|
public sealed class GlobalSettingsViewModel : ObservableObject
|
|
|
|
|
{
|
|
|
|
|
private readonly IIsoController _controller;
|
feat(ui): toast feedback for settings actions; refresh _NEXT.md
Adds a small auto-dismissing pill notification at the bottom-center of the participants area: 'Settings saved' on Apply Changes, 'Transcoder topology applied — restart Teams to take effect' after the one-click NDI groups setup. ToastViewModel owns its own DispatcherTimer and resets the dismissal countdown on successive calls, so the most recent message is always the one visible. Hooked into MainViewModel and threaded into GlobalSettingsViewModel via constructor injection.
_NEXT.md rewritten to reflect the May 2026 hardening pass: separates engine / UI / networking / Phase E.1 / diagnostics / CI / tests sections, lists every shipped item, and re-prioritizes the remaining work (Phase E.2-E.3 embedded Teams, code-signing the MSI, refresh-discovery affordance, output thumbnail previews, settings panel UX, auto-disable on departure, operator presets).
2026-05-09 09:30:04 -04:00
|
|
|
private readonly ToastViewModel? _toast;
|
2026-05-07 11:39:46 -04:00
|
|
|
private TargetFramerate _framerate;
|
|
|
|
|
private TargetResolution _resolution;
|
|
|
|
|
private AspectMode _aspect;
|
|
|
|
|
private AudioMode _audio;
|
feat(ui): rebuild MainWindow with Stone-theme design system
Adopts the design language from Dammyjay93/interface-design: warm Stone neutrals, accent orange (#EA580C), borders-only depth (no shadows), 8px spacing grid, all-caps section labels, mono typography for machine names and timecodes.
Themes/StoneTheme.xaml is the single source of truth for design tokens (color brushes, typography styles, spacing) plus restyled control templates (Button, TextBox, ComboBox, ComboBoxItem, CheckBox, DataGrid + DataGridColumnHeader / DataGridRow / DataGridCell, ScrollBar). MainWindow consumes the tokens and is laid out with a header (title + status pill), section-headed Settings sidebar (Output Format / NDI Network / Display), card-wrapped participant list, and a mono status footer.
Settings sidebar surfaces the new NDI group configuration (discovery + output) and a Hide-(Local) checkbox. The latter filters the user's own self-preview from the participants list at the MainViewModel layer (HideLocalSelf=true by default) so operators don't accidentally route their own preview as an ISO. Apply Changes round-trips both FrameProcessingSettings and NdiGroupSettings through the controller in one go.
2026-05-07 23:58:02 -04:00
|
|
|
private string _discoveryGroups;
|
|
|
|
|
private string _outputGroups;
|
|
|
|
|
private bool _hideLocalSelf = true;
|
2026-05-07 11:39:46 -04:00
|
|
|
|
feat(ui): toast feedback for settings actions; refresh _NEXT.md
Adds a small auto-dismissing pill notification at the bottom-center of the participants area: 'Settings saved' on Apply Changes, 'Transcoder topology applied — restart Teams to take effect' after the one-click NDI groups setup. ToastViewModel owns its own DispatcherTimer and resets the dismissal countdown on successive calls, so the most recent message is always the one visible. Hooked into MainViewModel and threaded into GlobalSettingsViewModel via constructor injection.
_NEXT.md rewritten to reflect the May 2026 hardening pass: separates engine / UI / networking / Phase E.1 / diagnostics / CI / tests sections, lists every shipped item, and re-prioritizes the remaining work (Phase E.2-E.3 embedded Teams, code-signing the MSI, refresh-discovery affordance, output thumbnail previews, settings panel UX, auto-disable on departure, operator presets).
2026-05-09 09:30:04 -04:00
|
|
|
public GlobalSettingsViewModel(IIsoController controller, ToastViewModel? toast = null)
|
2026-05-07 11:39:46 -04:00
|
|
|
{
|
|
|
|
|
_controller = controller;
|
feat(ui): toast feedback for settings actions; refresh _NEXT.md
Adds a small auto-dismissing pill notification at the bottom-center of the participants area: 'Settings saved' on Apply Changes, 'Transcoder topology applied — restart Teams to take effect' after the one-click NDI groups setup. ToastViewModel owns its own DispatcherTimer and resets the dismissal countdown on successive calls, so the most recent message is always the one visible. Hooked into MainViewModel and threaded into GlobalSettingsViewModel via constructor injection.
_NEXT.md rewritten to reflect the May 2026 hardening pass: separates engine / UI / networking / Phase E.1 / diagnostics / CI / tests sections, lists every shipped item, and re-prioritizes the remaining work (Phase E.2-E.3 embedded Teams, code-signing the MSI, refresh-discovery affordance, output thumbnail previews, settings panel UX, auto-disable on departure, operator presets).
2026-05-09 09:30:04 -04:00
|
|
|
_toast = toast;
|
2026-05-07 11:39:46 -04:00
|
|
|
var current = controller.GlobalSettings;
|
|
|
|
|
_framerate = current.Framerate;
|
|
|
|
|
_resolution = current.Resolution;
|
|
|
|
|
_aspect = current.Aspect;
|
|
|
|
|
_audio = current.Audio;
|
feat(ui): rebuild MainWindow with Stone-theme design system
Adopts the design language from Dammyjay93/interface-design: warm Stone neutrals, accent orange (#EA580C), borders-only depth (no shadows), 8px spacing grid, all-caps section labels, mono typography for machine names and timecodes.
Themes/StoneTheme.xaml is the single source of truth for design tokens (color brushes, typography styles, spacing) plus restyled control templates (Button, TextBox, ComboBox, ComboBoxItem, CheckBox, DataGrid + DataGridColumnHeader / DataGridRow / DataGridCell, ScrollBar). MainWindow consumes the tokens and is laid out with a header (title + status pill), section-headed Settings sidebar (Output Format / NDI Network / Display), card-wrapped participant list, and a mono status footer.
Settings sidebar surfaces the new NDI group configuration (discovery + output) and a Hide-(Local) checkbox. The latter filters the user's own self-preview from the participants list at the MainViewModel layer (HideLocalSelf=true by default) so operators don't accidentally route their own preview as an ISO. Apply Changes round-trips both FrameProcessingSettings and NdiGroupSettings through the controller in one go.
2026-05-07 23:58:02 -04:00
|
|
|
|
|
|
|
|
var groups = controller.GroupSettings;
|
|
|
|
|
_discoveryGroups = groups.DiscoveryGroups ?? string.Empty;
|
|
|
|
|
_outputGroups = groups.OutputGroups ?? string.Empty;
|
|
|
|
|
|
2026-05-07 11:39:46 -04:00
|
|
|
ApplyCommand = new AsyncRelayCommand(ApplyAsync);
|
2026-05-08 07:19:31 -04:00
|
|
|
ApplyTranscoderTopologyCommand = new AsyncRelayCommand(ApplyTranscoderTopologyAsync);
|
2026-05-07 11:39:46 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public IEnumerable<TargetFramerate> AvailableFramerates => Enum.GetValues<TargetFramerate>();
|
|
|
|
|
public IEnumerable<TargetResolution> AvailableResolutions => Enum.GetValues<TargetResolution>();
|
|
|
|
|
public IEnumerable<AspectMode> AvailableAspectModes => Enum.GetValues<AspectMode>();
|
|
|
|
|
public IEnumerable<AudioMode> AvailableAudioModes => Enum.GetValues<AudioMode>();
|
|
|
|
|
|
|
|
|
|
public TargetFramerate Framerate { get => _framerate; set => SetField(ref _framerate, value); }
|
|
|
|
|
public TargetResolution Resolution { get => _resolution; set => SetField(ref _resolution, value); }
|
|
|
|
|
public AspectMode Aspect { get => _aspect; set => SetField(ref _aspect, value); }
|
|
|
|
|
public AudioMode Audio { get => _audio; set => SetField(ref _audio, value); }
|
|
|
|
|
|
feat(ui): rebuild MainWindow with Stone-theme design system
Adopts the design language from Dammyjay93/interface-design: warm Stone neutrals, accent orange (#EA580C), borders-only depth (no shadows), 8px spacing grid, all-caps section labels, mono typography for machine names and timecodes.
Themes/StoneTheme.xaml is the single source of truth for design tokens (color brushes, typography styles, spacing) plus restyled control templates (Button, TextBox, ComboBox, ComboBoxItem, CheckBox, DataGrid + DataGridColumnHeader / DataGridRow / DataGridCell, ScrollBar). MainWindow consumes the tokens and is laid out with a header (title + status pill), section-headed Settings sidebar (Output Format / NDI Network / Display), card-wrapped participant list, and a mono status footer.
Settings sidebar surfaces the new NDI group configuration (discovery + output) and a Hide-(Local) checkbox. The latter filters the user's own self-preview from the participants list at the MainViewModel layer (HideLocalSelf=true by default) so operators don't accidentally route their own preview as an ISO. Apply Changes round-trips both FrameProcessingSettings and NdiGroupSettings through the controller in one go.
2026-05-07 23:58:02 -04:00
|
|
|
/// <summary>NDI discovery group(s) — comma-separated. Empty = default (Public).</summary>
|
|
|
|
|
public string DiscoveryGroups { get => _discoveryGroups; set => SetField(ref _discoveryGroups, value); }
|
|
|
|
|
|
|
|
|
|
/// <summary>NDI output group(s) — comma-separated. Empty = default (Public).</summary>
|
|
|
|
|
public string OutputGroups { get => _outputGroups; set => SetField(ref _outputGroups, value); }
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Hide the user's own self-preview ("(Local)") from the participants list.
|
|
|
|
|
/// On by default — operators rarely want to ISO-route their own preview.
|
|
|
|
|
/// Read by <see cref="MainViewModel"/> when filtering the list it presents.
|
|
|
|
|
/// </summary>
|
|
|
|
|
public bool HideLocalSelf { get => _hideLocalSelf; set => SetField(ref _hideLocalSelf, value); }
|
|
|
|
|
|
2026-05-07 11:39:46 -04:00
|
|
|
public AsyncRelayCommand ApplyCommand { get; }
|
|
|
|
|
|
2026-05-08 07:19:31 -04:00
|
|
|
/// <summary>
|
|
|
|
|
/// One-click "set up the transcoder topology" — writes ndi-config.v1.json so all
|
|
|
|
|
/// local senders broadcast on a private group ("teamsiso-input") while local
|
|
|
|
|
/// receivers can see both that and "public", then sets the engine's discovery and
|
|
|
|
|
/// output groups to align (engine receives from the private group, emits on Public).
|
|
|
|
|
/// User has to restart Teams for the new ndi-config.v1.json to take effect there.
|
|
|
|
|
/// </summary>
|
|
|
|
|
public AsyncRelayCommand ApplyTranscoderTopologyCommand { get; }
|
|
|
|
|
|
feat(ui): rebuild MainWindow with Stone-theme design system
Adopts the design language from Dammyjay93/interface-design: warm Stone neutrals, accent orange (#EA580C), borders-only depth (no shadows), 8px spacing grid, all-caps section labels, mono typography for machine names and timecodes.
Themes/StoneTheme.xaml is the single source of truth for design tokens (color brushes, typography styles, spacing) plus restyled control templates (Button, TextBox, ComboBox, ComboBoxItem, CheckBox, DataGrid + DataGridColumnHeader / DataGridRow / DataGridCell, ScrollBar). MainWindow consumes the tokens and is laid out with a header (title + status pill), section-headed Settings sidebar (Output Format / NDI Network / Display), card-wrapped participant list, and a mono status footer.
Settings sidebar surfaces the new NDI group configuration (discovery + output) and a Hide-(Local) checkbox. The latter filters the user's own self-preview from the participants list at the MainViewModel layer (HideLocalSelf=true by default) so operators don't accidentally route their own preview as an ISO. Apply Changes round-trips both FrameProcessingSettings and NdiGroupSettings through the controller in one go.
2026-05-07 23:58:02 -04:00
|
|
|
private async Task ApplyAsync()
|
2026-05-07 11:39:46 -04:00
|
|
|
{
|
|
|
|
|
var settings = new FrameProcessingSettings(_framerate, _resolution, _aspect, _audio);
|
feat(ui): rebuild MainWindow with Stone-theme design system
Adopts the design language from Dammyjay93/interface-design: warm Stone neutrals, accent orange (#EA580C), borders-only depth (no shadows), 8px spacing grid, all-caps section labels, mono typography for machine names and timecodes.
Themes/StoneTheme.xaml is the single source of truth for design tokens (color brushes, typography styles, spacing) plus restyled control templates (Button, TextBox, ComboBox, ComboBoxItem, CheckBox, DataGrid + DataGridColumnHeader / DataGridRow / DataGridCell, ScrollBar). MainWindow consumes the tokens and is laid out with a header (title + status pill), section-headed Settings sidebar (Output Format / NDI Network / Display), card-wrapped participant list, and a mono status footer.
Settings sidebar surfaces the new NDI group configuration (discovery + output) and a Hide-(Local) checkbox. The latter filters the user's own self-preview from the participants list at the MainViewModel layer (HideLocalSelf=true by default) so operators don't accidentally route their own preview as an ISO. Apply Changes round-trips both FrameProcessingSettings and NdiGroupSettings through the controller in one go.
2026-05-07 23:58:02 -04:00
|
|
|
await _controller.SetGlobalSettingsAsync(settings, CancellationToken.None);
|
|
|
|
|
|
|
|
|
|
var groups = new NdiGroupSettings(
|
|
|
|
|
DiscoveryGroups: string.IsNullOrWhiteSpace(_discoveryGroups) ? null : _discoveryGroups.Trim(),
|
|
|
|
|
OutputGroups: string.IsNullOrWhiteSpace(_outputGroups) ? null : _outputGroups.Trim());
|
|
|
|
|
await _controller.SetGroupSettingsAsync(groups, CancellationToken.None);
|
feat(ui): toast feedback for settings actions; refresh _NEXT.md
Adds a small auto-dismissing pill notification at the bottom-center of the participants area: 'Settings saved' on Apply Changes, 'Transcoder topology applied — restart Teams to take effect' after the one-click NDI groups setup. ToastViewModel owns its own DispatcherTimer and resets the dismissal countdown on successive calls, so the most recent message is always the one visible. Hooked into MainViewModel and threaded into GlobalSettingsViewModel via constructor injection.
_NEXT.md rewritten to reflect the May 2026 hardening pass: separates engine / UI / networking / Phase E.1 / diagnostics / CI / tests sections, lists every shipped item, and re-prioritizes the remaining work (Phase E.2-E.3 embedded Teams, code-signing the MSI, refresh-discovery affordance, output thumbnail previews, settings panel UX, auto-disable on departure, operator presets).
2026-05-09 09:30:04 -04:00
|
|
|
|
|
|
|
|
_toast?.Show("Settings saved");
|
2026-05-07 11:39:46 -04:00
|
|
|
}
|
2026-05-08 07:19:31 -04:00
|
|
|
|
|
|
|
|
private async Task ApplyTranscoderTopologyAsync()
|
|
|
|
|
{
|
|
|
|
|
// 1. Update the machine-wide NDI config so Teams' raw broadcasts go to the
|
|
|
|
|
// private group instead of polluting Public.
|
|
|
|
|
var result = NdiAccessManagerConfig.ApplyTranscoderTopology();
|
|
|
|
|
if (!result.Success)
|
|
|
|
|
{
|
|
|
|
|
MessageBox.Show(
|
|
|
|
|
$"Could not write NDI Access Manager config.\n\n{result.ErrorMessage}\n\nPath: {result.ConfigPath}",
|
|
|
|
|
"TeamsISO — Apply transcoder topology",
|
|
|
|
|
MessageBoxButton.OK,
|
|
|
|
|
MessageBoxImage.Warning);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 2. Update the engine: receive only from the private group, emit on Public.
|
|
|
|
|
var ourGroups = new NdiGroupSettings(
|
|
|
|
|
DiscoveryGroups: NdiAccessManagerConfig.TranscoderInputGroup,
|
|
|
|
|
OutputGroups: "public");
|
|
|
|
|
await _controller.SetGroupSettingsAsync(ourGroups, CancellationToken.None);
|
|
|
|
|
|
|
|
|
|
// 3. Reflect the new values in the bound text boxes.
|
|
|
|
|
DiscoveryGroups = NdiAccessManagerConfig.TranscoderInputGroup;
|
|
|
|
|
OutputGroups = "public";
|
|
|
|
|
|
|
|
|
|
var backupNote = result.BackupPath is null
|
|
|
|
|
? "No prior NDI config existed; a fresh one was created."
|
|
|
|
|
: $"A backup of your prior NDI config was saved to:\n{result.BackupPath}";
|
|
|
|
|
|
|
|
|
|
MessageBox.Show(
|
|
|
|
|
"Transcoder topology applied. ✓\n\n" +
|
|
|
|
|
"• Local senders (Teams, etc.) will broadcast on group 'teamsiso-input'.\n" +
|
|
|
|
|
"• Local receivers will see both 'public' and 'teamsiso-input'.\n" +
|
|
|
|
|
"• TeamsISO will discover from 'teamsiso-input' and re-emit on 'public'.\n\n" +
|
|
|
|
|
"RESTART Microsoft Teams for the new NDI config to take effect there.\n\n" +
|
|
|
|
|
backupNote,
|
|
|
|
|
"TeamsISO — Apply transcoder topology",
|
|
|
|
|
MessageBoxButton.OK,
|
|
|
|
|
MessageBoxImage.Information);
|
feat(ui): toast feedback for settings actions; refresh _NEXT.md
Adds a small auto-dismissing pill notification at the bottom-center of the participants area: 'Settings saved' on Apply Changes, 'Transcoder topology applied — restart Teams to take effect' after the one-click NDI groups setup. ToastViewModel owns its own DispatcherTimer and resets the dismissal countdown on successive calls, so the most recent message is always the one visible. Hooked into MainViewModel and threaded into GlobalSettingsViewModel via constructor injection.
_NEXT.md rewritten to reflect the May 2026 hardening pass: separates engine / UI / networking / Phase E.1 / diagnostics / CI / tests sections, lists every shipped item, and re-prioritizes the remaining work (Phase E.2-E.3 embedded Teams, code-signing the MSI, refresh-discovery affordance, output thumbnail previews, settings panel UX, auto-disable on departure, operator presets).
2026-05-09 09:30:04 -04:00
|
|
|
|
|
|
|
|
_toast?.Show("Transcoder topology applied — restart Teams to take effect");
|
2026-05-08 07:19:31 -04:00
|
|
|
}
|
2026-05-07 11:39:46 -04:00
|
|
|
}
|