using System.Linq; using System.Windows; using System.Windows.Input; using System.Windows.Shapes; using TeamsISO.App.Services; using TeamsISO.App.ViewModels; namespace TeamsISO.App; public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); StateChanged += OnWindowStateChanged; SourceInitialized += OnSourceInitialized; Closing += OnClosing; } public MainWindow(MainViewModel viewModel) : this() { DataContext = viewModel; } /// /// 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. /// private void OnSourceInitialized(object? sender, EventArgs e) { WindowStateStore.TryApply(this); } /// Persist the placement on close so next launch lands in the same spot. private void OnClosing(object? sender, System.ComponentModel.CancelEventArgs e) { WindowStateStore.Save(this); } /// Custom min button — chrome'd window has no system caption buttons. private void OnMinimize(object sender, RoutedEventArgs e) => WindowState = WindowState.Minimized; /// Toggles maximize/restore. Bound to the maximize button + double-click on the drag region. private void OnMaximizeRestore(object sender, RoutedEventArgs e) => WindowState = WindowState == WindowState.Maximized ? WindowState.Normal : WindowState.Maximized; /// Custom close button. private void OnClose(object sender, RoutedEventArgs e) => Close(); /// /// Toggle the right-hand settings pane. Collapses the 380px column to 0 /// so the participants list claims the full content width, then restores /// it on the next click. The pane's children stay loaded — we move the /// grid column, not the panel's Visibility — so scroll position and any /// in-flight edits survive the toggle. /// private void OnSettingsToggleClick(object sender, RoutedEventArgs e) { if (SettingsColumn is null) return; SettingsColumn.Width = SettingsColumn.Width.Value > 0 ? new System.Windows.GridLength(0) : new System.Windows.GridLength(380); } /// Opens the About dialog — version, NDI runtime, build SHA. private void OnAboutClick(object sender, RoutedEventArgs e) { var about = new AboutWindow { Owner = this }; about.ShowDialog(); } /// /// 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. /// 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(); } /// /// 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. /// private bool _teamsWindowsHidden; /// /// 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. /// 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"); } } /// /// Three-state click behavior matching operator intuition: /// 1. Teams not running → launch it via TeamsLauncher's fallback chain. /// 2. Teams running but its windows are hidden (we toggled them off, OR /// it launched into the tray) → restore the windows + foreground them. /// This is the case the previous "ask to stop" dialog was ambushing — /// operators don't think of a hidden Teams as "running and ready to /// stop", they think of it as "I clicked Launch and nothing happened". /// 3. Teams running with visible windows → bring the most recent one to /// the foreground. (Stopping Teams is now a right-click action; /// see OnLaunchTeamsRightClick.) /// private void OnLaunchTeamsClick(object sender, RoutedEventArgs e) { var toast = (DataContext as MainViewModel)?.Toast; if (!TeamsLauncher.IsRunning()) { if (!TeamsLauncher.TryLaunch(out var error)) { MessageBox.Show( $"Could not launch Microsoft Teams.\n\n{error}", "TeamsISO — Launch Teams", MessageBoxButton.OK, MessageBoxImage.Warning); } else { 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; } } return; } // Teams is running. Always try to restore + foreground its window — // if windows are already visible, ShowWindows is a SetForegroundWindow // no-op besides bringing them forward; if they were hidden by our // own toggle, this is the operator's intuitive "show me Teams" path. 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"); } /// /// Right-click on the rail 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". /// /// /// Open the experimental Teams embed window. Operator enables the /// preference first; this button materializes the host. See /// for the SetParent lifecycle. /// private void OnOpenEmbedWindowClick(object sender, RoutedEventArgs e) { // Non-modal so the operator can keep using TeamsISO's controls. // Owner = this so it minimizes / closes with TeamsISO. var w = new TeamsEmbedWindow { Owner = this }; w.Show(); } 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()) { MessageBox.Show( asked == 0 ? "No Teams windows responded to close." : $"Sent close to {asked} Teams window(s); some may still be exiting.", "TeamsISO — Stop Teams", MessageBoxButton.OK, MessageBoxImage.Information); } e.Handled = true; } /// /// Swap the maximize-button glyph between the "single rectangle" (when normal) and the /// "two-overlapping-rectangles" (when maximized) variants, matching the Windows 11 /// caption-button conventions. /// private void OnWindowStateChanged(object? sender, EventArgs e) { if (FindName("MaximizeIcon") is not Path icon) return; icon.Data = WindowState == WindowState.Maximized // Two-rectangle "restore" glyph ? System.Windows.Media.Geometry.Parse("M 2,0 L 10,0 L 10,8 M 0,2 L 8,2 L 8,10 L 0,10 Z") // Single-rectangle "maximize" glyph : System.Windows.Media.Geometry.Parse("M 0,0 L 10,0 L 10,10 L 0,10 Z"); } }