mirror of
https://github.com/Nighthawk42/YodaStoriesNG.git
synced 2026-08-30 09:02:26 +00:00
Add editable keybindings, 1x scale, screenshots, and UI fixes
- Add KeyBindings system with JSON persistence to %APPDATA% - ControlsWindow now allows clicking to rebind keys (click left/right for primary/alternate) - Press DEL/Backspace to clear a binding, Reset Defaults button to restore - Add 1x graphics scale option (original tile size) alongside 2x and 4x - Fix Asset Viewer click handling - MenuBar now checks window ID - Add RenderSetLogicalSize at initialization for consistent scaling - Add screenshots to README (title, gameplay, world map, script editor, asset viewer) - Remove placeholder comments from README Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.5
parent
b76b2cbb17
commit
94258da064
@@ -10,7 +10,6 @@ A modern reimplementation of **Star Wars: Yoda Stories** (1997) built with C# an
|
||||
|
||||
Yoda Stories NG is a fan-made recreation of the classic LucasArts desktop adventure game. It parses the original game data files and reimplements the game engine from scratch.
|
||||
|
||||
<!-- SCREENSHOT: Title Screen -->
|
||||

|
||||
|
||||
## Key Features
|
||||
@@ -22,25 +21,21 @@ Yoda Stories NG is a fan-made recreation of the classic LucasArts desktop advent
|
||||
- **Save/Load system** - JSON-based save files with full game state
|
||||
- **Widescreen UI** - Modern layout with sidebar HUD
|
||||
- **Xbox controller support** - Full gamepad controls
|
||||
- **Configurable graphics** - 2x and 4x scaling options
|
||||
- **Configurable graphics** - 1x, 2x, and 4x scaling options
|
||||
- **Automated Mission Bot** - AI-powered gameplay with A* pathfinding
|
||||
|
||||
## Screenshots
|
||||
|
||||
### Gameplay
|
||||
<!-- SCREENSHOT: Gameplay showing player in a zone with NPCs -->
|
||||

|
||||
|
||||
### World Map Viewer
|
||||
<!-- SCREENSHOT: Debug Map Window showing 10x10 or 15x15 grid -->
|
||||

|
||||
|
||||
### Script Editor
|
||||
<!-- SCREENSHOT: Script Editor window showing IACT scripts -->
|
||||

|
||||
|
||||
### Asset Viewer
|
||||
<!-- SCREENSHOT: Asset Viewer showing tiles/characters -->
|
||||

|
||||
|
||||
## Requirements
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 319 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 123 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 44 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 58 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 31 KiB |
@@ -113,7 +113,7 @@ public unsafe class GameEngine : IDisposable
|
||||
_titleScreen.OnStartGame += () => { _showingTitleScreen = false; StartNewGame(); };
|
||||
|
||||
_menuBar = new MenuBar(_renderer.GetFont());
|
||||
_menuBar.SetRenderer(_renderer.GetRenderer());
|
||||
_menuBar.SetRenderer(_renderer.GetRenderer(), _renderer.GetWindowID());
|
||||
_menuBar.OnNewGame += (size) => { _selectedWorldSize = size; StartNewGame(); };
|
||||
_menuBar.OnSaveGame += SaveGame;
|
||||
_menuBar.OnSaveGameAs += SaveGameAs;
|
||||
|
||||
@@ -0,0 +1,296 @@
|
||||
using System.Text.Json;
|
||||
using Hexa.NET.SDL2;
|
||||
|
||||
namespace YodaStoriesNG.Engine.Game;
|
||||
|
||||
/// <summary>
|
||||
/// Manages customizable keyboard bindings for game actions.
|
||||
/// </summary>
|
||||
public class KeyBindings
|
||||
{
|
||||
private static readonly string ConfigPath = Path.Combine(
|
||||
Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
|
||||
"YodaStoriesNG", "keybindings.json");
|
||||
|
||||
// Action names (used as keys in the dictionary)
|
||||
public const string MoveUp = "MoveUp";
|
||||
public const string MoveDown = "MoveDown";
|
||||
public const string MoveLeft = "MoveLeft";
|
||||
public const string MoveRight = "MoveRight";
|
||||
public const string Action = "Action";
|
||||
public const string ToggleWeapon = "ToggleWeapon";
|
||||
public const string Travel = "Travel";
|
||||
public const string Objective = "Objective";
|
||||
public const string Restart = "Restart";
|
||||
public const string ToggleSound = "ToggleSound";
|
||||
public const string Quit = "Quit";
|
||||
public const string DebugOverlay = "DebugOverlay";
|
||||
public const string MapViewer = "MapViewer";
|
||||
public const string ScriptEditor = "ScriptEditor";
|
||||
public const string AssetViewer = "AssetViewer";
|
||||
public const string NextZone = "NextZone";
|
||||
public const string PrevZone = "PrevZone";
|
||||
public const string FindZone = "FindZone";
|
||||
public const string Inspect = "Inspect";
|
||||
public const string ToggleBot = "ToggleBot";
|
||||
public const string Inventory1 = "Inventory1";
|
||||
public const string Inventory2 = "Inventory2";
|
||||
public const string Inventory3 = "Inventory3";
|
||||
public const string Inventory4 = "Inventory4";
|
||||
public const string Inventory5 = "Inventory5";
|
||||
public const string Inventory6 = "Inventory6";
|
||||
public const string Inventory7 = "Inventory7";
|
||||
public const string Inventory8 = "Inventory8";
|
||||
|
||||
// Primary and alternate key bindings
|
||||
public Dictionary<string, int> Primary { get; set; } = new();
|
||||
public Dictionary<string, int> Alternate { get; set; } = new();
|
||||
|
||||
// Display names for the UI
|
||||
public static readonly Dictionary<string, string> DisplayNames = new()
|
||||
{
|
||||
{ MoveUp, "Move Up" },
|
||||
{ MoveDown, "Move Down" },
|
||||
{ MoveLeft, "Move Left" },
|
||||
{ MoveRight, "Move Right" },
|
||||
{ Action, "Action / Attack / Talk" },
|
||||
{ ToggleWeapon, "Toggle Weapon" },
|
||||
{ Travel, "Travel (X-Wing)" },
|
||||
{ Objective, "Show Objective" },
|
||||
{ Restart, "Restart Game" },
|
||||
{ ToggleSound, "Toggle Sound" },
|
||||
{ Quit, "Quit" },
|
||||
{ DebugOverlay, "Debug Overlay" },
|
||||
{ MapViewer, "Map Viewer" },
|
||||
{ ScriptEditor, "Script Editor" },
|
||||
{ AssetViewer, "Asset Viewer" },
|
||||
{ NextZone, "Next Zone" },
|
||||
{ PrevZone, "Previous Zone" },
|
||||
{ FindZone, "Find Zone" },
|
||||
{ Inspect, "Inspect" },
|
||||
{ ToggleBot, "Toggle Bot" },
|
||||
{ Inventory1, "Inventory Slot 1" },
|
||||
{ Inventory2, "Inventory Slot 2" },
|
||||
{ Inventory3, "Inventory Slot 3" },
|
||||
{ Inventory4, "Inventory Slot 4" },
|
||||
{ Inventory5, "Inventory Slot 5" },
|
||||
{ Inventory6, "Inventory Slot 6" },
|
||||
{ Inventory7, "Inventory Slot 7" },
|
||||
{ Inventory8, "Inventory Slot 8" },
|
||||
};
|
||||
|
||||
// Categories for grouping in UI
|
||||
public static readonly (string category, string[] actions)[] Categories = new[]
|
||||
{
|
||||
("Movement", new[] { MoveUp, MoveDown, MoveLeft, MoveRight }),
|
||||
("Actions", new[] { Action, ToggleWeapon, Travel, Objective }),
|
||||
("Inventory", new[] { Inventory1, Inventory2, Inventory3, Inventory4, Inventory5, Inventory6, Inventory7, Inventory8 }),
|
||||
("Game", new[] { Restart, ToggleSound, Quit }),
|
||||
("Debug", new[] { DebugOverlay, MapViewer, ScriptEditor, AssetViewer, NextZone, PrevZone, FindZone, Inspect, ToggleBot }),
|
||||
};
|
||||
|
||||
public KeyBindings()
|
||||
{
|
||||
SetDefaults();
|
||||
}
|
||||
|
||||
public void SetDefaults()
|
||||
{
|
||||
// Primary bindings (arrow keys / standard)
|
||||
Primary = new Dictionary<string, int>
|
||||
{
|
||||
{ MoveUp, (int)SDLKeyCode.Up },
|
||||
{ MoveDown, (int)SDLKeyCode.Down },
|
||||
{ MoveLeft, (int)SDLKeyCode.Left },
|
||||
{ MoveRight, (int)SDLKeyCode.Right },
|
||||
{ Action, (int)SDLKeyCode.Space },
|
||||
{ ToggleWeapon, (int)SDLKeyCode.Tab },
|
||||
{ Travel, (int)SDLKeyCode.X },
|
||||
{ Objective, (int)SDLKeyCode.O },
|
||||
{ Restart, (int)SDLKeyCode.R },
|
||||
{ ToggleSound, (int)SDLKeyCode.M },
|
||||
{ Quit, (int)SDLKeyCode.Escape },
|
||||
{ DebugOverlay, (int)SDLKeyCode.F1 },
|
||||
{ MapViewer, (int)SDLKeyCode.F2 },
|
||||
{ ScriptEditor, (int)SDLKeyCode.F3 },
|
||||
{ AssetViewer, (int)SDLKeyCode.F4 },
|
||||
{ NextZone, (int)SDLKeyCode.N },
|
||||
{ PrevZone, (int)SDLKeyCode.P },
|
||||
{ FindZone, (int)SDLKeyCode.F },
|
||||
{ Inspect, (int)SDLKeyCode.I },
|
||||
{ ToggleBot, (int)SDLKeyCode.B },
|
||||
{ Inventory1, (int)SDLKeyCode.K1 },
|
||||
{ Inventory2, (int)SDLKeyCode.K2 },
|
||||
{ Inventory3, (int)SDLKeyCode.K3 },
|
||||
{ Inventory4, (int)SDLKeyCode.K4 },
|
||||
{ Inventory5, (int)SDLKeyCode.K5 },
|
||||
{ Inventory6, (int)SDLKeyCode.K6 },
|
||||
{ Inventory7, (int)SDLKeyCode.K7 },
|
||||
{ Inventory8, (int)SDLKeyCode.K8 },
|
||||
};
|
||||
|
||||
// Alternate bindings (WASD)
|
||||
Alternate = new Dictionary<string, int>
|
||||
{
|
||||
{ MoveUp, (int)SDLKeyCode.W },
|
||||
{ MoveDown, (int)SDLKeyCode.S },
|
||||
{ MoveLeft, (int)SDLKeyCode.A },
|
||||
{ MoveRight, (int)SDLKeyCode.D },
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if a key matches the binding for an action.
|
||||
/// </summary>
|
||||
public bool IsPressed(string action, int keyCode)
|
||||
{
|
||||
if (Primary.TryGetValue(action, out var primary) && primary == keyCode)
|
||||
return true;
|
||||
if (Alternate.TryGetValue(action, out var alternate) && alternate == keyCode)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the primary binding for an action.
|
||||
/// </summary>
|
||||
public void SetPrimary(string action, int keyCode)
|
||||
{
|
||||
Primary[action] = keyCode;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the alternate binding for an action.
|
||||
/// </summary>
|
||||
public void SetAlternate(string action, int keyCode)
|
||||
{
|
||||
Alternate[action] = keyCode;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears the alternate binding for an action.
|
||||
/// </summary>
|
||||
public void ClearAlternate(string action)
|
||||
{
|
||||
Alternate.Remove(action);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a display string for the current binding.
|
||||
/// </summary>
|
||||
public string GetBindingDisplay(string action)
|
||||
{
|
||||
var parts = new List<string>();
|
||||
|
||||
if (Primary.TryGetValue(action, out var primary))
|
||||
parts.Add(GetKeyName(primary));
|
||||
|
||||
if (Alternate.TryGetValue(action, out var alternate))
|
||||
parts.Add(GetKeyName(alternate));
|
||||
|
||||
return parts.Count > 0 ? string.Join(" / ", parts) : "(unbound)";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the display name for an SDL key code.
|
||||
/// </summary>
|
||||
public static string GetKeyName(int keyCode)
|
||||
{
|
||||
return keyCode switch
|
||||
{
|
||||
(int)SDLKeyCode.Up => "Up",
|
||||
(int)SDLKeyCode.Down => "Down",
|
||||
(int)SDLKeyCode.Left => "Left",
|
||||
(int)SDLKeyCode.Right => "Right",
|
||||
(int)SDLKeyCode.Space => "Space",
|
||||
(int)SDLKeyCode.Tab => "Tab",
|
||||
(int)SDLKeyCode.Escape => "Escape",
|
||||
(int)SDLKeyCode.Return => "Enter",
|
||||
(int)SDLKeyCode.Backspace => "Backspace",
|
||||
(int)SDLKeyCode.Delete => "Delete",
|
||||
(int)SDLKeyCode.Insert => "Insert",
|
||||
(int)SDLKeyCode.Home => "Home",
|
||||
(int)SDLKeyCode.End => "End",
|
||||
(int)SDLKeyCode.Pageup => "PageUp",
|
||||
(int)SDLKeyCode.Pagedown => "PageDown",
|
||||
(int)SDLKeyCode.F1 => "F1",
|
||||
(int)SDLKeyCode.F2 => "F2",
|
||||
(int)SDLKeyCode.F3 => "F3",
|
||||
(int)SDLKeyCode.F4 => "F4",
|
||||
(int)SDLKeyCode.F5 => "F5",
|
||||
(int)SDLKeyCode.F6 => "F6",
|
||||
(int)SDLKeyCode.F7 => "F7",
|
||||
(int)SDLKeyCode.F8 => "F8",
|
||||
(int)SDLKeyCode.F9 => "F9",
|
||||
(int)SDLKeyCode.F10 => "F10",
|
||||
(int)SDLKeyCode.F11 => "F11",
|
||||
(int)SDLKeyCode.F12 => "F12",
|
||||
(int)SDLKeyCode.K0 => "0",
|
||||
(int)SDLKeyCode.K1 => "1",
|
||||
(int)SDLKeyCode.K2 => "2",
|
||||
(int)SDLKeyCode.K3 => "3",
|
||||
(int)SDLKeyCode.K4 => "4",
|
||||
(int)SDLKeyCode.K5 => "5",
|
||||
(int)SDLKeyCode.K6 => "6",
|
||||
(int)SDLKeyCode.K7 => "7",
|
||||
(int)SDLKeyCode.K8 => "8",
|
||||
(int)SDLKeyCode.K9 => "9",
|
||||
(int)SDLKeyCode.Lshift => "LShift",
|
||||
(int)SDLKeyCode.Rshift => "RShift",
|
||||
(int)SDLKeyCode.Lctrl => "LCtrl",
|
||||
(int)SDLKeyCode.Rctrl => "RCtrl",
|
||||
(int)SDLKeyCode.Lalt => "LAlt",
|
||||
(int)SDLKeyCode.Ralt => "RAlt",
|
||||
_ => ((char)keyCode >= 'a' && (char)keyCode <= 'z') ? ((char)keyCode).ToString().ToUpper() : $"Key{keyCode}"
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Saves bindings to the config file.
|
||||
/// </summary>
|
||||
public void Save()
|
||||
{
|
||||
try
|
||||
{
|
||||
var dir = Path.GetDirectoryName(ConfigPath);
|
||||
if (!string.IsNullOrEmpty(dir) && !Directory.Exists(dir))
|
||||
Directory.CreateDirectory(dir);
|
||||
|
||||
var options = new JsonSerializerOptions { WriteIndented = true };
|
||||
var json = JsonSerializer.Serialize(this, options);
|
||||
File.WriteAllText(ConfigPath, json);
|
||||
Console.WriteLine($"[KeyBindings] Saved to {ConfigPath}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"[KeyBindings] Failed to save: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Loads bindings from the config file.
|
||||
/// </summary>
|
||||
public static KeyBindings Load()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (File.Exists(ConfigPath))
|
||||
{
|
||||
var json = File.ReadAllText(ConfigPath);
|
||||
var bindings = JsonSerializer.Deserialize<KeyBindings>(json);
|
||||
if (bindings != null)
|
||||
{
|
||||
Console.WriteLine($"[KeyBindings] Loaded from {ConfigPath}");
|
||||
return bindings;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"[KeyBindings] Failed to load: {ex.Message}");
|
||||
}
|
||||
|
||||
Console.WriteLine("[KeyBindings] Using defaults");
|
||||
return new KeyBindings();
|
||||
}
|
||||
}
|
||||
@@ -44,6 +44,7 @@ public unsafe class GameRenderer : IDisposable
|
||||
|
||||
public BitmapFont GetFont() => _font;
|
||||
public SDLRenderer* GetRenderer() => _renderer;
|
||||
public uint GetWindowID() => _window != null ? SDL.GetWindowID(_window) : 0;
|
||||
|
||||
/// <summary>
|
||||
/// Sets the window scale (2x or 4x).
|
||||
@@ -108,6 +109,9 @@ public unsafe class GameRenderer : IDisposable
|
||||
Console.WriteLine("Warning: Failed to initialize bitmap font");
|
||||
}
|
||||
|
||||
// Set initial logical size for consistent scaling behavior
|
||||
SDL.RenderSetLogicalSize(_renderer, WindowWidth, WindowHeight);
|
||||
|
||||
// Create tile atlas texture
|
||||
CreateTileAtlas();
|
||||
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
using Hexa.NET.SDL2;
|
||||
using YodaStoriesNG.Engine.Game;
|
||||
using YodaStoriesNG.Engine.Rendering;
|
||||
|
||||
namespace YodaStoriesNG.Engine.UI;
|
||||
|
||||
/// <summary>
|
||||
/// Window that displays keyboard and controller control mappings.
|
||||
/// Window that displays and allows editing keyboard and controller control mappings.
|
||||
/// </summary>
|
||||
public unsafe class ControlsWindow : IDisposable
|
||||
{
|
||||
@@ -15,49 +16,22 @@ public unsafe class ControlsWindow : IDisposable
|
||||
private uint _windowId;
|
||||
private int _scrollOffset = 0;
|
||||
|
||||
private const int WindowWidth = 600;
|
||||
private const int WindowHeight = 500;
|
||||
private const int WindowWidth = 650;
|
||||
private const int WindowHeight = 550;
|
||||
private const int LineHeight = 24;
|
||||
|
||||
// Which control set to show
|
||||
private bool _showController = false;
|
||||
|
||||
// Keyboard binding editing
|
||||
private KeyBindings _keyBindings;
|
||||
private string? _editingAction = null;
|
||||
private bool _editingAlternate = false;
|
||||
private int _hoveredRow = -1;
|
||||
|
||||
public bool IsOpen => _isOpen;
|
||||
|
||||
// Keyboard controls
|
||||
private static readonly (string action, string key)[] KeyboardControls = new[]
|
||||
{
|
||||
("Movement", ""),
|
||||
("Move Up", "Arrow Up / W"),
|
||||
("Move Down", "Arrow Down / S"),
|
||||
("Move Left", "Arrow Left / A"),
|
||||
("Move Right", "Arrow Right / D"),
|
||||
("Pull Block", "Shift + Direction"),
|
||||
("", ""),
|
||||
("Actions", ""),
|
||||
("Use Item / Attack / Talk", "Space"),
|
||||
("Toggle Weapon", "Tab"),
|
||||
("Select Inventory 1-8", "1, 2, 3, 4, 5, 6, 7, 8"),
|
||||
("Travel (X-Wing)", "X"),
|
||||
("Show Objective", "O"),
|
||||
("", ""),
|
||||
("Game", ""),
|
||||
("New Game / Restart", "R"),
|
||||
("Toggle Sound", "M"),
|
||||
("Quit", "Escape"),
|
||||
("", ""),
|
||||
("Debug", ""),
|
||||
("Toggle Debug Overlay", "F1"),
|
||||
("Toggle Map Viewer", "F2"),
|
||||
("Toggle Script Editor", "F3"),
|
||||
("Toggle Asset Viewer", "F4"),
|
||||
("Next Zone", "N"),
|
||||
("Previous Zone", "P"),
|
||||
("Find Zone with Content", "F"),
|
||||
("Inspect (Console)", "I"),
|
||||
("Toggle Bot", "B"),
|
||||
};
|
||||
|
||||
// Controller controls
|
||||
// Controller controls (read-only display)
|
||||
private static readonly (string action, string button)[] ControllerControls = new[]
|
||||
{
|
||||
("Movement", ""),
|
||||
@@ -79,6 +53,13 @@ public unsafe class ControlsWindow : IDisposable
|
||||
("Movement Rate", "Varies with stick"),
|
||||
};
|
||||
|
||||
public ControlsWindow()
|
||||
{
|
||||
_keyBindings = KeyBindings.Load();
|
||||
}
|
||||
|
||||
public KeyBindings GetKeyBindings() => _keyBindings;
|
||||
|
||||
public void Open(bool showController = false)
|
||||
{
|
||||
if (_isOpen)
|
||||
@@ -88,8 +69,10 @@ public unsafe class ControlsWindow : IDisposable
|
||||
|
||||
_showController = showController;
|
||||
_scrollOffset = 0;
|
||||
_editingAction = null;
|
||||
_hoveredRow = -1;
|
||||
|
||||
string title = showController ? "Controller Controls" : "Keyboard Controls";
|
||||
string title = showController ? "Controller Controls" : "Keyboard Controls (Click to Edit)";
|
||||
|
||||
_window = SDL.CreateWindow(
|
||||
title,
|
||||
@@ -162,9 +145,79 @@ public unsafe class ControlsWindow : IDisposable
|
||||
return true;
|
||||
}
|
||||
|
||||
if (evt->Type == (uint)SDLEventType.Mousemotion && evt->Motion.WindowID == _windowId)
|
||||
{
|
||||
if (!_showController && _editingAction == null)
|
||||
{
|
||||
int my = evt->Motion.Y;
|
||||
_hoveredRow = GetRowAtY(my);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (evt->Type == (uint)SDLEventType.Mousebuttondown && evt->Button.WindowID == _windowId)
|
||||
{
|
||||
if (!_showController)
|
||||
{
|
||||
int mx = evt->Button.X;
|
||||
int my = evt->Button.Y;
|
||||
|
||||
// Check for reset button click
|
||||
if (my >= WindowHeight - 35 && mx >= WindowWidth - 120 && mx < WindowWidth - 20)
|
||||
{
|
||||
_keyBindings.SetDefaults();
|
||||
_keyBindings.Save();
|
||||
return true;
|
||||
}
|
||||
|
||||
var clickedAction = GetActionAtY(my);
|
||||
if (clickedAction != null && _editingAction == null)
|
||||
{
|
||||
_editingAction = clickedAction;
|
||||
// Right half = alternate binding
|
||||
_editingAlternate = mx > WindowWidth / 2 + 50;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
if (evt->Type == (uint)SDLEventType.Keydown && evt->Key.WindowID == _windowId)
|
||||
{
|
||||
if (evt->Key.Keysym.Sym == 27) // Escape
|
||||
var keyCode = (int)evt->Key.Keysym.Sym;
|
||||
|
||||
// If editing a binding, capture the key
|
||||
if (_editingAction != null && !_showController)
|
||||
{
|
||||
if (keyCode == (int)SDLKeyCode.Escape)
|
||||
{
|
||||
// Cancel editing
|
||||
_editingAction = null;
|
||||
}
|
||||
else if (keyCode == (int)SDLKeyCode.Delete || keyCode == (int)SDLKeyCode.Backspace)
|
||||
{
|
||||
// Clear the binding
|
||||
if (_editingAlternate)
|
||||
_keyBindings.ClearAlternate(_editingAction);
|
||||
else
|
||||
_keyBindings.SetPrimary(_editingAction, 0);
|
||||
_keyBindings.Save();
|
||||
_editingAction = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Set the new binding
|
||||
if (_editingAlternate)
|
||||
_keyBindings.SetAlternate(_editingAction, keyCode);
|
||||
else
|
||||
_keyBindings.SetPrimary(_editingAction, keyCode);
|
||||
_keyBindings.Save();
|
||||
_editingAction = null;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
if (keyCode == (int)SDLKeyCode.Escape)
|
||||
{
|
||||
Close();
|
||||
return true;
|
||||
@@ -174,6 +227,48 @@ public unsafe class ControlsWindow : IDisposable
|
||||
return false;
|
||||
}
|
||||
|
||||
private int GetRowAtY(int y)
|
||||
{
|
||||
int contentY = 50 - _scrollOffset * LineHeight;
|
||||
int row = 0;
|
||||
|
||||
foreach (var (category, actions) in KeyBindings.Categories)
|
||||
{
|
||||
if (y >= contentY && y < contentY + LineHeight)
|
||||
return -1; // Category header
|
||||
contentY += LineHeight;
|
||||
|
||||
foreach (var action in actions)
|
||||
{
|
||||
if (y >= contentY && y < contentY + LineHeight)
|
||||
return row;
|
||||
contentY += LineHeight;
|
||||
row++;
|
||||
}
|
||||
contentY += 10; // Gap between categories
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
private string? GetActionAtY(int y)
|
||||
{
|
||||
int contentY = 50 - _scrollOffset * LineHeight;
|
||||
|
||||
foreach (var (category, actions) in KeyBindings.Categories)
|
||||
{
|
||||
contentY += LineHeight; // Skip category header
|
||||
|
||||
foreach (var action in actions)
|
||||
{
|
||||
if (y >= contentY && y < contentY + LineHeight)
|
||||
return action;
|
||||
contentY += LineHeight;
|
||||
}
|
||||
contentY += 10; // Gap between categories
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public void Render()
|
||||
{
|
||||
if (!_isOpen || _renderer == null || _font == null) return;
|
||||
@@ -184,24 +279,133 @@ public unsafe class ControlsWindow : IDisposable
|
||||
|
||||
// Header
|
||||
SDL.SetRenderDrawColor(_renderer, 45, 48, 58, 255);
|
||||
var headerRect = new SDLRect { X = 0, Y = 0, W = WindowWidth, H = 40 };
|
||||
var headerRect = new SDLRect { X = 0, Y = 0, W = WindowWidth, H = 45 };
|
||||
SDL.RenderFillRect(_renderer, &headerRect);
|
||||
|
||||
string title = _showController ? "CONTROLLER CONTROLS (Xbox)" : "KEYBOARD CONTROLS";
|
||||
int titleWidth = _font.GetTextWidth(title);
|
||||
_font.RenderText(_renderer, title, WindowWidth / 2 - titleWidth / 2, 12, 1, 255, 255, 100, 255);
|
||||
_font.RenderText(_renderer, title, WindowWidth / 2 - titleWidth / 2, 10, 1, 255, 255, 100, 255);
|
||||
|
||||
// Controls list
|
||||
int y = 50 - _scrollOffset * 20;
|
||||
if (!_showController)
|
||||
{
|
||||
_font.RenderText(_renderer, "Click a binding to change it. Press DEL to clear.", 20, 28, 1, 120, 120, 150, 255);
|
||||
}
|
||||
|
||||
if (_showController)
|
||||
{
|
||||
RenderControllerControls();
|
||||
}
|
||||
else
|
||||
{
|
||||
RenderKeyboardControls();
|
||||
}
|
||||
|
||||
// Footer
|
||||
SDL.SetRenderDrawColor(_renderer, 40, 42, 50, 255);
|
||||
var footerRect = new SDLRect { X = 0, Y = WindowHeight - 35, W = WindowWidth, H = 35 };
|
||||
SDL.RenderFillRect(_renderer, &footerRect);
|
||||
|
||||
_font.RenderText(_renderer, "Press ESC to close", 20, WindowHeight - 22, 1, 120, 120, 140, 255);
|
||||
|
||||
if (!_showController)
|
||||
{
|
||||
// Reset button
|
||||
SDL.SetRenderDrawColor(_renderer, 80, 60, 60, 255);
|
||||
var resetRect = new SDLRect { X = WindowWidth - 120, Y = WindowHeight - 30, W = 100, H = 25 };
|
||||
SDL.RenderFillRect(_renderer, &resetRect);
|
||||
_font.RenderText(_renderer, "Reset Defaults", WindowWidth - 115, WindowHeight - 22, 1, 200, 150, 150, 255);
|
||||
}
|
||||
|
||||
SDL.RenderPresent(_renderer);
|
||||
}
|
||||
|
||||
private void RenderKeyboardControls()
|
||||
{
|
||||
int y = 50 - _scrollOffset * LineHeight;
|
||||
int leftCol = 30;
|
||||
int primaryCol = 280;
|
||||
int altCol = 450;
|
||||
int row = 0;
|
||||
|
||||
// Column headers
|
||||
if (y > 40)
|
||||
{
|
||||
_font!.RenderText(_renderer, "Action", leftCol, y, 1, 150, 150, 180, 255);
|
||||
_font!.RenderText(_renderer, "Primary", primaryCol, y, 1, 150, 150, 180, 255);
|
||||
_font!.RenderText(_renderer, "Alternate", altCol, y, 1, 150, 150, 180, 255);
|
||||
}
|
||||
y += LineHeight + 5;
|
||||
|
||||
foreach (var (category, actions) in KeyBindings.Categories)
|
||||
{
|
||||
if (y > 40 && y < WindowHeight - 40)
|
||||
{
|
||||
// Category header
|
||||
SDL.SetRenderDrawColor(_renderer, 50, 55, 70, 255);
|
||||
var sectionRect = new SDLRect { X = 20, Y = y - 2, W = WindowWidth - 40, H = LineHeight };
|
||||
SDL.RenderFillRect(_renderer, §ionRect);
|
||||
_font!.RenderText(_renderer, category, leftCol, y, 1, 100, 200, 255, 255);
|
||||
}
|
||||
y += LineHeight;
|
||||
|
||||
foreach (var action in actions)
|
||||
{
|
||||
if (y > 40 && y < WindowHeight - 40)
|
||||
{
|
||||
bool isEditing = _editingAction == action;
|
||||
bool isHovered = _hoveredRow == row && _editingAction == null;
|
||||
|
||||
// Highlight row
|
||||
if (isEditing || isHovered)
|
||||
{
|
||||
SDL.SetRenderDrawColor(_renderer, isEditing ? (byte)70 : (byte)45,
|
||||
isEditing ? (byte)70 : (byte)48, isEditing ? (byte)90 : (byte)58, 255);
|
||||
var rowRect = new SDLRect { X = 20, Y = y - 2, W = WindowWidth - 40, H = LineHeight };
|
||||
SDL.RenderFillRect(_renderer, &rowRect);
|
||||
}
|
||||
|
||||
// Action name
|
||||
string displayName = KeyBindings.DisplayNames.TryGetValue(action, out var dn) ? dn : action;
|
||||
_font!.RenderText(_renderer, displayName, leftCol, y, 1, 200, 200, 200, 255);
|
||||
|
||||
// Primary binding
|
||||
if (isEditing && !_editingAlternate)
|
||||
{
|
||||
_font!.RenderText(_renderer, "Press a key...", primaryCol, y, 1, 255, 255, 100, 255);
|
||||
}
|
||||
else
|
||||
{
|
||||
var primaryKey = _keyBindings.Primary.TryGetValue(action, out var pk) ? KeyBindings.GetKeyName(pk) : "-";
|
||||
_font!.RenderText(_renderer, primaryKey, primaryCol, y, 1, 150, 255, 150, 255);
|
||||
}
|
||||
|
||||
// Alternate binding
|
||||
if (isEditing && _editingAlternate)
|
||||
{
|
||||
_font!.RenderText(_renderer, "Press a key...", altCol, y, 1, 255, 255, 100, 255);
|
||||
}
|
||||
else
|
||||
{
|
||||
var altKey = _keyBindings.Alternate.TryGetValue(action, out var ak) ? KeyBindings.GetKeyName(ak) : "-";
|
||||
_font!.RenderText(_renderer, altKey, altCol, y, 1, 150, 200, 255, 255);
|
||||
}
|
||||
}
|
||||
y += LineHeight;
|
||||
row++;
|
||||
}
|
||||
y += 10; // Gap between categories
|
||||
}
|
||||
}
|
||||
|
||||
private void RenderControllerControls()
|
||||
{
|
||||
int y = 55 - _scrollOffset * LineHeight;
|
||||
int leftCol = 30;
|
||||
int rightCol = 300;
|
||||
int lineHeight = 22;
|
||||
|
||||
var controls = _showController ? ControllerControls : KeyboardControls;
|
||||
|
||||
foreach (var (action, binding) in controls)
|
||||
foreach (var (action, binding) in ControllerControls)
|
||||
{
|
||||
if (y > 40 && y < WindowHeight - 30)
|
||||
if (y > 40 && y < WindowHeight - 40)
|
||||
{
|
||||
if (string.IsNullOrEmpty(action) && string.IsNullOrEmpty(binding))
|
||||
{
|
||||
@@ -211,29 +415,20 @@ public unsafe class ControlsWindow : IDisposable
|
||||
{
|
||||
// Section header
|
||||
SDL.SetRenderDrawColor(_renderer, 50, 55, 70, 255);
|
||||
var sectionRect = new SDLRect { X = 20, Y = y - 2, W = WindowWidth - 40, H = lineHeight };
|
||||
var sectionRect = new SDLRect { X = 20, Y = y - 2, W = WindowWidth - 40, H = LineHeight };
|
||||
SDL.RenderFillRect(_renderer, §ionRect);
|
||||
|
||||
_font.RenderText(_renderer, action, leftCol, y, 1, 100, 200, 255, 255);
|
||||
_font!.RenderText(_renderer, action, leftCol, y, 1, 100, 200, 255, 255);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Control binding
|
||||
_font.RenderText(_renderer, action, leftCol, y, 1, 200, 200, 200, 255);
|
||||
_font.RenderText(_renderer, binding, rightCol, y, 1, 150, 255, 150, 255);
|
||||
_font!.RenderText(_renderer, action, leftCol, y, 1, 200, 200, 200, 255);
|
||||
_font!.RenderText(_renderer, binding, rightCol, y, 1, 150, 255, 150, 255);
|
||||
}
|
||||
}
|
||||
y += lineHeight;
|
||||
y += LineHeight;
|
||||
}
|
||||
|
||||
// Footer
|
||||
SDL.SetRenderDrawColor(_renderer, 40, 42, 50, 255);
|
||||
var footerRect = new SDLRect { X = 0, Y = WindowHeight - 35, W = WindowWidth, H = 35 };
|
||||
SDL.RenderFillRect(_renderer, &footerRect);
|
||||
|
||||
_font.RenderText(_renderer, "Press ESC to close", WindowWidth / 2 - 70, WindowHeight - 22, 1, 120, 120, 140, 255);
|
||||
|
||||
SDL.RenderPresent(_renderer);
|
||||
}
|
||||
|
||||
public void Dispose() => Close();
|
||||
|
||||
@@ -0,0 +1,254 @@
|
||||
using YodaStoriesNG.Engine.Data;
|
||||
using YodaStoriesNG.Engine.Game;
|
||||
|
||||
namespace YodaStoriesNG.Engine.UI;
|
||||
|
||||
/// <summary>
|
||||
/// In-game debug overlay that renders on screen.
|
||||
/// Press F1 to toggle, arrow keys to navigate, Enter to select.
|
||||
/// </summary>
|
||||
public class DebugOverlay
|
||||
{
|
||||
private readonly GameData _gameData;
|
||||
private readonly GameState _state;
|
||||
private readonly WorldGenerator? _worldGenerator;
|
||||
|
||||
public bool IsVisible { get; set; } = false;
|
||||
public int CurrentTab { get; private set; } = 0;
|
||||
public int ScrollOffset { get; private set; } = 0;
|
||||
|
||||
private readonly string[] _tabs = { "State", "Zone", "Scripts", "Inventory", "Map" };
|
||||
|
||||
public DebugOverlay(GameData gameData, GameState state, WorldGenerator? worldGenerator = null)
|
||||
{
|
||||
_gameData = gameData;
|
||||
_state = state;
|
||||
_worldGenerator = worldGenerator;
|
||||
}
|
||||
|
||||
public void Toggle() => IsVisible = !IsVisible;
|
||||
|
||||
public void NextTab()
|
||||
{
|
||||
CurrentTab = (CurrentTab + 1) % _tabs.Length;
|
||||
ScrollOffset = 0;
|
||||
}
|
||||
|
||||
public void PrevTab()
|
||||
{
|
||||
CurrentTab = (CurrentTab - 1 + _tabs.Length) % _tabs.Length;
|
||||
ScrollOffset = 0;
|
||||
}
|
||||
|
||||
public void ScrollUp() => ScrollOffset = Math.Max(0, ScrollOffset - 1);
|
||||
public void ScrollDown() => ScrollOffset++;
|
||||
|
||||
public string[] GetTabs() => _tabs;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the content lines for a specific tab.
|
||||
/// </summary>
|
||||
public List<string> GetTabContent(int tab)
|
||||
{
|
||||
return tab switch
|
||||
{
|
||||
0 => GetStateContent(),
|
||||
1 => GetZoneContent(),
|
||||
2 => GetScriptsContent(),
|
||||
3 => GetInventoryContent(),
|
||||
4 => GetMapContent(),
|
||||
_ => new List<string> { "Unknown tab" }
|
||||
};
|
||||
}
|
||||
|
||||
private List<string> GetStateContent()
|
||||
{
|
||||
var lines = new List<string>
|
||||
{
|
||||
"=== GAME STATE ===",
|
||||
"",
|
||||
$"Zone ID: {_state.CurrentZoneId}",
|
||||
$"Position: ({_state.PlayerX}, {_state.PlayerY})",
|
||||
$"Direction: {_state.PlayerDirection}",
|
||||
$"Health: {_state.Health} / {_state.MaxHealth}",
|
||||
"",
|
||||
$"Games Won: {_state.GamesWon}",
|
||||
$"Game Over: {_state.IsGameOver}",
|
||||
$"Game Won: {_state.IsGameWon}",
|
||||
"",
|
||||
"=== MISSION ==="
|
||||
};
|
||||
|
||||
var mission = _worldGenerator?.CurrentWorld?.Mission;
|
||||
if (mission != null)
|
||||
{
|
||||
lines.Add($"Mission {mission.MissionNumber}/15: {mission.Name}");
|
||||
lines.Add($"Planet: {mission.Planet}");
|
||||
lines.Add($"Step: {mission.CurrentStep + 1}/{mission.PuzzleChain.Count}");
|
||||
lines.Add($"Completed: {mission.IsCompleted}");
|
||||
}
|
||||
else
|
||||
{
|
||||
lines.Add("No mission active");
|
||||
}
|
||||
|
||||
return lines;
|
||||
}
|
||||
|
||||
private List<string> GetZoneContent()
|
||||
{
|
||||
var lines = new List<string>();
|
||||
var zone = _state.CurrentZone;
|
||||
|
||||
if (zone == null)
|
||||
{
|
||||
lines.Add("No zone loaded");
|
||||
return lines;
|
||||
}
|
||||
|
||||
lines.Add($"=== ZONE {zone.Id} ===");
|
||||
lines.Add($"Size: {zone.Width}x{zone.Height}");
|
||||
lines.Add($"Planet: {zone.Planet}");
|
||||
lines.Add($"Type: {zone.Type}");
|
||||
lines.Add("");
|
||||
lines.Add($"=== OBJECTS ({zone.Objects.Count}) ===");
|
||||
|
||||
foreach (var obj in zone.Objects)
|
||||
{
|
||||
lines.Add($" {obj.Type} at ({obj.X},{obj.Y}) arg={obj.Argument}");
|
||||
}
|
||||
|
||||
lines.Add("");
|
||||
lines.Add($"=== NPCs ({_state.ZoneNPCs.Count}) ===");
|
||||
|
||||
foreach (var npc in _state.ZoneNPCs)
|
||||
{
|
||||
string name = npc.CharacterId < _gameData.Characters.Count
|
||||
? _gameData.Characters[npc.CharacterId].Name ?? $"#{npc.CharacterId}"
|
||||
: $"#{npc.CharacterId}";
|
||||
lines.Add($" {name} at ({npc.X},{npc.Y}) HP:{npc.Health}");
|
||||
}
|
||||
|
||||
return lines;
|
||||
}
|
||||
|
||||
private List<string> GetScriptsContent()
|
||||
{
|
||||
var lines = new List<string>();
|
||||
var zone = _state.CurrentZone;
|
||||
|
||||
if (zone == null)
|
||||
{
|
||||
lines.Add("No zone loaded");
|
||||
return lines;
|
||||
}
|
||||
|
||||
lines.Add($"=== IACT SCRIPTS ({zone.Actions.Count}) ===");
|
||||
lines.Add("");
|
||||
|
||||
for (int i = 0; i < zone.Actions.Count; i++)
|
||||
{
|
||||
var action = zone.Actions[i];
|
||||
lines.Add($"--- Action #{i} ---");
|
||||
lines.Add("IF:");
|
||||
|
||||
foreach (var cond in action.Conditions)
|
||||
{
|
||||
var args = string.Join(",", cond.Arguments);
|
||||
lines.Add($" {cond.Opcode}({args})");
|
||||
}
|
||||
|
||||
lines.Add("THEN:");
|
||||
|
||||
foreach (var instr in action.Instructions)
|
||||
{
|
||||
var args = string.Join(",", instr.Arguments);
|
||||
var text = !string.IsNullOrEmpty(instr.Text) ? $" \"{Truncate(instr.Text, 30)}\"" : "";
|
||||
lines.Add($" {instr.Opcode}({args}){text}");
|
||||
}
|
||||
|
||||
lines.Add("");
|
||||
}
|
||||
|
||||
return lines;
|
||||
}
|
||||
|
||||
private List<string> GetInventoryContent()
|
||||
{
|
||||
var lines = new List<string>
|
||||
{
|
||||
"=== INVENTORY ===",
|
||||
""
|
||||
};
|
||||
|
||||
lines.Add($"Items ({_state.Inventory.Count}):");
|
||||
for (int i = 0; i < _state.Inventory.Count; i++)
|
||||
{
|
||||
var itemId = _state.Inventory[i];
|
||||
var selected = _state.SelectedItem == itemId ? " [SELECTED]" : "";
|
||||
var name = _gameData.TileNames.TryGetValue(itemId, out var n) ? n : $"Item #{itemId}";
|
||||
lines.Add($" {i + 1}. {name}{selected}");
|
||||
}
|
||||
|
||||
lines.Add("");
|
||||
lines.Add($"Weapons ({_state.Weapons.Count}):");
|
||||
for (int i = 0; i < _state.Weapons.Count; i++)
|
||||
{
|
||||
var weaponId = _state.Weapons[i];
|
||||
var equipped = i == _state.CurrentWeaponIndex ? " [EQUIPPED]" : "";
|
||||
var name = _gameData.TileNames.TryGetValue(weaponId, out var n) ? n : $"Weapon #{weaponId}";
|
||||
lines.Add($" {name}{equipped}");
|
||||
}
|
||||
|
||||
return lines;
|
||||
}
|
||||
|
||||
private List<string> GetMapContent()
|
||||
{
|
||||
var lines = new List<string>();
|
||||
var world = _worldGenerator?.CurrentWorld;
|
||||
|
||||
if (world?.Grid == null)
|
||||
{
|
||||
lines.Add("No world generated");
|
||||
return lines;
|
||||
}
|
||||
|
||||
lines.Add("=== WORLD MAP (10x10) ===");
|
||||
lines.Add("L=Landing G=Goal F=Force .=Empty");
|
||||
lines.Add("");
|
||||
|
||||
// Header
|
||||
lines.Add(" 0 1 2 3 4 5 6 7 8 9");
|
||||
|
||||
for (int y = 0; y < 10; y++)
|
||||
{
|
||||
var row = $" {y} ";
|
||||
for (int x = 0; x < 10; x++)
|
||||
{
|
||||
var zoneId = world.Grid[y, x];
|
||||
bool isLanding = (x == world.LandingPosition.x && y == world.LandingPosition.y);
|
||||
bool isGoal = (x == world.ObjectivePosition.x && y == world.ObjectivePosition.y);
|
||||
bool isForce = world.TheForceZoneId.HasValue &&
|
||||
(x == world.TheForcePosition.x && y == world.TheForcePosition.y);
|
||||
|
||||
if (isLanding) row += " L ";
|
||||
else if (isGoal) row += " G ";
|
||||
else if (isForce) row += " F ";
|
||||
else if (zoneId.HasValue) row += $"{zoneId.Value,4} ";
|
||||
else row += " . ";
|
||||
}
|
||||
lines.Add(row);
|
||||
}
|
||||
|
||||
lines.Add("");
|
||||
lines.Add($"Landing: Zone {world.LandingZoneId} at ({world.LandingPosition.x},{world.LandingPosition.y})");
|
||||
lines.Add($"Goal: Zone {world.ObjectiveZoneId} at ({world.ObjectivePosition.x},{world.ObjectivePosition.y})");
|
||||
if (world.TheForceZoneId.HasValue)
|
||||
lines.Add($"Force: Zone {world.TheForceZoneId} at ({world.TheForcePosition.x},{world.TheForcePosition.y})");
|
||||
|
||||
return lines;
|
||||
}
|
||||
|
||||
private string Truncate(string s, int max) => s.Length <= max ? s : s.Substring(0, max - 3) + "...";
|
||||
}
|
||||
@@ -11,6 +11,7 @@ public unsafe class MenuBar
|
||||
{
|
||||
private readonly BitmapFont _font;
|
||||
private SDLRenderer* _renderer;
|
||||
private uint _windowId;
|
||||
|
||||
public const int Height = 22;
|
||||
|
||||
@@ -22,7 +23,7 @@ public unsafe class MenuBar
|
||||
private readonly string[][] _menuItems = {
|
||||
new[] { "New Game: Small", "New Game: Medium", "New Game: Large", "New Game: X-tra Large", "-", "Save Game", "Save As...", "Load Game", "-", "Exit" },
|
||||
new[] { "Asset Viewer (F2)", "Script Editor (F3)", "Map Viewer (F4)", "-", "Enable Bot", "Disable Bot" },
|
||||
new[] { "Graphics: 2x Scale", "Graphics: 4x Scale", "-", "Keyboard Controls", "Controller Controls", "-", "Select Data File..." },
|
||||
new[] { "Graphics: 1x Scale", "Graphics: 2x Scale", "Graphics: 4x Scale", "-", "Keyboard Controls", "Controller Controls", "-", "Select Data File..." },
|
||||
new[] { "About Yoda Stories NG", "GitHub Repository" }
|
||||
};
|
||||
|
||||
@@ -55,13 +56,22 @@ public unsafe class MenuBar
|
||||
_font = font;
|
||||
}
|
||||
|
||||
public void SetRenderer(SDLRenderer* renderer)
|
||||
public void SetRenderer(SDLRenderer* renderer, uint windowId)
|
||||
{
|
||||
_renderer = renderer;
|
||||
_windowId = windowId;
|
||||
}
|
||||
|
||||
public bool HandleEvent(SDLEvent* evt)
|
||||
{
|
||||
// Only handle events for our window
|
||||
if (evt->Type == (uint)SDLEventType.Mousebuttondown && evt->Button.WindowID != _windowId)
|
||||
return false;
|
||||
if (evt->Type == (uint)SDLEventType.Mousemotion && evt->Motion.WindowID != _windowId)
|
||||
return false;
|
||||
if (evt->Type == (uint)SDLEventType.Keydown && evt->Key.WindowID != _windowId)
|
||||
return false;
|
||||
|
||||
if (evt->Type == (uint)SDLEventType.Mousebuttondown)
|
||||
{
|
||||
int mx = evt->Button.X;
|
||||
@@ -216,11 +226,12 @@ public unsafe class MenuBar
|
||||
case 2: // Config
|
||||
switch (item)
|
||||
{
|
||||
case 0: OnSetScale?.Invoke(2); break;
|
||||
case 1: OnSetScale?.Invoke(4); break;
|
||||
case 3: OnShowKeyboardControls?.Invoke(); break;
|
||||
case 4: OnShowControllerControls?.Invoke(); break;
|
||||
case 6: OnSelectDataFile?.Invoke(); break;
|
||||
case 0: OnSetScale?.Invoke(1); break;
|
||||
case 1: OnSetScale?.Invoke(2); break;
|
||||
case 2: OnSetScale?.Invoke(4); break;
|
||||
case 4: OnShowKeyboardControls?.Invoke(); break;
|
||||
case 5: OnShowControllerControls?.Invoke(); break;
|
||||
case 7: OnSelectDataFile?.Invoke(); break;
|
||||
}
|
||||
break;
|
||||
case 3: // About
|
||||
|
||||
Reference in New Issue
Block a user