mirror of
https://git.ryujinx.app/ryubing/ryujinx.git
synced 2025-06-28 08:56:24 +02:00
Overlay system
This commit is contained in:
parent
3be20385ed
commit
069a703f22
13 changed files with 1088 additions and 19 deletions
244
src/Ryujinx.Graphics.Gpu/Overlay/ControllerOverlay.cs
Normal file
244
src/Ryujinx.Graphics.Gpu/Overlay/ControllerOverlay.cs
Normal file
|
@ -0,0 +1,244 @@
|
|||
using SkiaSharp;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using OriginalInputConfig = Ryujinx.Common.Configuration.Hid.InputConfig;
|
||||
using OriginalPlayerIndex = Ryujinx.Common.Configuration.Hid.PlayerIndex;
|
||||
using OriginalInputBackendType = Ryujinx.Common.Configuration.Hid.InputBackendType;
|
||||
|
||||
namespace Ryujinx.Graphics.Gpu.Overlay
|
||||
{
|
||||
/// <summary>
|
||||
/// Controller overlay that shows controller bindings matching the original AXAML design
|
||||
/// </summary>
|
||||
public class ControllerOverlay : Overlay
|
||||
{
|
||||
private const float OverlayWidth = 400;
|
||||
private const float Padding = 24;
|
||||
private const float Spacing = 16;
|
||||
private const float PlayerSpacing = 12;
|
||||
private const float PlayerRowHeight = 32;
|
||||
|
||||
private const float TitleTextSize = 25;
|
||||
private const float PlayerTextSize = 22;
|
||||
|
||||
private float _lifespan = 0f;
|
||||
|
||||
public ControllerOverlay() : base("ControllerOverlay")
|
||||
{
|
||||
CreateBaseElements();
|
||||
}
|
||||
|
||||
private void CreateBaseElements()
|
||||
{
|
||||
// Main background container
|
||||
var background = new RectangleElement(0, 0, OverlayWidth, 200, // Dynamic height will be set later
|
||||
new SKColor(0, 0, 0, 224)) // #E0000000
|
||||
{
|
||||
Name = "Background",
|
||||
CornerRadius = 12,
|
||||
BorderColor = new SKColor(255, 255, 255, 64), // #40FFFFFF
|
||||
BorderWidth = 1
|
||||
};
|
||||
AddElement(background);
|
||||
|
||||
// Title text
|
||||
var titleText = new TextElement(Padding + 30, Padding, "Controller Bindings", TitleTextSize, SKColors.White)
|
||||
{
|
||||
Name = "TitleText",
|
||||
FontStyle = SKFontStyle.Bold
|
||||
};
|
||||
AddElement(titleText);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Show controller bindings matching the original AXAML implementation
|
||||
/// </summary>
|
||||
public void ShowControllerBindings(List<OriginalInputConfig> inputConfigs, int durationSeconds = 3)
|
||||
{
|
||||
// Reset lifespan and opacity
|
||||
_lifespan = durationSeconds;
|
||||
|
||||
// Clear existing player bindings
|
||||
ClearPlayerBindings();
|
||||
|
||||
// Debug: Log input data
|
||||
// Group controllers by player index
|
||||
var playerBindings = new Dictionary<OriginalPlayerIndex, List<OriginalInputConfig>>();
|
||||
|
||||
foreach (var config in inputConfigs.Where(c => c.PlayerIndex <= OriginalPlayerIndex.Player4))
|
||||
{
|
||||
Console.WriteLine($"ControllerOverlay: Config for Player {config.PlayerIndex}: {config.Name} ({config.Backend})");
|
||||
if (!playerBindings.ContainsKey(config.PlayerIndex))
|
||||
{
|
||||
playerBindings[config.PlayerIndex] = new List<OriginalInputConfig>();
|
||||
}
|
||||
playerBindings[config.PlayerIndex].Add(config);
|
||||
}
|
||||
|
||||
float currentY = Padding + 40; // After title
|
||||
|
||||
// Add player bindings to UI
|
||||
for (int i = 0; i < 4; i++)
|
||||
{
|
||||
var playerIndex = (OriginalPlayerIndex)i;
|
||||
float rowY = currentY + (i * (PlayerRowHeight + PlayerSpacing));
|
||||
|
||||
// Player number with colored background (circular badge)
|
||||
var playerColor = GetPlayerColor(i);
|
||||
var playerBadge = new RectangleElement(Padding, rowY, 24, 20, playerColor)
|
||||
{
|
||||
Name = $"PlayerBadge_{i}",
|
||||
CornerRadius = 12
|
||||
};
|
||||
AddElement(playerBadge);
|
||||
|
||||
// Player number text
|
||||
var playerLabel = new TextElement(Padding + 12, rowY + 2, $"P{i + 1}", PlayerTextSize, SKColors.White)
|
||||
{
|
||||
Name = $"PlayerLabel_{i}",
|
||||
FontStyle = SKFontStyle.Bold,
|
||||
TextAlign = SKTextAlign.Center
|
||||
};
|
||||
AddElement(playerLabel);
|
||||
|
||||
// Controller info
|
||||
if (playerBindings.ContainsKey(playerIndex))
|
||||
{
|
||||
var controllers = playerBindings[playerIndex];
|
||||
var controllerNames = controllers.Select(c => GetControllerDisplayName(c)).ToList();
|
||||
|
||||
var controllerText = new TextElement(Padding + 56, rowY + 2, string.Join(", ", controllerNames), PlayerTextSize, new SKColor(144, 238, 144)) // LightGreen
|
||||
{
|
||||
Name = $"ControllerText_{i}",
|
||||
FontStyle = SKFontStyle.Bold
|
||||
};
|
||||
AddElement(controllerText);
|
||||
}
|
||||
else
|
||||
{
|
||||
var noControllerText = new TextElement(Padding + 56, rowY + 2, "No controller assigned", PlayerTextSize, new SKColor(128, 128, 128)) // Gray
|
||||
{
|
||||
Name = $"NoControllerText_{i}",
|
||||
FontStyle = SKFontStyle.Italic
|
||||
};
|
||||
AddElement(noControllerText);
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate total height and update background
|
||||
float totalHeight = Padding + 40 + (4 * (PlayerRowHeight + PlayerSpacing)) + Padding + 40; // Extra space for duration text
|
||||
var background = FindElement<RectangleElement>("Background");
|
||||
if (background != null)
|
||||
{
|
||||
background.Height = totalHeight;
|
||||
}
|
||||
|
||||
// Duration text at bottom
|
||||
string durationText = durationSeconds == 1
|
||||
? "This overlay will disappear in 1 second"
|
||||
: $"This overlay will disappear in {durationSeconds} seconds";
|
||||
|
||||
// Show the overlay (position will be set by Window class with actual dimensions)
|
||||
IsVisible = true;
|
||||
}
|
||||
|
||||
private static SKColor GetPlayerColor(int playerIndex)
|
||||
{
|
||||
return playerIndex switch
|
||||
{
|
||||
0 => new SKColor(255, 92, 92), // Red for Player 1
|
||||
1 => new SKColor(54, 162, 235), // Blue for Player 2
|
||||
2 => new SKColor(255, 206, 84), // Yellow for Player 3
|
||||
3 => new SKColor(75, 192, 192), // Green for Player 4
|
||||
_ => new SKColor(128, 128, 128) // Gray fallback
|
||||
};
|
||||
}
|
||||
|
||||
private static string GetControllerDisplayName(OriginalInputConfig config)
|
||||
{
|
||||
if (string.IsNullOrEmpty(config.Name))
|
||||
{
|
||||
return config.Backend switch
|
||||
{
|
||||
OriginalInputBackendType.WindowKeyboard => "Keyboard",
|
||||
OriginalInputBackendType.GamepadSDL2 => "Controller",
|
||||
_ => "Unknown"
|
||||
};
|
||||
}
|
||||
|
||||
// Truncate long controller names
|
||||
string name = config.Name;
|
||||
if (name.Length > 25)
|
||||
{
|
||||
name = name.Substring(0, 22) + "...";
|
||||
}
|
||||
|
||||
return name;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clear all player bindings
|
||||
/// </summary>
|
||||
private void ClearPlayerBindings()
|
||||
{
|
||||
var elementsToRemove = new List<OverlayElement>();
|
||||
|
||||
foreach (var element in GetElements())
|
||||
{
|
||||
if (element.Name.StartsWith("PlayerBadge_") ||
|
||||
element.Name.StartsWith("PlayerLabel_") ||
|
||||
element.Name.StartsWith("ControllerText_") ||
|
||||
element.Name.StartsWith("NoControllerText_"))
|
||||
{
|
||||
elementsToRemove.Add(element);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var element in elementsToRemove)
|
||||
{
|
||||
RemoveElement(element);
|
||||
element.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update overlay
|
||||
/// </summary>
|
||||
public override void Update(float deltaTime, SKSize screenSize)
|
||||
{
|
||||
_lifespan -= deltaTime;
|
||||
|
||||
if (_lifespan <= 0)
|
||||
{
|
||||
IsVisible = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (_lifespan <= 0.5f)
|
||||
{
|
||||
// Fade out during the last 0.5 seconds
|
||||
Opacity = _lifespan / 0.5f;
|
||||
}
|
||||
else
|
||||
{
|
||||
Opacity = 1;
|
||||
}
|
||||
|
||||
// Update position if screen size is provided
|
||||
if (screenSize.Width > 0 && screenSize.Height > 0)
|
||||
{
|
||||
SetPositionToTopRight(screenSize.Width, screenSize.Height);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Position overlay to top-right matching original AXAML positioning
|
||||
/// </summary>
|
||||
public void SetPositionToTopRight(float screenWidth, float screenHeight)
|
||||
{
|
||||
X = screenWidth - OverlayWidth - 20; // 20px margin from right
|
||||
Y = 50; // 50px margin from top
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue