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)) { child = child is Visual or System.Windows.Media.Media3D.Visual3D ? VisualTreeHelper.GetParent(child) : LogicalTreeHelper.GetParent(child); } 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 ShapeButton_Click(object sender, RoutedEventArgs e) { if ((DataContext as MainViewModel)?.SelectedElement is WebcamSceneConfig webcam) webcam.ToggleClipShape(); } 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 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 WebcamBorderColorPicker_Click(object sender, RoutedEventArgs e) { if ((DataContext as MainViewModel)?.SelectedElement is not SceneElement src) return; if (sender is FrameworkElement fe && fe.ContextMenu == null) { var menu = new System.Windows.Controls.ContextMenu(); string[] colors = ["#FFFFFF", "#000000", "#FF0000", "#00FF00", "#0000FF", "#FFFF00", "#FF00FF", "#00FFFF", "#FF8800", "#8800FF", "#e94560", "#22c55e", "#3b82f6", "#f59e0b"]; foreach (var c in colors) { var item = new System.Windows.Controls.MenuItem { Header = c, ToolTip = c }; var brush = new System.Windows.Media.SolidColorBrush( (System.Windows.Media.Color)System.Windows.Media.ColorConverter.ConvertFromString(c)); item.Icon = new System.Windows.Controls.Border { Width = 16, Height = 16, Background = brush, CornerRadius = new System.Windows.CornerRadius(2) }; var color = c; item.Click += (_, _) => src.BorderColor = color; menu.Items.Add(item); } fe.ContextMenu = menu; } if (sender is FrameworkElement fe2) fe2.ContextMenu.IsOpen = true; } 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; } }