mirror of
https://github.com/par274/sharpemu.git
synced 2026-07-19 01:16:12 +08:00
Merge branch 'main' of https://github.com/par274/sharpemu
This commit is contained in:
@@ -0,0 +1,150 @@
|
|||||||
|
// Copyright (C) 2026 SharpEmu Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
using Avalonia;
|
||||||
|
using Avalonia.Collections;
|
||||||
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Controls.Templates;
|
||||||
|
using Avalonia.Data;
|
||||||
|
using Avalonia.Layout;
|
||||||
|
using Avalonia.Media;
|
||||||
|
using Avalonia.Platform;
|
||||||
|
using Avalonia.Threading;
|
||||||
|
using System.Collections.Specialized;
|
||||||
|
|
||||||
|
namespace SharpEmu.GUI;
|
||||||
|
|
||||||
|
public sealed class ConsoleWindow : Window
|
||||||
|
{
|
||||||
|
private readonly AvaloniaList<LogLine> _sourceLines;
|
||||||
|
private readonly AvaloniaList<LogLine> _visibleLines = new();
|
||||||
|
private readonly ListBox _list;
|
||||||
|
private readonly TextBox _searchBox;
|
||||||
|
private readonly CheckBox _autoScrollCheck;
|
||||||
|
|
||||||
|
public ConsoleWindow(
|
||||||
|
AvaloniaList<LogLine> lines,
|
||||||
|
Action clear,
|
||||||
|
bool autoScroll)
|
||||||
|
{
|
||||||
|
_sourceLines = lines;
|
||||||
|
Title = "SharpEmu Console";
|
||||||
|
Width = 980;
|
||||||
|
Height = 620;
|
||||||
|
MinWidth = 520;
|
||||||
|
MinHeight = 320;
|
||||||
|
Background = new SolidColorBrush(Color.Parse("#0D1017"));
|
||||||
|
Icon = new WindowIcon(AssetLoader.Open(new Uri("avares://SharpEmu.GUI/Assets/SharpEmu.ico")));
|
||||||
|
|
||||||
|
_searchBox = new TextBox { Watermark = "Search...", Width = 320, Margin = new Thickness(0, 0, 12, 0) };
|
||||||
|
_autoScrollCheck = new CheckBox
|
||||||
|
{
|
||||||
|
Content = "Auto-scroll",
|
||||||
|
IsChecked = autoScroll,
|
||||||
|
FontSize = 12,
|
||||||
|
Margin = new Thickness(0, 0, 12, 0),
|
||||||
|
VerticalAlignment = VerticalAlignment.Center,
|
||||||
|
};
|
||||||
|
var copyButton = new Button
|
||||||
|
{
|
||||||
|
Classes = { "ghost" },
|
||||||
|
Content = "Copy",
|
||||||
|
Padding = new Thickness(10, 4),
|
||||||
|
Margin = new Thickness(0, 0, 8, 0),
|
||||||
|
};
|
||||||
|
var clearButton = new Button { Classes = { "ghost" }, Content = "Clear", Padding = new Thickness(10, 4) };
|
||||||
|
copyButton.Click += async (_, _) => await CopyAsync();
|
||||||
|
clearButton.Click += (_, _) => clear();
|
||||||
|
_searchBox.TextChanged += (_, _) => RefreshVisibleLines();
|
||||||
|
|
||||||
|
_list = new ListBox
|
||||||
|
{
|
||||||
|
Classes = { "console" },
|
||||||
|
ItemsSource = _visibleLines,
|
||||||
|
BorderThickness = new Thickness(1),
|
||||||
|
BorderBrush = new SolidColorBrush(Color.Parse("#232B3A")),
|
||||||
|
ItemTemplate = new FuncDataTemplate<LogLine>((_, _) =>
|
||||||
|
{
|
||||||
|
var text = new TextBlock { TextWrapping = TextWrapping.NoWrap };
|
||||||
|
text.Bind(TextBlock.TextProperty, new Binding(nameof(LogLine.Text)));
|
||||||
|
text.Bind(TextBlock.ForegroundProperty, new Binding(nameof(LogLine.Brush)));
|
||||||
|
return text;
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
|
Content = new Grid
|
||||||
|
{
|
||||||
|
Margin = new Thickness(12),
|
||||||
|
RowDefinitions = new RowDefinitions("Auto,*"),
|
||||||
|
Children =
|
||||||
|
{
|
||||||
|
new Grid
|
||||||
|
{
|
||||||
|
Margin = new Thickness(0, 0, 0, 8),
|
||||||
|
ColumnDefinitions = new ColumnDefinitions("*,Auto,Auto,Auto,Auto"),
|
||||||
|
Children =
|
||||||
|
{
|
||||||
|
new TextBlock
|
||||||
|
{
|
||||||
|
Classes = { "sectionTitle" },
|
||||||
|
Text = "CONSOLE",
|
||||||
|
VerticalAlignment = VerticalAlignment.Center,
|
||||||
|
},
|
||||||
|
_searchBox.WithGridColumn(1),
|
||||||
|
_autoScrollCheck.WithGridColumn(2),
|
||||||
|
copyButton.WithGridColumn(3),
|
||||||
|
clearButton.WithGridColumn(4),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
_list.WithGridRow(1),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
lines.CollectionChanged += OnLinesChanged;
|
||||||
|
Closed += (_, _) => lines.CollectionChanged -= OnLinesChanged;
|
||||||
|
RefreshVisibleLines();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnLinesChanged(object? sender, NotifyCollectionChangedEventArgs e)
|
||||||
|
{
|
||||||
|
RefreshVisibleLines();
|
||||||
|
if (_autoScrollCheck.IsChecked == true)
|
||||||
|
{
|
||||||
|
Dispatcher.UIThread.Post(() => (_list.Scroll as ScrollViewer)?.ScrollToEnd());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RefreshVisibleLines()
|
||||||
|
{
|
||||||
|
var query = _searchBox.Text ?? string.Empty;
|
||||||
|
_visibleLines.Clear();
|
||||||
|
_visibleLines.AddRange(string.IsNullOrWhiteSpace(query)
|
||||||
|
? _sourceLines
|
||||||
|
: _sourceLines.Where(line => line.Text.Contains(query, StringComparison.OrdinalIgnoreCase)));
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task CopyAsync()
|
||||||
|
{
|
||||||
|
if (_visibleLines.Count == 0 || Clipboard is null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await Clipboard.SetTextAsync(string.Join(Environment.NewLine, _visibleLines.Select(line => line.Text)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
file static class GridExtensions
|
||||||
|
{
|
||||||
|
public static T WithGridColumn<T>(this T control, int column) where T : Control
|
||||||
|
{
|
||||||
|
Grid.SetColumn(control, column);
|
||||||
|
return control;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static T WithGridRow<T>(this T control, int row) where T : Control
|
||||||
|
{
|
||||||
|
Grid.SetRow(control, row);
|
||||||
|
return control;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -160,15 +160,17 @@ SPDX-License-Identifier: GPL-2.0-or-later
|
|||||||
<Border Grid.Row="2" x:Name="ConsolePanel" Classes="card" Padding="0" Height="240"
|
<Border Grid.Row="2" x:Name="ConsolePanel" Classes="card" Padding="0" Height="240"
|
||||||
Margin="0,12,0,0" IsVisible="False">
|
Margin="0,12,0,0" IsVisible="False">
|
||||||
<Grid RowDefinitions="Auto,*">
|
<Grid RowDefinitions="Auto,*">
|
||||||
<Grid Grid.Row="0" ColumnDefinitions="*,Auto,Auto,Auto, Auto" Margin="16,12,16,8">
|
<Grid Grid.Row="0" ColumnDefinitions="*,Auto,Auto,Auto,Auto,Auto" Margin="16,12,16,8">
|
||||||
<TextBlock Classes="sectionTitle" Text="CONSOLE" VerticalAlignment="Center" />
|
<TextBlock Classes="sectionTitle" Text="CONSOLE" VerticalAlignment="Center" />
|
||||||
<TextBox Grid.Column="1" FontSize="12" Margin="0,0,12,0" x:Name="ConsoleSearchBox"
|
<TextBox Grid.Column="1" FontSize="12" Margin="0,0,12,0" x:Name="ConsoleSearchBox"
|
||||||
Watermark="Search..." MaxWidth="5500" />
|
Watermark="Search..." Width="320" />
|
||||||
<CheckBox Grid.Column="2" x:Name="AutoScrollCheck" Content="Auto-scroll" IsChecked="True"
|
<CheckBox Grid.Column="2" x:Name="AutoScrollCheck" Content="Auto-scroll" IsChecked="True"
|
||||||
FontSize="12" Margin="0,0,12,0" />
|
FontSize="12" Margin="0,0,12,0" />
|
||||||
<Button Grid.Column="3" x:Name="CopyLogButton" Classes="ghost" Content="Copy" FontSize="12"
|
<Button Grid.Column="3" x:Name="DetachConsoleButton" Classes="ghost" Content="Split" FontSize="12"
|
||||||
Padding="10,4" Margin="0,0,8,0" />
|
Padding="10,4" Margin="0,0,8,0" />
|
||||||
<Button Grid.Column="4" x:Name="ClearLogButton" Classes="ghost" Content="Clear" FontSize="12"
|
<Button Grid.Column="4" x:Name="CopyLogButton" Classes="ghost" Content="Copy" FontSize="12"
|
||||||
|
Padding="10,4" Margin="0,0,8,0" />
|
||||||
|
<Button Grid.Column="5" x:Name="ClearLogButton" Classes="ghost" Content="Clear" FontSize="12"
|
||||||
Padding="10,4" />
|
Padding="10,4" />
|
||||||
</Grid>
|
</Grid>
|
||||||
<ListBox Grid.Row="1" x:Name="ConsoleList" Classes="console" BorderThickness="0,1,0,0"
|
<ListBox Grid.Row="1" x:Name="ConsoleList" Classes="console" BorderThickness="0,1,0,0"
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ public partial class MainWindow : Window
|
|||||||
|
|
||||||
private GuiSettings _settings = new();
|
private GuiSettings _settings = new();
|
||||||
private EmulatorProcess? _emulator;
|
private EmulatorProcess? _emulator;
|
||||||
|
private ConsoleWindow? _consoleWindow;
|
||||||
private StreamWriter? _fileLog;
|
private StreamWriter? _fileLog;
|
||||||
private readonly SndPreviewPlayer _sndPreview = new();
|
private readonly SndPreviewPlayer _sndPreview = new();
|
||||||
private string? _emulatorExePath;
|
private string? _emulatorExePath;
|
||||||
@@ -98,8 +99,9 @@ public partial class MainWindow : Window
|
|||||||
StopButton.Click += (_, _) => StopEmulator();
|
StopButton.Click += (_, _) => StopEmulator();
|
||||||
ClearLogButton.Click += (_, _) => _consoleLines.Clear();
|
ClearLogButton.Click += (_, _) => _consoleLines.Clear();
|
||||||
CopyLogButton.Click += async (_, _) => await CopyConsoleAsync();
|
CopyLogButton.Click += async (_, _) => await CopyConsoleAsync();
|
||||||
|
DetachConsoleButton.Click += (_, _) => ShowConsoleWindow();
|
||||||
OptionsToggle.IsCheckedChanged += (_, _) => OptionsPanel.IsVisible = OptionsToggle.IsChecked == true;
|
OptionsToggle.IsCheckedChanged += (_, _) => OptionsPanel.IsVisible = OptionsToggle.IsChecked == true;
|
||||||
ConsoleToggle.IsCheckedChanged += (_, _) => ConsolePanel.IsVisible = ConsoleToggle.IsChecked == true;
|
ConsoleToggle.IsCheckedChanged += (_, _) => ConsolePanel.IsVisible = ConsoleToggle.IsChecked == true && _consoleWindow is null;
|
||||||
SelectLogFilePathButton.Click += async (_, _) => await SelectFilePathAsync();
|
SelectLogFilePathButton.Click += async (_, _) => await SelectFilePathAsync();
|
||||||
TitleMusicToggle.IsCheckedChanged += (_, _) => OnTitleMusicToggled();
|
TitleMusicToggle.IsCheckedChanged += (_, _) => OnTitleMusicToggled();
|
||||||
DiscordToggle.IsCheckedChanged += (_, _) =>
|
DiscordToggle.IsCheckedChanged += (_, _) =>
|
||||||
@@ -329,6 +331,7 @@ public partial class MainWindow : Window
|
|||||||
_gamepadTimer.Stop();
|
_gamepadTimer.Stop();
|
||||||
_sndPreview.Stop();
|
_sndPreview.Stop();
|
||||||
_discord?.Dispose();
|
_discord?.Dispose();
|
||||||
|
_consoleWindow?.Close();
|
||||||
_emulator?.Dispose();
|
_emulator?.Dispose();
|
||||||
DropFileLog();
|
DropFileLog();
|
||||||
}
|
}
|
||||||
@@ -1470,4 +1473,28 @@ public partial class MainWindow : Window
|
|||||||
var text = string.Join(Environment.NewLine, _consoleLines.Select(line => line.Text));
|
var text = string.Join(Environment.NewLine, _consoleLines.Select(line => line.Text));
|
||||||
await Clipboard.SetTextAsync(text);
|
await Clipboard.SetTextAsync(text);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void ShowConsoleWindow()
|
||||||
|
{
|
||||||
|
if (_consoleWindow is { } window)
|
||||||
|
{
|
||||||
|
window.Activate();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ConsoleSearchBox.Text = string.Empty;
|
||||||
|
ConsoleToggle.IsChecked = false;
|
||||||
|
ConsolePanel.IsVisible = false;
|
||||||
|
_consoleWindow = new ConsoleWindow(
|
||||||
|
_consoleLines,
|
||||||
|
() => { _consoleLines.Clear(); _allConsoleLines.Clear(); },
|
||||||
|
AutoScrollCheck.IsChecked == true);
|
||||||
|
_consoleWindow.Closed += (_, _) =>
|
||||||
|
{
|
||||||
|
_consoleWindow = null;
|
||||||
|
ConsoleToggle.IsChecked = true;
|
||||||
|
ConsolePanel.IsVisible = true;
|
||||||
|
};
|
||||||
|
_consoleWindow.Show(this);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user