mirror of
https://git.ryujinx.app/ryubing/ryujinx.git
synced 2025-06-27 22:06:24 +02:00
Controller overlay showing which player is bound to which controller
This commit is contained in:
parent
74a9b94227
commit
8765dc9901
3 changed files with 243 additions and 0 deletions
|
@ -32,6 +32,7 @@ using Ryujinx.Ava.UI.Windows;
|
||||||
using Ryujinx.Ava.Utilities;
|
using Ryujinx.Ava.Utilities;
|
||||||
using Ryujinx.Common;
|
using Ryujinx.Common;
|
||||||
using Ryujinx.Common.Configuration;
|
using Ryujinx.Common.Configuration;
|
||||||
|
using Ryujinx.Common.Configuration.Hid;
|
||||||
using Ryujinx.Common.Helper;
|
using Ryujinx.Common.Helper;
|
||||||
using Ryujinx.Common.Logging;
|
using Ryujinx.Common.Logging;
|
||||||
using Ryujinx.Common.UI;
|
using Ryujinx.Common.UI;
|
||||||
|
@ -1690,11 +1691,37 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||||
SetMainContent(RendererHostControl);
|
SetMainContent(RendererHostControl);
|
||||||
|
|
||||||
RendererHostControl.Focus();
|
RendererHostControl.Focus();
|
||||||
|
|
||||||
|
// Show controller overlay
|
||||||
|
ShowControllerOverlay();
|
||||||
});
|
});
|
||||||
|
|
||||||
public static void UpdateGameMetadata(string titleId, TimeSpan playTime)
|
public static void UpdateGameMetadata(string titleId, TimeSpan playTime)
|
||||||
=> ApplicationLibrary.LoadAndSaveMetaData(titleId, appMetadata => appMetadata.UpdatePostGame(playTime));
|
=> ApplicationLibrary.LoadAndSaveMetaData(titleId, appMetadata => appMetadata.UpdatePostGame(playTime));
|
||||||
|
|
||||||
|
private void ShowControllerOverlay()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var inputConfigs = ConfigurationState.Instance.System.UseInputGlobalConfig.Value && Program.UseExtraConfig
|
||||||
|
? ConfigurationState.InstanceExtra.Hid.InputConfig.Value
|
||||||
|
: ConfigurationState.Instance.Hid.InputConfig.Value;
|
||||||
|
|
||||||
|
// Only show overlay if there are actual controller configurations for players 1-4
|
||||||
|
if (inputConfigs?.Any(c => c.PlayerIndex <= PlayerIndex.Player4) == true)
|
||||||
|
{
|
||||||
|
var overlay = new Windows.ControllerOverlayWindow(Window);
|
||||||
|
overlay.ShowControllerBindings(inputConfigs);
|
||||||
|
overlay.Show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
// Log the error but don't let it crash the game launch
|
||||||
|
Logger.Error?.Print(LogClass.UI, $"Failed to show controller overlay: {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void RefreshFirmwareStatus()
|
public void RefreshFirmwareStatus()
|
||||||
{
|
{
|
||||||
SystemVersion version = null;
|
SystemVersion version = null;
|
||||||
|
|
50
src/Ryujinx/UI/Windows/ControllerOverlayWindow.axaml
Normal file
50
src/Ryujinx/UI/Windows/ControllerOverlayWindow.axaml
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
<window:StyleableAppWindow
|
||||||
|
xmlns="https://github.com/avaloniaui"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
xmlns:window="clr-namespace:Ryujinx.Ava.UI.Windows"
|
||||||
|
mc:Ignorable="d"
|
||||||
|
d:DesignWidth="400"
|
||||||
|
d:DesignHeight="300"
|
||||||
|
x:Class="Ryujinx.Ava.UI.Windows.ControllerOverlayWindow"
|
||||||
|
Title="Controller Overlay"
|
||||||
|
Focusable="False"
|
||||||
|
Topmost="True"
|
||||||
|
WindowStartupLocation="CenterScreen"
|
||||||
|
Width="400"
|
||||||
|
Height="250"
|
||||||
|
SizeToContent="Height">
|
||||||
|
|
||||||
|
<Border Background="#E0000000"
|
||||||
|
CornerRadius="12"
|
||||||
|
Padding="24"
|
||||||
|
BorderBrush="#40FFFFFF"
|
||||||
|
BorderThickness="1">
|
||||||
|
<StackPanel Spacing="16">
|
||||||
|
<StackPanel Orientation="Horizontal"
|
||||||
|
HorizontalAlignment="Center"
|
||||||
|
Spacing="8">
|
||||||
|
<TextBlock Text="🎮"
|
||||||
|
FontSize="18"
|
||||||
|
VerticalAlignment="Center"/>
|
||||||
|
<TextBlock Text="Controller Bindings"
|
||||||
|
FontSize="18"
|
||||||
|
FontWeight="Bold"
|
||||||
|
Foreground="White"
|
||||||
|
VerticalAlignment="Center"/>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<StackPanel Name="PlayerBindings" Spacing="10">
|
||||||
|
<!-- Player bindings will be added programmatically -->
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<TextBlock Text="This overlay will disappear in a few seconds"
|
||||||
|
FontSize="11"
|
||||||
|
Foreground="#AAAAAA"
|
||||||
|
HorizontalAlignment="Center"
|
||||||
|
Margin="0,8,0,0"
|
||||||
|
FontStyle="Italic"/>
|
||||||
|
</StackPanel>
|
||||||
|
</Border>
|
||||||
|
</window:StyleableAppWindow>
|
166
src/Ryujinx/UI/Windows/ControllerOverlayWindow.axaml.cs
Normal file
166
src/Ryujinx/UI/Windows/ControllerOverlayWindow.axaml.cs
Normal file
|
@ -0,0 +1,166 @@
|
||||||
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Media;
|
||||||
|
using Avalonia.Threading;
|
||||||
|
using Ryujinx.Common.Configuration.Hid;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Ryujinx.Ava.UI.Windows
|
||||||
|
{
|
||||||
|
public partial class ControllerOverlayWindow : StyleableWindow
|
||||||
|
{
|
||||||
|
private const int AutoHideDelayMs = 4000; // 4 seconds
|
||||||
|
|
||||||
|
public ControllerOverlayWindow()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
|
||||||
|
TransparencyLevelHint = [WindowTransparencyLevel.Transparent];
|
||||||
|
SystemDecorations = SystemDecorations.None;
|
||||||
|
ExtendClientAreaTitleBarHeightHint = 0;
|
||||||
|
Background = Brushes.Transparent;
|
||||||
|
CanResize = false;
|
||||||
|
ShowInTaskbar = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ControllerOverlayWindow(Window owner) : this()
|
||||||
|
{
|
||||||
|
if (owner != null)
|
||||||
|
{
|
||||||
|
// Position the overlay in the top-right corner of the owner window
|
||||||
|
WindowStartupLocation = WindowStartupLocation.Manual;
|
||||||
|
|
||||||
|
// Set position after the window is loaded
|
||||||
|
Loaded += (s, e) =>
|
||||||
|
{
|
||||||
|
if (owner.WindowState != WindowState.Minimized)
|
||||||
|
{
|
||||||
|
Position = new Avalonia.PixelPoint(
|
||||||
|
(int)(owner.Position.X + owner.Width - Width - 20),
|
||||||
|
(int)(owner.Position.Y + 50)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ShowControllerBindings(List<InputConfig> inputConfigs)
|
||||||
|
{
|
||||||
|
// Clear existing bindings
|
||||||
|
PlayerBindings.Children.Clear();
|
||||||
|
|
||||||
|
// Group controllers by player index
|
||||||
|
var playerBindings = new Dictionary<PlayerIndex, List<InputConfig>>();
|
||||||
|
|
||||||
|
foreach (var config in inputConfigs.Where(c => c.PlayerIndex <= PlayerIndex.Player4))
|
||||||
|
{
|
||||||
|
if (!playerBindings.ContainsKey(config.PlayerIndex))
|
||||||
|
{
|
||||||
|
playerBindings[config.PlayerIndex] = new List<InputConfig>();
|
||||||
|
}
|
||||||
|
playerBindings[config.PlayerIndex].Add(config);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add player bindings to UI
|
||||||
|
for (int i = 0; i < 4; i++)
|
||||||
|
{
|
||||||
|
var playerIndex = (PlayerIndex)i;
|
||||||
|
var playerPanel = new StackPanel { Orientation = Avalonia.Layout.Orientation.Horizontal, Spacing = 12 };
|
||||||
|
|
||||||
|
// Player number with colored background
|
||||||
|
var playerNumberBorder = new Border
|
||||||
|
{
|
||||||
|
Background = GetPlayerColor(i),
|
||||||
|
CornerRadius = new Avalonia.CornerRadius(12),
|
||||||
|
Padding = new Avalonia.Thickness(8, 4),
|
||||||
|
VerticalAlignment = Avalonia.Layout.VerticalAlignment.Center
|
||||||
|
};
|
||||||
|
|
||||||
|
var playerLabel = new TextBlock
|
||||||
|
{
|
||||||
|
Text = $"P{i + 1}",
|
||||||
|
FontWeight = FontWeight.Bold,
|
||||||
|
Foreground = Brushes.White,
|
||||||
|
FontSize = 12,
|
||||||
|
HorizontalAlignment = Avalonia.Layout.HorizontalAlignment.Center
|
||||||
|
};
|
||||||
|
playerNumberBorder.Child = playerLabel;
|
||||||
|
playerPanel.Children.Add(playerNumberBorder);
|
||||||
|
|
||||||
|
if (playerBindings.ContainsKey(playerIndex))
|
||||||
|
{
|
||||||
|
var controllers = playerBindings[playerIndex];
|
||||||
|
var controllerNames = controllers.Select(c => GetControllerDisplayName(c)).ToList();
|
||||||
|
|
||||||
|
var controllerText = new TextBlock
|
||||||
|
{
|
||||||
|
Text = string.Join(", ", controllerNames),
|
||||||
|
Foreground = Brushes.LightGreen,
|
||||||
|
VerticalAlignment = Avalonia.Layout.VerticalAlignment.Center,
|
||||||
|
FontWeight = FontWeight.SemiBold
|
||||||
|
};
|
||||||
|
playerPanel.Children.Add(controllerText);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var noControllerText = new TextBlock
|
||||||
|
{
|
||||||
|
Text = "No controller assigned",
|
||||||
|
Foreground = Brushes.Gray,
|
||||||
|
VerticalAlignment = Avalonia.Layout.VerticalAlignment.Center,
|
||||||
|
FontStyle = FontStyle.Italic
|
||||||
|
};
|
||||||
|
playerPanel.Children.Add(noControllerText);
|
||||||
|
}
|
||||||
|
|
||||||
|
PlayerBindings.Children.Add(playerPanel);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Auto-hide after delay
|
||||||
|
_ = Task.Run(async () =>
|
||||||
|
{
|
||||||
|
await Task.Delay(AutoHideDelayMs);
|
||||||
|
await Dispatcher.UIThread.InvokeAsync(() =>
|
||||||
|
{
|
||||||
|
Close();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IBrush GetPlayerColor(int playerIndex)
|
||||||
|
{
|
||||||
|
return playerIndex switch
|
||||||
|
{
|
||||||
|
0 => new SolidColorBrush(Color.FromRgb(255, 92, 92)), // Red for Player 1
|
||||||
|
1 => new SolidColorBrush(Color.FromRgb(54, 162, 235)), // Blue for Player 2
|
||||||
|
2 => new SolidColorBrush(Color.FromRgb(255, 206, 84)), // Yellow for Player 3
|
||||||
|
3 => new SolidColorBrush(Color.FromRgb(75, 192, 192)), // Green for Player 4
|
||||||
|
_ => new SolidColorBrush(Color.FromRgb(128, 128, 128)) // Gray fallback
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string GetControllerDisplayName(InputConfig config)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(config.Name))
|
||||||
|
{
|
||||||
|
return config.Backend switch
|
||||||
|
{
|
||||||
|
InputBackendType.WindowKeyboard => "Keyboard",
|
||||||
|
InputBackendType.GamepadSDL2 => "Controller",
|
||||||
|
_ => "Unknown"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Truncate long controller names
|
||||||
|
string name = config.Name;
|
||||||
|
if (name.Length > 25)
|
||||||
|
{
|
||||||
|
name = name.Substring(0, 22) + "...";
|
||||||
|
}
|
||||||
|
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue