mirror of
https://git.ryujinx.app/ryubing/ryujinx.git
synced 2025-06-28 00:16:23 +02:00
Controller overlay showing which player is bound to which controller
This commit is contained in:
parent
5d136980a3
commit
75d6cc4946
3 changed files with 243 additions and 0 deletions
|
@ -32,6 +32,7 @@ using Ryujinx.Ava.UI.Windows;
|
|||
using Ryujinx.Ava.Utilities;
|
||||
using Ryujinx.Common;
|
||||
using Ryujinx.Common.Configuration;
|
||||
using Ryujinx.Common.Configuration.Hid;
|
||||
using Ryujinx.Common.Helper;
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Common.UI;
|
||||
|
@ -1686,11 +1687,37 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||
SetMainContent(RendererHostControl);
|
||||
|
||||
RendererHostControl.Focus();
|
||||
|
||||
// Show controller overlay
|
||||
ShowControllerOverlay();
|
||||
});
|
||||
|
||||
public static void UpdateGameMetadata(string titleId, TimeSpan 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()
|
||||
{
|
||||
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