2026-05-14 12:46:24 -04:00
|
|
|
using System;
|
2026-05-10 09:42:29 -04:00
|
|
|
using System.Linq;
|
2026-05-07 11:09:56 -04:00
|
|
|
using System.Windows;
|
2026-05-10 14:39:04 -04:00
|
|
|
using System.Windows.Input;
|
2026-05-08 01:05:26 -04:00
|
|
|
using TeamsISO.App.Services;
|
2026-05-07 11:40:49 -04:00
|
|
|
using TeamsISO.App.ViewModels;
|
2026-05-07 11:09:56 -04:00
|
|
|
|
|
|
|
|
namespace TeamsISO.App;
|
|
|
|
|
|
|
|
|
|
public partial class MainWindow : Window
|
|
|
|
|
{
|
|
|
|
|
public MainWindow()
|
|
|
|
|
{
|
|
|
|
|
InitializeComponent();
|
2026-05-08 13:59:14 -04:00
|
|
|
SourceInitialized += OnSourceInitialized;
|
|
|
|
|
Closing += OnClosing;
|
2026-05-14 12:46:24 -04:00
|
|
|
// Esc dismisses the settings drawer when it's open. Bound at the
|
|
|
|
|
// window level so any focused control inside the drawer also gets
|
|
|
|
|
// the affordance.
|
|
|
|
|
PreviewKeyDown += OnPreviewKeyDown;
|
2026-05-07 11:09:56 -04:00
|
|
|
}
|
2026-05-07 11:40:49 -04:00
|
|
|
|
|
|
|
|
public MainWindow(MainViewModel viewModel) : this()
|
|
|
|
|
{
|
|
|
|
|
DataContext = viewModel;
|
feat(wpf): v2 task 39+40 - studio table redesign + Ctrl+K command palette
Task 39: 5-column participants table - state LED, name+codec caption, 5-bar audio meter, mono output name, ISO pill. Row height 52, full-row active-speaker tint (no left stripe). New converter LevelThresholdConverter, OutputName property on ParticipantViewModel.
Task 40: Ctrl+K / Ctrl+P command palette - chromeless centered floating window, fuzzy Contains match across Label/Category/Keywords, arrow nav, Enter invoke, Esc close. Quick/Teams/Network/App categories cover top operator verbs and theme switching.
Also: log startup exceptions to Serilog before the modal MessageBox fires - much better triage signal than user-pasted dialog text.
2026-05-15 11:15:00 -04:00
|
|
|
// Hand the view-model the palette-opener callback so Ctrl+K's
|
|
|
|
|
// KeyBinding (which lives on the VM as an ICommand) can reach
|
|
|
|
|
// back into the view layer to materialize the window.
|
|
|
|
|
viewModel.RegisterCommandPaletteOpener(() => OnCommandPaletteClick(this, new RoutedEventArgs()));
|
2026-05-07 11:40:49 -04:00
|
|
|
}
|
2026-05-08 00:55:57 -04:00
|
|
|
|
2026-05-08 13:59:14 -04:00
|
|
|
/// <summary>
|
|
|
|
|
/// Restore the window's previous placement after the HWND is created (so
|
|
|
|
|
/// SetWindowPos / WindowState transitions actually take effect). Falls
|
|
|
|
|
/// silently back to the XAML-default startup location if no snapshot exists.
|
|
|
|
|
/// </summary>
|
|
|
|
|
private void OnSourceInitialized(object? sender, EventArgs e)
|
|
|
|
|
{
|
|
|
|
|
WindowStateStore.TryApply(this);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>Persist the placement on close so next launch lands in the same spot.</summary>
|
|
|
|
|
private void OnClosing(object? sender, System.ComponentModel.CancelEventArgs e)
|
|
|
|
|
{
|
|
|
|
|
WindowStateStore.Save(this);
|
|
|
|
|
}
|
|
|
|
|
|
feat: app icon, FPS, drops counter, --version, About dialog, Stop Teams toggle
Six related polish items, all building on tonight's groundwork.
1. App icon: teamsiso.ico generated from dragon-mark.png at 7 sizes (16-256), wired as ApplicationIcon in the WPF csproj, MainWindow.Icon, AboutWindow.Icon, and ARPPRODUCTICON in the WiX MSI. Taskbar / window / Add-Remove-Programs all show the dragon mark now.
2. Running incoming FPS: ring buffer of last 30 frame timestamps in IsoPipeline; ComputeFps() returns moving-average rate. Surfaced on IsoHealthStats.IncomingFps and shown in the Source column of the participants DataGrid as 'WxH · 59.94 fps'. Resets cleanly on every supervisor restart.
3. Drops counter: FrameProcessor.Stats already aggregated FramesDropped (closest-frame strategy when the receiver outpaces the processor) and FramesDuplicated; just plumbed _liveProcessor through IsoPipeline so GetStats() can read them. Exposed in the Live column under the in/out counters as a coral-tinted 'drop N'.
4. Console --version flag: prints engine version (with embedded git SHA), .NET version, OS, NDI runtime banner, expected prefix, exit-code legend, plus a wilddragon.net link. Useful for support tickets.
5. About dialog: chromeless modal with the dragon mark + version / .NET / OS / NDI runtime fields and a link to wilddragon.net. Triggered by clicking the rail logo.
6. Teams launcher Stop toggle: TeamsLauncher gains IsRunning() and StopAll(). The rail's Teams button now toggles — if Teams is up, ask to close all Teams windows via WM_CLOSE; otherwise launch as before. Confirms before stopping so we don't kill the user's call mid-transition.
Tests: 74/74 unit + 9/9 NDI integration green throughout. MSI builds clean and now embeds the dragon icon for ARP.
2026-05-08 13:50:19 -04:00
|
|
|
/// <summary>Opens the About dialog — version, NDI runtime, build SHA.</summary>
|
|
|
|
|
private void OnAboutClick(object sender, RoutedEventArgs e)
|
|
|
|
|
{
|
|
|
|
|
var about = new AboutWindow { Owner = this };
|
|
|
|
|
about.ShowDialog();
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-10 09:42:29 -04:00
|
|
|
/// <summary>
|
|
|
|
|
/// Opens the operator-presets dialog. Hands it the current participants
|
|
|
|
|
/// snapshot (so Save captures live state) and the engine controller (so
|
|
|
|
|
/// Apply can reconcile enable/disable). Owner is set so the chromeless
|
|
|
|
|
/// dialog centers over the main window and inherits z-order.
|
|
|
|
|
/// </summary>
|
|
|
|
|
private void OnPresetsClick(object sender, RoutedEventArgs e)
|
|
|
|
|
{
|
|
|
|
|
if (DataContext is not MainViewModel vm) return;
|
|
|
|
|
var dialog = new PresetsDialog(vm.Controller, vm.Participants.ToList(), vm.Toast)
|
|
|
|
|
{
|
|
|
|
|
Owner = this,
|
|
|
|
|
};
|
|
|
|
|
dialog.ShowDialog();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Tracks whether we have hidden Teams' windows so the next click reverses
|
|
|
|
|
/// the action. We treat this as "intent" rather than a query of OS state
|
|
|
|
|
/// because hidden windows still report as hidden if the operator manually
|
|
|
|
|
/// re-opens them and we only care about TeamsISO's own toggle history.
|
|
|
|
|
/// </summary>
|
|
|
|
|
private bool _teamsWindowsHidden;
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Phase E.2 toggle. Hides every visible top-level Teams window on first
|
|
|
|
|
/// click; shows them again on the next. Surfaces the result via the toast
|
|
|
|
|
/// so the operator gets feedback even though the affected windows aren't
|
|
|
|
|
/// visible anymore.
|
|
|
|
|
/// </summary>
|
|
|
|
|
private void OnToggleTeamsWindowClick(object sender, RoutedEventArgs e)
|
|
|
|
|
{
|
|
|
|
|
if (!TeamsLauncher.IsRunning())
|
|
|
|
|
{
|
|
|
|
|
MessageBox.Show(
|
|
|
|
|
"Microsoft Teams isn't running. Click the camera icon above to launch it first.",
|
|
|
|
|
"TeamsISO — Hide / show Teams",
|
|
|
|
|
MessageBoxButton.OK,
|
|
|
|
|
MessageBoxImage.Information);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var toast = (DataContext as MainViewModel)?.Toast;
|
|
|
|
|
if (_teamsWindowsHidden)
|
|
|
|
|
{
|
|
|
|
|
var shown = TeamsLauncher.ShowWindows();
|
|
|
|
|
_teamsWindowsHidden = false;
|
|
|
|
|
toast?.Show(shown > 0 ? $"Restored {shown} Teams window(s)" : "No Teams windows to restore");
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
var hidden = TeamsLauncher.HideWindows();
|
|
|
|
|
_teamsWindowsHidden = hidden > 0;
|
|
|
|
|
toast?.Show(hidden > 0 ? $"Hid {hidden} Teams window(s)" : "Teams has no visible windows yet");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-08 01:05:26 -04:00
|
|
|
/// <summary>
|
2026-05-10 14:39:04 -04:00
|
|
|
/// Three-state click behavior matching operator intuition:
|
|
|
|
|
/// 1. Teams not running → launch it via TeamsLauncher's fallback chain.
|
2026-05-14 12:46:24 -04:00
|
|
|
/// 2. Teams running but its windows are hidden → restore + foreground them.
|
|
|
|
|
/// 3. Teams running with visible windows → bring the most recent to front.
|
|
|
|
|
/// (Stopping Teams is a right-click action; see OnLaunchTeamsRightClick.)
|
2026-05-08 01:05:26 -04:00
|
|
|
/// </summary>
|
|
|
|
|
private void OnLaunchTeamsClick(object sender, RoutedEventArgs e)
|
|
|
|
|
{
|
2026-05-10 14:39:04 -04:00
|
|
|
var toast = (DataContext as MainViewModel)?.Toast;
|
feat: app icon, FPS, drops counter, --version, About dialog, Stop Teams toggle
Six related polish items, all building on tonight's groundwork.
1. App icon: teamsiso.ico generated from dragon-mark.png at 7 sizes (16-256), wired as ApplicationIcon in the WPF csproj, MainWindow.Icon, AboutWindow.Icon, and ARPPRODUCTICON in the WiX MSI. Taskbar / window / Add-Remove-Programs all show the dragon mark now.
2. Running incoming FPS: ring buffer of last 30 frame timestamps in IsoPipeline; ComputeFps() returns moving-average rate. Surfaced on IsoHealthStats.IncomingFps and shown in the Source column of the participants DataGrid as 'WxH · 59.94 fps'. Resets cleanly on every supervisor restart.
3. Drops counter: FrameProcessor.Stats already aggregated FramesDropped (closest-frame strategy when the receiver outpaces the processor) and FramesDuplicated; just plumbed _liveProcessor through IsoPipeline so GetStats() can read them. Exposed in the Live column under the in/out counters as a coral-tinted 'drop N'.
4. Console --version flag: prints engine version (with embedded git SHA), .NET version, OS, NDI runtime banner, expected prefix, exit-code legend, plus a wilddragon.net link. Useful for support tickets.
5. About dialog: chromeless modal with the dragon mark + version / .NET / OS / NDI runtime fields and a link to wilddragon.net. Triggered by clicking the rail logo.
6. Teams launcher Stop toggle: TeamsLauncher gains IsRunning() and StopAll(). The rail's Teams button now toggles — if Teams is up, ask to close all Teams windows via WM_CLOSE; otherwise launch as before. Confirms before stopping so we don't kill the user's call mid-transition.
Tests: 74/74 unit + 9/9 NDI integration green throughout. MSI builds clean and now embeds the dragon icon for ARP.
2026-05-08 13:50:19 -04:00
|
|
|
|
2026-05-10 14:39:04 -04:00
|
|
|
if (!TeamsLauncher.IsRunning())
|
|
|
|
|
{
|
|
|
|
|
if (!TeamsLauncher.TryLaunch(out var error))
|
feat: app icon, FPS, drops counter, --version, About dialog, Stop Teams toggle
Six related polish items, all building on tonight's groundwork.
1. App icon: teamsiso.ico generated from dragon-mark.png at 7 sizes (16-256), wired as ApplicationIcon in the WPF csproj, MainWindow.Icon, AboutWindow.Icon, and ARPPRODUCTICON in the WiX MSI. Taskbar / window / Add-Remove-Programs all show the dragon mark now.
2. Running incoming FPS: ring buffer of last 30 frame timestamps in IsoPipeline; ComputeFps() returns moving-average rate. Surfaced on IsoHealthStats.IncomingFps and shown in the Source column of the participants DataGrid as 'WxH · 59.94 fps'. Resets cleanly on every supervisor restart.
3. Drops counter: FrameProcessor.Stats already aggregated FramesDropped (closest-frame strategy when the receiver outpaces the processor) and FramesDuplicated; just plumbed _liveProcessor through IsoPipeline so GetStats() can read them. Exposed in the Live column under the in/out counters as a coral-tinted 'drop N'.
4. Console --version flag: prints engine version (with embedded git SHA), .NET version, OS, NDI runtime banner, expected prefix, exit-code legend, plus a wilddragon.net link. Useful for support tickets.
5. About dialog: chromeless modal with the dragon mark + version / .NET / OS / NDI runtime fields and a link to wilddragon.net. Triggered by clicking the rail logo.
6. Teams launcher Stop toggle: TeamsLauncher gains IsRunning() and StopAll(). The rail's Teams button now toggles — if Teams is up, ask to close all Teams windows via WM_CLOSE; otherwise launch as before. Confirms before stopping so we don't kill the user's call mid-transition.
Tests: 74/74 unit + 9/9 NDI integration green throughout. MSI builds clean and now embeds the dragon icon for ARP.
2026-05-08 13:50:19 -04:00
|
|
|
{
|
|
|
|
|
MessageBox.Show(
|
2026-05-10 14:39:04 -04:00
|
|
|
$"Could not launch Microsoft Teams.\n\n{error}",
|
|
|
|
|
"TeamsISO — Launch Teams",
|
feat: app icon, FPS, drops counter, --version, About dialog, Stop Teams toggle
Six related polish items, all building on tonight's groundwork.
1. App icon: teamsiso.ico generated from dragon-mark.png at 7 sizes (16-256), wired as ApplicationIcon in the WPF csproj, MainWindow.Icon, AboutWindow.Icon, and ARPPRODUCTICON in the WiX MSI. Taskbar / window / Add-Remove-Programs all show the dragon mark now.
2. Running incoming FPS: ring buffer of last 30 frame timestamps in IsoPipeline; ComputeFps() returns moving-average rate. Surfaced on IsoHealthStats.IncomingFps and shown in the Source column of the participants DataGrid as 'WxH · 59.94 fps'. Resets cleanly on every supervisor restart.
3. Drops counter: FrameProcessor.Stats already aggregated FramesDropped (closest-frame strategy when the receiver outpaces the processor) and FramesDuplicated; just plumbed _liveProcessor through IsoPipeline so GetStats() can read them. Exposed in the Live column under the in/out counters as a coral-tinted 'drop N'.
4. Console --version flag: prints engine version (with embedded git SHA), .NET version, OS, NDI runtime banner, expected prefix, exit-code legend, plus a wilddragon.net link. Useful for support tickets.
5. About dialog: chromeless modal with the dragon mark + version / .NET / OS / NDI runtime fields and a link to wilddragon.net. Triggered by clicking the rail logo.
6. Teams launcher Stop toggle: TeamsLauncher gains IsRunning() and StopAll(). The rail's Teams button now toggles — if Teams is up, ask to close all Teams windows via WM_CLOSE; otherwise launch as before. Confirms before stopping so we don't kill the user's call mid-transition.
Tests: 74/74 unit + 9/9 NDI integration green throughout. MSI builds clean and now embeds the dragon icon for ARP.
2026-05-08 13:50:19 -04:00
|
|
|
MessageBoxButton.OK,
|
2026-05-10 14:39:04 -04:00
|
|
|
MessageBoxImage.Warning);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2026-05-10 20:35:00 -04:00
|
|
|
var autoHide = (DataContext as MainViewModel)?.Settings.AutoHideTeamsWindows ?? false;
|
|
|
|
|
toast?.Show(autoHide
|
|
|
|
|
? "Launching Microsoft Teams (will hide windows automatically)…"
|
|
|
|
|
: "Launching Microsoft Teams…");
|
|
|
|
|
if (autoHide)
|
|
|
|
|
{
|
|
|
|
|
_ = TeamsLauncher.AutoHideAfterLaunchAsync();
|
|
|
|
|
_teamsWindowsHidden = true;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
_teamsWindowsHidden = false;
|
|
|
|
|
}
|
feat: app icon, FPS, drops counter, --version, About dialog, Stop Teams toggle
Six related polish items, all building on tonight's groundwork.
1. App icon: teamsiso.ico generated from dragon-mark.png at 7 sizes (16-256), wired as ApplicationIcon in the WPF csproj, MainWindow.Icon, AboutWindow.Icon, and ARPPRODUCTICON in the WiX MSI. Taskbar / window / Add-Remove-Programs all show the dragon mark now.
2. Running incoming FPS: ring buffer of last 30 frame timestamps in IsoPipeline; ComputeFps() returns moving-average rate. Surfaced on IsoHealthStats.IncomingFps and shown in the Source column of the participants DataGrid as 'WxH · 59.94 fps'. Resets cleanly on every supervisor restart.
3. Drops counter: FrameProcessor.Stats already aggregated FramesDropped (closest-frame strategy when the receiver outpaces the processor) and FramesDuplicated; just plumbed _liveProcessor through IsoPipeline so GetStats() can read them. Exposed in the Live column under the in/out counters as a coral-tinted 'drop N'.
4. Console --version flag: prints engine version (with embedded git SHA), .NET version, OS, NDI runtime banner, expected prefix, exit-code legend, plus a wilddragon.net link. Useful for support tickets.
5. About dialog: chromeless modal with the dragon mark + version / .NET / OS / NDI runtime fields and a link to wilddragon.net. Triggered by clicking the rail logo.
6. Teams launcher Stop toggle: TeamsLauncher gains IsRunning() and StopAll(). The rail's Teams button now toggles — if Teams is up, ask to close all Teams windows via WM_CLOSE; otherwise launch as before. Confirms before stopping so we don't kill the user's call mid-transition.
Tests: 74/74 unit + 9/9 NDI integration green throughout. MSI builds clean and now embeds the dragon icon for ARP.
2026-05-08 13:50:19 -04:00
|
|
|
}
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-10 14:39:04 -04:00
|
|
|
var shown = TeamsLauncher.ShowWindows();
|
|
|
|
|
_teamsWindowsHidden = false;
|
|
|
|
|
toast?.Show(shown > 0
|
|
|
|
|
? $"Teams is already running — surfaced {shown} window(s)"
|
|
|
|
|
: "Teams is running but has no visible windows yet");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
2026-05-14 12:46:24 -04:00
|
|
|
/// Right-click on the Launch button asks to stop Teams. Split out from the
|
|
|
|
|
/// left-click so a normal click is "open / surface" rather than the previous
|
|
|
|
|
/// "open OR ambush you with a stop dialog".
|
2026-05-10 21:14:42 -04:00
|
|
|
/// </summary>
|
2026-05-10 14:39:04 -04:00
|
|
|
private void OnLaunchTeamsRightClick(object sender, MouseButtonEventArgs e)
|
|
|
|
|
{
|
|
|
|
|
if (!TeamsLauncher.IsRunning()) return;
|
|
|
|
|
|
|
|
|
|
var confirm = MessageBox.Show(
|
|
|
|
|
"Microsoft Teams is currently running.\n\nClose all Teams windows now?",
|
|
|
|
|
"TeamsISO — Stop Teams",
|
|
|
|
|
MessageBoxButton.YesNo,
|
|
|
|
|
MessageBoxImage.Question);
|
|
|
|
|
if (confirm != MessageBoxResult.Yes) return;
|
|
|
|
|
|
|
|
|
|
var asked = TeamsLauncher.StopAll();
|
|
|
|
|
if (TeamsLauncher.IsRunning())
|
2026-05-08 01:05:26 -04:00
|
|
|
{
|
|
|
|
|
MessageBox.Show(
|
2026-05-10 14:39:04 -04:00
|
|
|
asked == 0
|
|
|
|
|
? "No Teams windows responded to close."
|
|
|
|
|
: $"Sent close to {asked} Teams window(s); some may still be exiting.",
|
|
|
|
|
"TeamsISO — Stop Teams",
|
2026-05-08 01:05:26 -04:00
|
|
|
MessageBoxButton.OK,
|
2026-05-10 14:39:04 -04:00
|
|
|
MessageBoxImage.Information);
|
2026-05-08 01:05:26 -04:00
|
|
|
}
|
2026-05-10 14:39:04 -04:00
|
|
|
e.Handled = true;
|
2026-05-08 01:05:26 -04:00
|
|
|
}
|
|
|
|
|
|
2026-05-08 00:55:57 -04:00
|
|
|
/// <summary>
|
2026-05-14 12:46:24 -04:00
|
|
|
/// Open the experimental Teams embed window. Operator enables the
|
|
|
|
|
/// preference first; this button materializes the host.
|
2026-05-08 00:55:57 -04:00
|
|
|
/// </summary>
|
2026-05-14 12:46:24 -04:00
|
|
|
private void OnOpenEmbedWindowClick(object sender, RoutedEventArgs e)
|
2026-05-08 00:55:57 -04:00
|
|
|
{
|
2026-05-14 12:46:24 -04:00
|
|
|
var w = new TeamsEmbedWindow { Owner = this };
|
|
|
|
|
w.Show();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Toggle the v2 settings drawer overlay. The header gear button and the
|
|
|
|
|
/// drawer's own Close button both call this. State is held by the
|
|
|
|
|
/// overlay's <see cref="UIElement.Visibility"/> directly — no separate
|
|
|
|
|
/// flag — so the toggle is idempotent regardless of how many entry
|
|
|
|
|
/// points open / close it.
|
|
|
|
|
/// </summary>
|
|
|
|
|
private void OnSettingsToggleClick(object sender, RoutedEventArgs e)
|
|
|
|
|
{
|
|
|
|
|
if (SettingsDrawerOverlay is null) return;
|
|
|
|
|
SettingsDrawerOverlay.Visibility = SettingsDrawerOverlay.Visibility == Visibility.Visible
|
|
|
|
|
? Visibility.Collapsed
|
|
|
|
|
: Visibility.Visible;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Clicking the scrim behind the drawer dismisses it — same affordance as
|
|
|
|
|
/// every well-behaved slide-over on every platform.
|
|
|
|
|
/// </summary>
|
|
|
|
|
private void OnSettingsScrimClick(object sender, MouseButtonEventArgs e)
|
|
|
|
|
{
|
|
|
|
|
if (SettingsDrawerOverlay is null) return;
|
|
|
|
|
SettingsDrawerOverlay.Visibility = Visibility.Collapsed;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
feat(wpf): v2 task 39+40 - studio table redesign + Ctrl+K command palette
Task 39: 5-column participants table - state LED, name+codec caption, 5-bar audio meter, mono output name, ISO pill. Row height 52, full-row active-speaker tint (no left stripe). New converter LevelThresholdConverter, OutputName property on ParticipantViewModel.
Task 40: Ctrl+K / Ctrl+P command palette - chromeless centered floating window, fuzzy Contains match across Label/Category/Keywords, arrow nav, Enter invoke, Esc close. Quick/Teams/Network/App categories cover top operator verbs and theme switching.
Also: log startup exceptions to Serilog before the modal MessageBox fires - much better triage signal than user-pasted dialog text.
2026-05-15 11:15:00 -04:00
|
|
|
/// Open the v2 Ctrl+K command palette. Bound to the header ⌘K button and
|
|
|
|
|
/// to the Ctrl+K keyboard binding. The palette is a chromeless floating
|
|
|
|
|
/// window owned by this MainWindow so it centers correctly, closes on
|
|
|
|
|
/// Deactivated (click outside), and inherits z-order. We construct a
|
|
|
|
|
/// fresh view-model each time so the filter starts empty.
|
2026-05-14 12:46:24 -04:00
|
|
|
/// </summary>
|
|
|
|
|
private void OnCommandPaletteClick(object sender, RoutedEventArgs e)
|
|
|
|
|
{
|
feat(wpf): v2 task 39+40 - studio table redesign + Ctrl+K command palette
Task 39: 5-column participants table - state LED, name+codec caption, 5-bar audio meter, mono output name, ISO pill. Row height 52, full-row active-speaker tint (no left stripe). New converter LevelThresholdConverter, OutputName property on ParticipantViewModel.
Task 40: Ctrl+K / Ctrl+P command palette - chromeless centered floating window, fuzzy Contains match across Label/Category/Keywords, arrow nav, Enter invoke, Esc close. Quick/Teams/Network/App categories cover top operator verbs and theme switching.
Also: log startup exceptions to Serilog before the modal MessageBox fires - much better triage signal than user-pasted dialog text.
2026-05-15 11:15:00 -04:00
|
|
|
if (DataContext is not MainViewModel vm) return;
|
|
|
|
|
var paletteVm = new ViewModels.CommandPaletteViewModel(vm, Dispatcher);
|
|
|
|
|
var palette = new Views.CommandPaletteWindow(paletteVm) { Owner = this };
|
|
|
|
|
palette.ShowDialog();
|
2026-05-14 12:46:24 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Esc closes the drawer when it's open. We use PreviewKeyDown rather than
|
|
|
|
|
/// KeyDown so the drawer's nested inputs (TextBox, ComboBox) don't swallow
|
|
|
|
|
/// the key before this handler sees it.
|
|
|
|
|
/// </summary>
|
|
|
|
|
private void OnPreviewKeyDown(object sender, System.Windows.Input.KeyEventArgs e)
|
|
|
|
|
{
|
|
|
|
|
if (e.Key != Key.Escape) return;
|
|
|
|
|
if (SettingsDrawerOverlay?.Visibility == Visibility.Visible)
|
|
|
|
|
{
|
|
|
|
|
SettingsDrawerOverlay.Visibility = Visibility.Collapsed;
|
|
|
|
|
e.Handled = true;
|
|
|
|
|
}
|
2026-05-08 00:55:57 -04:00
|
|
|
}
|
2026-05-07 11:09:56 -04:00
|
|
|
}
|