pro3_control_panel_v2/Views/Controls/ConsolePanel.xaml.cs
2025-12-23 14:12:21 +08:00

483 lines
16 KiB
C#

using System.Collections.Specialized;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Input;
using System.Windows.Media;
using AmebaPro3_ControlPanel.ViewModels;
namespace AmebaPro3_ControlPanel.Views.Controls;
public partial class ConsolePanel : UserControl
{
public static readonly DependencyProperty HeaderContentProperty =
DependencyProperty.Register(nameof(HeaderContent), typeof(UIElement), typeof(ConsolePanel), new PropertyMetadata(null));
public static readonly DependencyProperty CommandButtonsContentProperty =
DependencyProperty.Register(nameof(CommandButtonsContent), typeof(UIElement), typeof(ConsolePanel), new PropertyMetadata(null));
public UIElement? HeaderContent
{
get => (UIElement?)GetValue(HeaderContentProperty);
set => SetValue(HeaderContentProperty, value);
}
public UIElement? CommandButtonsContent
{
get => (UIElement?)GetValue(CommandButtonsContentProperty);
set => SetValue(CommandButtonsContentProperty, value);
}
private bool _isAutoScrolling = true;
private ScrollViewer? _logScrollViewer;
private bool _isProgrammaticScroll = false;
private bool _userScrollIntent = false;
private bool _isHistoryAutoScrolling = true;
private ScrollViewer? _historyScrollViewer;
private bool _isHistoryProgrammaticScroll = false;
private bool _historyUserScrollIntent = false;
public ConsolePanel()
{
InitializeComponent();
Loaded += ConsolePanel_Loaded;
}
private void ConsolePanel_Loaded(object sender, RoutedEventArgs e)
{
if (DataContext is ConsolePanelViewModel vm)
{
vm.LogLines.CollectionChanged += LogLines_CollectionChanged;
vm.History.CollectionChanged += History_CollectionChanged;
// Scroll to bottom if there are already items
if (vm.LogLines.Count > 0)
{
Dispatcher.BeginInvoke(new System.Action(() =>
{
if (_logScrollViewer != null)
{
_isProgrammaticScroll = true;
_logScrollViewer.ScrollToEnd();
_isProgrammaticScroll = false;
}
else if (LogListBox.Items.Count > 0)
{
LogListBox.ScrollIntoView(LogListBox.Items[LogListBox.Items.Count - 1]);
}
}), System.Windows.Threading.DispatcherPriority.Loaded);
}
if (vm.History.Count > 0)
{
Dispatcher.BeginInvoke(new System.Action(() =>
{
if (_historyScrollViewer != null)
{
_isHistoryProgrammaticScroll = true;
_historyScrollViewer.ScrollToEnd();
_isHistoryProgrammaticScroll = false;
}
}), System.Windows.Threading.DispatcherPriority.Loaded);
}
}
// Find the ScrollViewer in the LogListBox
_logScrollViewer = FindScrollViewer(LogListBox);
if (_logScrollViewer != null)
{
_logScrollViewer.ScrollChanged += LogScrollViewer_ScrollChanged;
// Initialize auto-scroll state
_isAutoScrolling = IsAtBottom(_logScrollViewer);
}
_historyScrollViewer = FindScrollViewer(HistoryListBox);
if (_historyScrollViewer != null)
{
_historyScrollViewer.ScrollChanged += HistoryScrollViewer_ScrollChanged;
_isHistoryAutoScrolling = IsAtBottom(_historyScrollViewer);
}
}
private void LogLines_CollectionChanged(object? sender, NotifyCollectionChangedEventArgs e)
{
if (e.Action == NotifyCollectionChangedAction.Add)
{
// Ensure ScrollViewer is found (might not be available immediately)
if (_logScrollViewer == null)
{
_logScrollViewer = FindScrollViewer(LogListBox);
if (_logScrollViewer != null)
{
_logScrollViewer.ScrollChanged += LogScrollViewer_ScrollChanged;
}
}
// Check if we should auto-scroll (verify current position before scrolling)
bool shouldAutoScroll = _isAutoScrolling && !_userScrollIntent;
if (_logScrollViewer != null && !_userScrollIntent)
{
shouldAutoScroll = IsAtBottom(_logScrollViewer);
}
if (shouldAutoScroll)
{
// Scroll to the bottom after UI updates
Dispatcher.BeginInvoke(new System.Action(() =>
{
if (_logScrollViewer != null)
{
_isProgrammaticScroll = true;
_logScrollViewer.ScrollToEnd();
_isProgrammaticScroll = false;
}
else
{
// Fallback: try to find ScrollViewer again and scroll
_logScrollViewer = FindScrollViewer(LogListBox);
if (_logScrollViewer != null)
{
_logScrollViewer.ScrollChanged += LogScrollViewer_ScrollChanged;
_isProgrammaticScroll = true;
_logScrollViewer.ScrollToEnd();
_isProgrammaticScroll = false;
}
else if (LogListBox.Items.Count > 0)
{
// Last resort: use ScrollIntoView
LogListBox.ScrollIntoView(LogListBox.Items[LogListBox.Items.Count - 1]);
}
}
}), System.Windows.Threading.DispatcherPriority.Loaded);
}
}
}
private void LogScrollViewer_ScrollChanged(object sender, ScrollChangedEventArgs e)
{
if (_logScrollViewer == null) return;
// Ignore scroll changes caused by programmatic scrolling
if (_isProgrammaticScroll)
{
return;
}
if (e.VerticalChange != 0)
{
_userScrollIntent = false;
}
if (!_userScrollIntent)
{
_isAutoScrolling = IsAtBottom(_logScrollViewer);
}
}
private void History_CollectionChanged(object? sender, NotifyCollectionChangedEventArgs e)
{
if (e.Action == NotifyCollectionChangedAction.Add)
{
if (_historyScrollViewer == null)
{
_historyScrollViewer = FindScrollViewer(HistoryListBox);
if (_historyScrollViewer != null)
{
_historyScrollViewer.ScrollChanged += HistoryScrollViewer_ScrollChanged;
}
}
bool shouldAutoScroll = _isHistoryAutoScrolling && !_historyUserScrollIntent;
if (_historyScrollViewer != null && !_historyUserScrollIntent)
{
shouldAutoScroll = IsAtBottom(_historyScrollViewer);
}
if (shouldAutoScroll)
{
Dispatcher.BeginInvoke(new System.Action(() =>
{
if (_historyScrollViewer != null)
{
_isHistoryProgrammaticScroll = true;
_historyScrollViewer.ScrollToEnd();
_isHistoryProgrammaticScroll = false;
}
}), System.Windows.Threading.DispatcherPriority.Loaded);
}
}
}
private void HistoryScrollViewer_ScrollChanged(object sender, ScrollChangedEventArgs e)
{
if (_historyScrollViewer == null) return;
if (_isHistoryProgrammaticScroll)
{
return;
}
if (e.VerticalChange != 0)
{
_historyUserScrollIntent = false;
}
if (!_historyUserScrollIntent)
{
_isHistoryAutoScrolling = IsAtBottom(_historyScrollViewer);
}
}
private static bool IsAtBottom(ScrollViewer scrollViewer)
{
return scrollViewer.ExtentHeight <= scrollViewer.ViewportHeight
|| (scrollViewer.VerticalOffset + scrollViewer.ViewportHeight >= scrollViewer.ExtentHeight - 5);
}
private static ScrollViewer? FindScrollViewer(DependencyObject parent)
{
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(parent); i++)
{
var child = VisualTreeHelper.GetChild(parent, i);
if (child is ScrollViewer scrollViewer)
{
return scrollViewer;
}
var result = FindScrollViewer(child);
if (result != null)
{
return result;
}
}
return null;
}
private void LogListBox_OnPreviewMouseWheel(object sender, MouseWheelEventArgs e)
{
MarkUserScrollIntent();
}
private void LogListBox_OnPreviewMouseDown(object sender, MouseButtonEventArgs e)
{
if (e.ChangedButton == MouseButton.Left || e.ChangedButton == MouseButton.Middle || e.ChangedButton == MouseButton.Right)
{
MarkUserScrollIntent();
}
}
private void LogListBox_OnPreviewKeyDown(object sender, KeyEventArgs e)
{
if (e.Key is Key.Up or Key.Down or Key.PageUp or Key.PageDown or Key.Home or Key.End)
{
MarkUserScrollIntent();
}
}
private void MarkUserScrollIntent()
{
_userScrollIntent = true;
if (_logScrollViewer == null)
{
_logScrollViewer = FindScrollViewer(LogListBox);
if (_logScrollViewer != null)
{
_logScrollViewer.ScrollChanged += LogScrollViewer_ScrollChanged;
}
}
Dispatcher.BeginInvoke(new System.Action(() =>
{
if (_logScrollViewer == null)
{
return;
}
if (_userScrollIntent)
{
_userScrollIntent = false;
_isAutoScrolling = IsAtBottom(_logScrollViewer);
}
}), System.Windows.Threading.DispatcherPriority.Background);
}
private void HistoryListBox_OnPreviewMouseWheel(object sender, MouseWheelEventArgs e)
{
MarkHistoryUserScrollIntent();
}
private void HistoryListBox_OnPreviewMouseDown(object sender, MouseButtonEventArgs e)
{
if (e.ChangedButton == MouseButton.Left || e.ChangedButton == MouseButton.Middle || e.ChangedButton == MouseButton.Right)
{
MarkHistoryUserScrollIntent();
}
}
private void HistoryListBox_OnPreviewKeyDown(object sender, KeyEventArgs e)
{
if (e.Key is Key.Up or Key.Down or Key.PageUp or Key.PageDown or Key.Home or Key.End)
{
MarkHistoryUserScrollIntent();
}
}
private void MarkHistoryUserScrollIntent()
{
_historyUserScrollIntent = true;
if (_historyScrollViewer == null)
{
_historyScrollViewer = FindScrollViewer(HistoryListBox);
if (_historyScrollViewer != null)
{
_historyScrollViewer.ScrollChanged += HistoryScrollViewer_ScrollChanged;
}
}
Dispatcher.BeginInvoke(new System.Action(() =>
{
if (_historyScrollViewer == null)
{
return;
}
if (_historyUserScrollIntent)
{
_historyUserScrollIntent = false;
_isHistoryAutoScrolling = IsAtBottom(_historyScrollViewer);
}
}), System.Windows.Threading.DispatcherPriority.Background);
}
private void HistoryListBox_OnSelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (DataContext is ConsolePanelViewModel vm)
{
// Only update CurrentCommand if single item is selected
if (HistoryListBox.SelectedItems.Count == 1 && HistoryListBox.SelectedItem is string selected)
{
vm.CurrentCommand = selected;
}
else if (HistoryListBox.SelectedItems.Count == 0)
{
vm.CurrentCommand = string.Empty;
}
// For multi-select, don't update CurrentCommand
}
}
private void HistoryListBox_OnMouseDoubleClick(object sender, MouseButtonEventArgs e)
{
if (DataContext is ConsolePanelViewModel vm && HistoryListBox.SelectedItem is string selected)
{
vm.SendFromHistory(selected);
}
}
private void CommandInput_OnKeyDown(object sender, KeyEventArgs e)
{
if (DataContext is not ConsolePanelViewModel vm)
{
return;
}
if (e.Key == Key.Enter)
{
if (vm.SendCommand.CanExecute(null))
{
vm.SendCommand.Execute(null);
HistoryListBox.SelectedIndex = -1;
}
e.Handled = true;
}
else if (e.Key == Key.Up)
{
if (HistoryListBox.Items.Count == 0)
{
return;
}
if (HistoryListBox.SelectedIndex < 0)
{
HistoryListBox.SelectedIndex = HistoryListBox.Items.Count - 1;
}
else if (HistoryListBox.SelectedIndex > 0)
{
HistoryListBox.SelectedIndex -= 1;
}
e.Handled = true;
}
else if (e.Key == Key.Down)
{
if (HistoryListBox.Items.Count == 0)
{
return;
}
if (HistoryListBox.SelectedIndex >= 0 && HistoryListBox.SelectedIndex < HistoryListBox.Items.Count - 1)
{
HistoryListBox.SelectedIndex += 1;
}
else
{
HistoryListBox.SelectedIndex = -1;
vm.CurrentCommand = string.Empty;
}
e.Handled = true;
}
}
private void HistoryListBox_OnKeyDown(object sender, KeyEventArgs e)
{
if (e.Key == Key.Delete && DataContext is ConsolePanelViewModel vm)
{
var selectedItems = HistoryListBox.SelectedItems.Cast<string>().ToList();
if (selectedItems.Count > 0)
{
vm.DeleteHistoryItems(selectedItems);
// Clear selection after deletion
HistoryListBox.SelectedIndex = -1;
e.Handled = true;
}
}
}
private void LogListBox_OnKeyDown(object sender, KeyEventArgs e)
{
if (DataContext is not ConsolePanelViewModel vm)
{
return;
}
if (e.Key == Key.Delete)
{
var selectedItems = LogListBox.SelectedItems.Cast<string>().ToList();
if (selectedItems.Count > 0)
{
vm.DeleteLogLines(selectedItems);
// Clear selection after deletion
LogListBox.SelectedIndex = -1;
e.Handled = true;
}
}
else if (e.Key == Key.C && (Keyboard.Modifiers & ModifierKeys.Control) == ModifierKeys.Control)
{
// Copy selected items to clipboard
var selectedItems = LogListBox.SelectedItems.Cast<string>().ToList();
if (selectedItems.Count > 0)
{
var clipboardText = string.Join("\r\n", selectedItems);
Clipboard.SetText(clipboardText);
e.Handled = true;
}
}
}
}