diff --git a/src/TeamsISO.App/HelpWindow.xaml b/src/TeamsISO.App/HelpWindow.xaml
index 1e360e7..5bd6e42 100644
--- a/src/TeamsISO.App/HelpWindow.xaml
+++ b/src/TeamsISO.App/HelpWindow.xaml
@@ -120,12 +120,19 @@
Text="Ctrl + R"
Style="{StaticResource Wd.Text.Mono}"
Foreground="{DynamicResource Wd.Accent.Cyan}"
- Margin="0,0,16,0"/>
+ Margin="0,0,16,6"/>
+ FontSize="12"
+ Margin="0,0,0,6"/>
+
diff --git a/src/TeamsISO.App/MainWindow.xaml b/src/TeamsISO.App/MainWindow.xaml
index b00a708..fba8989 100644
--- a/src/TeamsISO.App/MainWindow.xaml
+++ b/src/TeamsISO.App/MainWindow.xaml
@@ -54,6 +54,26 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/TeamsISO.App/ViewModels/MainViewModel.cs b/src/TeamsISO.App/ViewModels/MainViewModel.cs
index 426d4eb..c721abb 100644
--- a/src/TeamsISO.App/ViewModels/MainViewModel.cs
+++ b/src/TeamsISO.App/ViewModels/MainViewModel.cs
@@ -181,6 +181,14 @@ public sealed class MainViewModel : ObservableObject, IDisposable
/// Save a PNG snapshot of every enabled participant's current frame.
public RelayCommand SnapshotAllCommand { get; }
+ ///
+ /// Toggle the ISO for the Nth visible participant (1-based, matches the
+ /// numpad layout). Used by the NumPad1..NumPad9 hotkeys; resolves
+ /// against ParticipantsView so the index matches what the operator
+ /// sees in the current sort + filter.
+ ///
+ public RelayCommand ToggleByIndexCommand { get; }
+
///
/// Two-way bound to the quick-join input. Whatever the operator pastes
/// gets handed to when the
@@ -412,6 +420,25 @@ public sealed class MainViewModel : ObservableObject, IDisposable
SnapshotAllCommand = new RelayCommand(SnapshotAll, () => Participants.Any(p => p.IsEnabled));
+ ToggleByIndexCommand = new RelayCommand(s =>
+ {
+ // Numpad / digit hotkeys pass "1".."9" as a string. Resolve
+ // against the filtered/sorted view so the index matches what
+ // the operator sees on screen, not the underlying storage order.
+ if (!int.TryParse(s, out var idx) || idx < 1 || idx > 9) return;
+ var i = 0;
+ foreach (var item in ParticipantsView)
+ {
+ if (item is not ParticipantViewModel p) continue;
+ if (++i == idx)
+ {
+ if (p.ToggleIsoCommand.CanExecute(null))
+ p.ToggleIsoCommand.Execute(null);
+ break;
+ }
+ }
+ });
+
JoinMeetingCommand = new RelayCommand(() =>
{
// Trim + handle the operator pasting whitespace around the URL.
diff --git a/src/TeamsISO.App/ViewModels/RelayCommand.cs b/src/TeamsISO.App/ViewModels/RelayCommand.cs
index a859178..87416fa 100644
--- a/src/TeamsISO.App/ViewModels/RelayCommand.cs
+++ b/src/TeamsISO.App/ViewModels/RelayCommand.cs
@@ -23,6 +23,38 @@ public sealed class RelayCommand : ICommand
public void RaiseCanExecuteChanged() => CanExecuteChanged?.Invoke(this, EventArgs.Empty);
}
+///
+/// Synchronous command that accepts a typed parameter. Used by hotkeys
+/// that need to pass an index (e.g. NumPad1..NumPad9 → 1..9). The
+/// parameter is converted from object via Convert.ChangeType so XAML
+/// CommandParameter="1" works for int T.
+///
+public sealed class RelayCommand : ICommand
+{
+ private readonly Action _execute;
+ private readonly Func? _canExecute;
+
+ public RelayCommand(Action execute, Func? canExecute = null)
+ {
+ _execute = execute;
+ _canExecute = canExecute;
+ }
+
+ public bool CanExecute(object? parameter) => _canExecute?.Invoke(Convert(parameter)) ?? true;
+ public void Execute(object? parameter) => _execute(Convert(parameter));
+
+ public event EventHandler? CanExecuteChanged;
+ public void RaiseCanExecuteChanged() => CanExecuteChanged?.Invoke(this, EventArgs.Empty);
+
+ private static TValue Convert(object? value)
+ {
+ if (value is null) return default!;
+ if (value is TValue typed) return typed;
+ try { return (TValue)System.Convert.ChangeType(value, typeof(TValue)); }
+ catch { return default!; }
+ }
+}
+
///
/// Async command that suppresses re-entrancy while running.
///