using System; using System.ComponentModel; using System.Windows; using System.Windows.Controls; using System.Windows.Input; using System.Windows.Media; using ytLive.Helpers; using ytLive.ViewModels; namespace ytLive; public partial class MainWindow : Window { private readonly MainViewModel _viewModel; private Controls.TopBar _topBar = null!; private Controls.PreviewPane _previewPane = null!; private Controls.LeftPanel _leftPanel = null!; public MainWindow() { AppLog.Write("MainWindow ctor: before InitializeComponent"); InitializeComponent(); AppLog.Write("MainWindow ctor: after InitializeComponent"); _viewModel = (MainViewModel)DataContext; _viewModel.PropertyChanged += OnViewModelPropertyChanged; _topBar = (Controls.TopBar)FindName("TopBar")!; _previewPane = (Controls.PreviewPane)FindName("PreviewPane")!; _leftPanel = (Controls.LeftPanel)FindName("LeftPanel")!; UpdateTaskbarOverlay(); UpdateSelectionOverlay(); _topBar.RefreshAvatar(); RestoreWindowGeometry(); AppLog.Write("MainWindow ctor: end"); } private void MainWindow_Loaded(object sender, RoutedEventArgs e) { AppLog.Write("MainWindow loaded"); // Web sources need the window HWND as the composition controller's parent, // which does not exist in the ctor — InitWebView2 runs here (Loaded), after // SourceInitialized. Nothing renders from web frames before this. _viewModel.InitWebView2(); } // ─── Window geometry persistence (launch size/position) ─── // Saved on close (RestoreBounds so a maximized window remembers its // un-maximize rect); restored in the ctor before the window is shown. // Values clamp to the minimums and to the primary work area — a position // saved on a since-disconnected secondary monitor falls back on-screen. private void RestoreWindowGeometry() { try { var saved = _viewModel.RestoreWindowState(); if (saved == null) return; var (left, top, width, height, state) = saved.Value; var work = SystemParameters.WorkArea; width = Math.Clamp(width, MinWidth, work.Width); height = Math.Clamp(height, MinHeight, work.Height); left = Math.Clamp(left, work.Left - width + 120, work.Right - 120); top = Math.Clamp(top, work.Top, work.Bottom - 80); Left = left; Top = top; Width = width; Height = height; if (state == "Maximized") WindowState = WindowState.Maximized; } catch (Exception ex) { AppLog.Write($"Window geometry restore failed: {ex.Message}"); } } private void SaveWindowGeometry() { try { var bounds = RestoreBounds; var state = WindowState == WindowState.Maximized ? "Maximized" : "Normal"; _viewModel.PersistWindowState(bounds.Left, bounds.Top, bounds.Width, bounds.Height, state); } catch (Exception ex) { AppLog.Write($"Window geometry save failed: {ex.Message}"); } } private ytLive.Services.GlobalHotkeyManager? _hotkeys; protected override void OnSourceInitialized(EventArgs e) { base.OnSourceInitialized(e); _hotkeys = new ytLive.Services.GlobalHotkeyManager(); _hotkeys.Activated += id => _viewModel.HandleHotkey(id); _hotkeys.Attach(this, _viewModel.CurrentHotkeyBindings); _viewModel.HotkeyBindingsChanged = bindings => _hotkeys.Reattach(this, bindings); } private void Window_ContextMenuOpening(object sender, ContextMenuEventArgs e) { if (!_viewModel.IsEditMode) e.Handled = true; } private void OnViewModelPropertyChanged(object? sender, PropertyChangedEventArgs e) { if (e.PropertyName == nameof(MainViewModel.IsLive)) UpdateTaskbarOverlay(); else if (e.PropertyName == nameof(MainViewModel.SelectedElement)) UpdateSelectionOverlay(); else if (e.PropertyName == nameof(MainViewModel.AccountAvatarUrl) || e.PropertyName == nameof(MainViewModel.IsConnected)) _topBar.RefreshAvatar(); } private void MainWindow_Closing(object? sender, CancelEventArgs e) { SaveWindowGeometry(); _viewModel.Shutdown(); } private void MainWindow_Activated(object? sender, EventArgs e) { _viewModel.RefreshBackgroundAutoCapture(); } private void MainWindow_Deactivated(object? sender, EventArgs e) { } private void UpdateTaskbarOverlay() { TaskbarInfo.Overlay = _viewModel.IsLive ? (ImageSource)FindResource("LiveOverlay") : null; } private void Window_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e) { if (e.OriginalSource is not DependencyObject original) return; var form = _viewModel.BroadcastForm; var ypp = _viewModel.Ypp; var test = _viewModel.TestSession; if (!_previewPane.IsClickInsideDrawer(original)) { if (form.IsDrawerOpen) form.CloseDrawerCommand.Execute(null); if (ypp.IsDrawerOpen) ypp.CloseDrawerCommand.Execute(null); if (test.IsDrawerOpen) test.CloseDrawerCommand.Execute(null); } if (_previewPane.IsClickInsidePreview(original)) return; if (_leftPanel.IsClickInside(original)) return; _viewModel.SelectedElement = null; } private void UpdateSelectionOverlay() { if (!_viewModel.IsEditMode) { _previewPane.UpdateSelectionOverlay(false); return; } var selected = _viewModel.SelectedElement is { } s && Controls.PreviewPane.IsDraggableElement(s); _previewPane.UpdateSelectionOverlay(selected); _leftPanel.OnSelectionChanged(_viewModel); } }