[GUI] Fixes and improvements for the GUI, including new image assets and updates to language files. (#400)

This commit is contained in:
Berk
2026-07-18 17:44:04 +03:00
committed by GitHub
parent a709ccca17
commit 18708aa2d3
18 changed files with 559 additions and 31 deletions
+62 -11
View File
@@ -88,6 +88,10 @@ public partial class MainWindow : Window
private int _detailLoadGeneration;
private int _backdropGeneration;
// Bundled key art shown whenever no game-specific backdrop applies; the
// plain window color remains the fallback when the asset fails to load.
private Bitmap? _defaultBackdrop;
// Controller navigation state.
private readonly DispatcherTimer _gamepadTimer;
private HostGamepadButtons _previousPadButtons;
@@ -111,6 +115,18 @@ public partial class MainWindow : Window
{
InitializeComponent();
try
{
_defaultBackdrop = new Bitmap(
AssetLoader.Open(new Uri("avares://SharpEmu.GUI/Assets/pic0.png")));
BackdropImage.Source = _defaultBackdrop;
BackdropImage.Opacity = 1.0;
}
catch (Exception)
{
_defaultBackdrop = null; // color background remains the fallback
}
GameList.ItemsSource = _visibleGames;
ConsoleList.ItemsSource = _consoleLines;
_consoleMirror = GuiConsoleMirror.Install((line, isError) =>
@@ -1631,8 +1647,20 @@ public partial class MainWindow : Window
var generation = ++_backdropGeneration;
BackdropImage.Opacity = 0;
// The bundled key art is the primary backdrop whenever the selection
// has no art of its own; the window color stays as the last fallback.
void ShowDefaultBackdrop()
{
if (generation == _backdropGeneration && _defaultBackdrop is not null)
{
BackdropImage.Source = _defaultBackdrop;
BackdropImage.Opacity = 1.0;
}
}
if (game?.BackgroundPath is null)
{
ShowDefaultBackdrop();
return;
}
@@ -1649,7 +1677,8 @@ public partial class MainWindow : Window
}
catch (Exception)
{
return; // undecodable key art: keep the plain background
ShowDefaultBackdrop(); // undecodable key art
return;
}
}
@@ -1969,6 +1998,7 @@ public partial class MainWindow : Window
_awaitingFirstFrame = false;
ClearLibraryBlur();
MainContent.Margin = new Thickness(0);
RestoreGameViewToFull();
GameView.Background = Brushes.Black;
GameView.IsHitTestVisible = true;
_gameSurfaceHost?.SetPresentationVisible(true);
@@ -2030,11 +2060,31 @@ public partial class MainWindow : Window
}
}
/// <summary>
/// The native host attachment is a real child window: it sits above every
/// Avalonia control it covers and swallows their mouse input regardless of
/// hit-test settings. While the library must stay interactive (loading,
/// closing), the surface is parked offscreen AT FULL SIZE via a negative
/// margin. It must not be shrunk instead: the emulator child polls the
/// HWND client size and its presenter defers swapchain creation while the
/// surface is 1px, which would deadlock the loading handshake.
/// </summary>
private void ParkGameViewOffscreen()
{
GameView.Margin = new Thickness(-20000, 0, 20000, 0);
}
private void RestoreGameViewToFull()
{
GameView.Margin = new Thickness(0);
}
private void ShowGameView()
{
_isStopping = false;
_awaitingFirstFrame = true;
var host = EnsureGameSurfaceHost();
ParkGameViewOffscreen();
GameView.IsVisible = true;
GameView.Background = Brushes.Transparent;
GameView.IsHitTestVisible = false;
@@ -2068,16 +2118,15 @@ public partial class MainWindow : Window
LibraryPage.IsVisible = _activePageIndex == 0;
LibraryToolbar.IsVisible = _activePageIndex == 0;
OptionsPage.IsVisible = _activePageIndex == 1;
if (GameList.SelectedItem is GameEntry game && game.Background is not null)
{
BackdropImage.Opacity = 1;
}
// Game art when the source still holds it, otherwise the bundled
// default; a bare color only when neither is available.
BackdropImage.Opacity = BackdropImage.Source is not null ? 1 : 0;
}
private void AnimateLibraryBlur(double targetRadius, bool clearWhenComplete = false)
{
_libraryBlur ??= new BlurEffect();
MainContent.Effect = _libraryBlur;
PagesHost.Effect = _libraryBlur;
_libraryBlurStartRadius = _libraryBlur.Radius;
_libraryBlurTargetRadius = Math.Max(0, targetRadius);
@@ -2126,7 +2175,7 @@ public partial class MainWindow : Window
if (_clearLibraryBlurWhenComplete)
{
MainContent.Effect = null;
PagesHost.Effect = null;
_libraryBlur = null;
_clearLibraryBlurWhenComplete = false;
}
@@ -2137,7 +2186,7 @@ public partial class MainWindow : Window
_libraryBlurTimer.Stop();
_libraryBlur = null;
_clearLibraryBlurWhenComplete = false;
MainContent.Effect = null;
PagesHost.Effect = null;
}
private void ShowSessionLoading(string title, string detail)
@@ -2156,10 +2205,12 @@ public partial class MainWindow : Window
// Keep the native child alive until the session exits, but hide it
// immediately. Destroying it while Vulkan still owns the surface can
// crash the GUI; leaving it transparent lets the library recover
// while the native closing popup reports teardown progress.
// crash the GUI; parking it in the 1x1 corner lets the library
// recover — and stay clickable — while the native closing popup
// reports teardown progress.
_gameSurfaceHost?.SetPresentationVisible(false);
_awaitingFirstFrame = false;
ParkGameViewOffscreen();
GameView.Background = Brushes.Transparent;
GameView.IsHitTestVisible = false;
SessionBarPopup.IsOpen = false;
@@ -2171,7 +2222,7 @@ public partial class MainWindow : Window
LibraryPage.IsVisible = _activePageIndex == 0;
LibraryToolbar.IsVisible = _activePageIndex == 0;
OptionsPage.IsVisible = _activePageIndex == 1;
BackdropImage.Opacity = GameList.SelectedItem is GameEntry { Background: not null } ? 1 : 0;
BackdropImage.Opacity = BackdropImage.Source is not null ? 1 : 0;
UpdateRunButtons();
Console.Error.WriteLine("[GUI][INFO] Library restored while embedded session is closing.");
}