diff --git a/Controls/LeftPanel.xaml b/Controls/LeftPanel.xaml
new file mode 100644
index 0000000..03d252f
--- /dev/null
+++ b/Controls/LeftPanel.xaml
@@ -0,0 +1,499 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Controls/LeftPanel.xaml.cs b/Controls/LeftPanel.xaml.cs
new file mode 100644
index 0000000..f67187d
--- /dev/null
+++ b/Controls/LeftPanel.xaml.cs
@@ -0,0 +1,310 @@
+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;
+
+ 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;
+ }
+
+ 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;
+ }
+
+ private void List_PreviewMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
+ {
+ EndListDrag((ListBox)sender);
+ }
+
+ private void EndListDrag(ListBox listBox)
+ {
+ if (_isDragging)
+ listBox.ReleaseMouseCapture();
+ _dragIndex = -1;
+ _isDragging = false;
+ _lastHoveredItem = null;
+ }
+
+ 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;
+ }
+}
diff --git a/MainWindow.xaml b/MainWindow.xaml
index 905ed84..7ed8818 100644
--- a/MainWindow.xaml
+++ b/MainWindow.xaml
@@ -98,499 +98,8 @@
-
-
-
-
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/MainWindow.xaml.cs b/MainWindow.xaml.cs
index 967be1f..7d5021b 100644
--- a/MainWindow.xaml.cs
+++ b/MainWindow.xaml.cs
@@ -1,12 +1,10 @@
using System;
-using System.Collections;
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using ytLive.Helpers;
-using ytLive.Models;
using ytLive.ViewModels;
namespace ytLive;
@@ -16,6 +14,7 @@ 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()
{
@@ -25,9 +24,9 @@ public partial class MainWindow : Window
_viewModel = (MainViewModel)DataContext;
_viewModel.InitWebView2(WebViewHostPanel);
_viewModel.PropertyChanged += OnViewModelPropertyChanged;
- _viewModel.ChatMessages.CollectionChanged += OnChatMessagesChanged;
_topBar = (Controls.TopBar)FindName("TopBar")!;
_previewPane = (Controls.PreviewPane)FindName("PreviewPane")!;
+ _leftPanel = (Controls.LeftPanel)FindName("LeftPanel")!;
UpdateTaskbarOverlay();
UpdateSelectionOverlay();
_topBar.RefreshAvatar();
@@ -136,15 +135,6 @@ public partial class MainWindow : Window
: null;
}
- private void SourceNameBox_IsVisibleChanged(object sender, DependencyPropertyChangedEventArgs e)
- {
- if (sender is TextBox { IsVisible: true } box)
- {
- box.Focus();
- box.SelectAll();
- }
- }
-
private void BackToAbout_Click(object sender, RoutedEventArgs e)
{
if (DataContext is ViewModels.MainViewModel vm)
@@ -154,17 +144,6 @@ public partial class MainWindow : Window
}
}
- 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 Window_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
if (e.OriginalSource is not DependencyObject original) return;
@@ -172,22 +151,10 @@ public partial class MainWindow : Window
if (form.IsDrawerOpen && !_previewPane.IsClickInsideDrawer(original))
form.CloseDrawerCommand.Execute(null);
if (_previewPane.IsClickInsidePreview(original)) return;
- if (IsDescendantOf(original, SourceList)) return;
- if (IsDescendantOf(original, ElementsScrollViewer)) return;
+ if (_leftPanel.IsClickInside(original)) return;
_viewModel.SelectedElement = null;
}
- private static bool IsDescendantOf(DependencyObject? child, DependencyObject ancestor)
- {
- while (child != null && !ReferenceEquals(child, ancestor))
- {
- child = child is System.Windows.Media.Visual or System.Windows.Media.Media3D.Visual3D
- ? VisualTreeHelper.GetParent(child)
- : LogicalTreeHelper.GetParent(child);
- }
- return child != null;
- }
-
private void UpdateSelectionOverlay()
{
if (!_viewModel.IsEditMode)
@@ -197,249 +164,6 @@ public partial class MainWindow : Window
}
var selected = _viewModel.SelectedElement is { } s && Controls.PreviewPane.IsDraggableElement(s);
_previewPane.UpdateSelectionOverlay(selected);
- if (selected)
- OpacityValueText.Text = $"{Math.Round(_viewModel.SelectedElement!.Opacity * 100)}%";
- TakeSnapshot();
- }
-
- 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 (_viewModel.SelectedElement is WebcamSceneConfig webcam)
- webcam.IsMirrored = !webcam.IsMirrored;
- }
-
- private void ShapeButton_Click(object sender, RoutedEventArgs e)
- {
- if (_viewModel.SelectedElement is WebcamSceneConfig webcam)
- webcam.ToggleClipShape();
- }
-
- private void SourceMenu_Remove(object sender, RoutedEventArgs e)
- {
- if (sender is MenuItem { DataContext: SceneElement element })
- _viewModel.RemoveSourceCommand.Execute(element);
- }
-
- private void BackgroundMenu_ChangeCapture(object sender, RoutedEventArgs e)
- {
- _ = _viewModel.ChangeBackgroundCaptureAsync();
- }
-
- private void BackgroundMenu_RefreshCapture(object sender, RoutedEventArgs e)
- {
- _viewModel.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 Point _dragStartPoint;
- private int _dragIndex = -1;
- private bool _isDragging;
- private object? _lastHoveredItem;
-
- 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))
- _viewModel.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;
- }
-
- 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;
- }
-
- private void List_PreviewMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
- {
- EndListDrag((ListBox)sender);
- }
-
- private void EndListDrag(ListBox listBox)
- {
- if (_isDragging)
- listBox.ReleaseMouseCapture();
- _dragIndex = -1;
- _isDragging = false;
- _lastHoveredItem = null;
- }
-
- 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 Models.SceneElement? _snapshotElement;
- private string _snapshotBorderColor = string.Empty;
- private int _snapshotBorderWidth;
- private int _snapshotTimerMinutes = 15;
- private string? _snapshotWebUri;
-
- private void TakeSnapshot()
- {
- if (_viewModel.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 (_viewModel.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;
+ _leftPanel.OnSelectionChanged(_viewModel);
}
}
diff --git a/ViewModels/index.md b/ViewModels/index.md
index 3e9df15..457c356 100644
--- a/ViewModels/index.md
+++ b/ViewModels/index.md
@@ -58,7 +58,8 @@ Progress (Commit N/12 · tag):
- [x] **Commit 13 · `refactor-commit-13`** — **HealthBanner** → `Controls/HealthBanner.xaml` (Row 1). Also promoted the 7 value↔visibility converters out of `MainWindow.Window.Resources` into `Themes/Controls.xaml` so extracted controls can resolve them (app-level, not window-level).
- [x] **Commit 14 · `refactor-commit-14`** — **TopBar** → `Controls/TopBar.xaml(.cs)` (Row 0: logo, REC/ON-AIR pills + signs, avatar, Sign In/Start/End buttons). Avatar fetch moved (async-preserved) into the control; window exposes `_topBar.RefreshAvatar()` invoked on `IsConnected`/`AccountAvatarUrl` changes.
- [x] **Commit 15 · `refactor-commit-15`** — **PreviewPane** → `Controls/PreviewPane.xaml(.cs)` (Row 1 Col 1 center preview pane). Moved the 1920×1080 canvas (OverlayCanvas/SelectionOverlay/BrandFlashLayer/SocialBarElement), text pull-out, and the live-controls row (TRAX/game/mic meters + sliders + speakers + socials button). Code-behind moved: preview drag/select/resize/hit-test (Preview_Mouse*, HitHandle, HitElement), webcam-context-menu handlers (WebcamMenu_*), social-bar glow (SocialBar_*, SocialsButton_*, SocialGlowTick), audio sliders/speakers (VolumeSlider_*, GameVolumeSlider_*, MicSpeaker_*, GameSpeaker_*, SetSliderValueFromClick, FindParent), TraxButton_*. Window keeps the source-list left panel + thumbnail strip + edit UI, and routes selection-overlay via `_previewPane.UpdateSelectionOverlay(bool)` plus `_previewPane.IsClickInsidePreview/Drawer`. `BackgroundMenu_*` stayed in the window (used by the left-panel source context menu).
-- [x] **Commit 16 · `refactor-commit-16`** — **SceneThumbnailStrip** → `Controls/SceneThumbnailStrip.xaml(.cs)` (Row 0 scene-picker strip). `Thumbnail_MouseLeftButtonDown` moved into the control (transitions via `DataContext.TransitionToScene`). Pending: left scene/chat panel, overlay host.
+- [x] **Commit 16 · `refactor-commit-16`** — **SceneThumbnailStrip** → `Controls/SceneThumbnailStrip.xaml(.cs)` (Row 0 scene-picker strip). `Thumbnail_MouseLeftButtonDown` moved into the control (transitions via `DataContext.TransitionToScene`).
+- [x] **Commit 17 · `refactor-commit-17`** — **LeftPanel** → `Controls/LeftPanel.xaml(.cs)` (Row 1 Col 0 left panel: layers/properties edit UI + live chat list). Moved ALL property-editor and chat handlers: `SourceNameBox_*`/`CommitSourceEdit`, `AddSourceButton_Click`, `OnChatMessagesChanged` (now self-subscribed on Loaded), `MirrorButton_Click`, `ShapeButton_Click`, `SourceMenu_Remove`, `BackgroundMenu_*`, `OpacitySlider_ValueChanged`, list drag-to-reorder (`_dragStartPoint`+`List_Preview*`+`EndListDrag`+`FindItemContainerAt`), and the snapshot family (`TakeSnapshot`, `WebcamBorder*`, `Countdown*`, `Web*`). Window exposes via `_leftPanel.IsClickInside(original)` (deselect guard) and `_leftPanel.OnSelectionChanged(vm)` (opacity label + snapshot). Pending: overlay host region.
The partial `.cs` files land next to `MainViewModel.cs` in this folder as the split
proceeds; each partial carries its own `using`s and re-declares nothing from core.
\ No newline at end of file