483 lines
16 KiB
C#
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;
|
|
}
|
|
}
|
|
}
|
|
}
|