using System.Collections; using System.Windows; using System.Windows.Controls; using System.Windows.Input; using System.Windows.Media; using ytLive.Models; using ytLive.ViewModels; namespace ytLive.Controls; public partial class LeftPanel : UserControl { private Models.SceneElement? _snapshotElement; private string _snapshotBorderColor = string.Empty; private int _snapshotBorderWidth; private int _snapshotTimerMinutes = 15; private string? _snapshotWebUri; private Point _dragStartPoint; private int _dragIndex = -1; private bool _isDragging; private object? _lastHoveredItem; private bool _dragReordered; public LeftPanel() { InitializeComponent(); Loaded += (_, _) => { if (DataContext is MainViewModel vm) vm.ChatMessages.CollectionChanged += OnChatMessagesChanged; }; Unloaded += (_, _) => { if (DataContext is MainViewModel vm) vm.ChatMessages.CollectionChanged -= OnChatMessagesChanged; }; } // Called by the window's UpdateSelectionOverlay (edit mode only). public void OnSelectionChanged(MainViewModel vm) { if (vm.SelectedElement is { } s && PreviewPane.IsDraggableElement(s)) OpacityValueText.Text = $"{Math.Round(s.Opacity * 100)}%"; TakeSnapshot(vm); } // Deselection guard: true when the click is inside the source list or the // element property scroller (window's Window_PreviewMouseLeftButtonDown). public bool IsClickInside(DependencyObject original) => IsDescendantOf(original, SourceList) || IsDescendantOf(original, ElementsScrollViewer); private static bool IsDescendantOf(DependencyObject? child, DependencyObject ancestor) { while (child != null && !ReferenceEquals(child, ancestor)) { var next = child is Visual or System.Windows.Media.Media3D.Visual3D ? VisualTreeHelper.GetParent(child) : null; child = next ?? LogicalTreeHelper.GetParent(child) ?? (child as FrameworkElement)?.TemplatedParent; } return child != null; } private void SourceNameBox_IsVisibleChanged(object sender, DependencyPropertyChangedEventArgs e) { if (sender is TextBox { IsVisible: true } box) { box.Focus(); box.SelectAll(); } } private void AddSourceButton_Click(object sender, RoutedEventArgs e) { if (sender is Button { ContextMenu: { } menu } button) { menu.PlacementTarget = button; menu.Placement = System.Windows.Controls.Primitives.PlacementMode.Bottom; menu.IsOpen = true; } } private void OnChatMessagesChanged(object? sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e) { if (e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Add) { ChatListBox.ScrollIntoView(ChatListBox.Items[^1]); } } private void MirrorButton_Click(object sender, RoutedEventArgs e) { if ((DataContext as MainViewModel)?.SelectedElement is WebcamSceneConfig webcam) webcam.IsMirrored = !webcam.IsMirrored; } private void SourceMenu_Remove(object sender, RoutedEventArgs e) { if (sender is MenuItem { DataContext: SceneElement element } && DataContext is MainViewModel vm) vm.RemoveSourceCommand.Execute(element); } private void BackgroundMenu_ChangeCapture(object sender, RoutedEventArgs e) { _ = (DataContext as MainViewModel)?.ChangeBackgroundCaptureAsync(); } private void BackgroundMenu_RefreshCapture(object sender, RoutedEventArgs e) { (DataContext as MainViewModel)?.RefreshBackgroundAutoCapture(); } private void BackgroundMenu_RefreshWindowList(object sender, RoutedEventArgs e) { (DataContext as MainViewModel)?.RefreshWindowsCommand.Execute(null); } private void OpacitySlider_ValueChanged(object sender, RoutedPropertyChangedEventArgs e) => OpacityValueText.Text = $"{Math.Round(e.NewValue * 100)}%"; private void SourceNameBox_KeyDown(object sender, KeyEventArgs e) { if (e.Key is Key.Enter or Key.Escape) { CommitSourceEdit(sender); e.Handled = true; } } private void SourceNameBox_LostFocus(object sender, RoutedEventArgs e) { CommitSourceEdit(sender); } private static void CommitSourceEdit(object sender) { if (sender is FrameworkElement { DataContext: SceneElement element }) element.IsEditing = false; } // ─── List drag-to-reorder (scenes list + sources list) ─── private void List_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e) { var listBox = (ListBox)sender; var item = FindItemContainerAt(listBox, e.GetPosition(listBox)); if (item == null) { if (ReferenceEquals(listBox, SourceList)) (DataContext as MainViewModel)!.SelectedElement = null; _dragIndex = -1; return; } if (listBox.ItemsSource is not IList items || items.Count == 0) { _dragIndex = -1; return; } _dragIndex = listBox.Items.IndexOf(item.DataContext); _dragStartPoint = e.GetPosition(listBox); _isDragging = false; _lastHoveredItem = null; _dragReordered = false; } private void List_PreviewMouseMove(object sender, MouseEventArgs e) { if (_dragIndex < 0) return; var listBox = (ListBox)sender; if (e.LeftButton != MouseButtonState.Pressed || listBox.ItemsSource is not IList items) { EndListDrag(listBox); return; } var position = e.GetPosition(listBox); if (!_isDragging) { if (Math.Abs(position.X - _dragStartPoint.X) < SystemParameters.MinimumHorizontalDragDistance && Math.Abs(position.Y - _dragStartPoint.Y) < SystemParameters.MinimumVerticalDragDistance) return; _isDragging = true; listBox.CaptureMouse(); } var item = FindItemContainerAt(listBox, position); if (item == null) return; var targetItem = item.DataContext; if (ReferenceEquals(targetItem, items[_dragIndex]) || ReferenceEquals(targetItem, _lastHoveredItem)) return; var targetIndex = listBox.Items.IndexOf(targetItem); if (targetIndex < 0) return; var dragged = items[_dragIndex]; items.RemoveAt(_dragIndex); items.Insert(targetIndex, dragged); _dragIndex = targetIndex; _lastHoveredItem = targetItem; _dragReordered = true; } private void List_PreviewMouseLeftButtonUp(object sender, MouseButtonEventArgs e) { EndListDrag((ListBox)sender); } private void EndListDrag(ListBox listBox) { if (_isDragging) listBox.ReleaseMouseCapture(); var reordered = _dragReordered; _dragIndex = -1; _isDragging = false; _lastHoveredItem = null; _dragReordered = false; if (reordered && DataContext is MainViewModel vm) vm.OnSceneElementsReordered(); } private static ListBoxItem? FindItemContainerAt(ListBox listBox, Point position) { var hit = listBox.InputHitTest(position) as DependencyObject; while (hit != null && hit != listBox) { if (hit is ListBoxItem item) return item; hit = VisualTreeHelper.GetParent(hit); } return null; } private void TakeSnapshot(MainViewModel vm) { if (vm.SelectedElement is Models.Source src) { _snapshotElement = src; _snapshotBorderColor = src.BorderColor; _snapshotBorderWidth = src.BorderWidth; _snapshotTimerMinutes = src.TimerMinutes; _snapshotWebUri = src.WebUri; } } private void WebcamBorderRevert_Click(object sender, RoutedEventArgs e) { if (_snapshotElement is Models.Source src) { src.BorderColor = _snapshotBorderColor; src.BorderWidth = _snapshotBorderWidth; } } private void WebcamBorderAccept_Click(object sender, RoutedEventArgs e) { if (_snapshotElement is Models.Source src) { _snapshotBorderColor = src.BorderColor; _snapshotBorderWidth = src.BorderWidth; } } private void CountdownRevert_Click(object sender, RoutedEventArgs e) { if (_snapshotElement is Models.Source src) src.TimerMinutes = _snapshotTimerMinutes; } private void CountdownAccept_Click(object sender, RoutedEventArgs e) { if (_snapshotElement is Models.Source src) _snapshotTimerMinutes = src.TimerMinutes; } private void WebRevert_Click(object sender, RoutedEventArgs e) { if (_snapshotElement is Models.Source src) src.WebUri = string.Empty; _snapshotWebUri = string.Empty; } private void WebAccept_Click(object sender, RoutedEventArgs e) { if (_snapshotElement is Models.Source src) _snapshotWebUri = src.WebUri; } }