R-Type
Distributed multiplayer game engine in C++
Loading...
Searching...
No Matches
KeyBindingsMenu.cpp
Go to the documentation of this file.
1/*
2** EPITECH PROJECT, 2026
3** r-type
4** File description:
5** KeyBindingsMenu - Menu for remapping key bindings implementation
6*/
7
9#include <raylib.h>
10#include "../common/Logger/Logger.hpp"
11
12namespace Game {
13
15 : BaseMenu(uiFactory), _graphics(graphics) {}
16
18 if (!_menu) {
19 return;
20 }
21
22 _menu->Clear();
23 _bindingButtons.clear();
24
25 // Title (using a disabled button as label)
26 std::shared_ptr<UI::IButton> titleLabel = _uiFactory.CreateButton();
27 titleLabel->SetSize(400, 30);
28 titleLabel->SetAlign(UI::Align::CENTER_HORIZONTAL);
29 titleLabel->ApplyAlignment();
30 float labelX = 0.0f;
31 float labelY = 0.0f;
32 titleLabel->GetPosition(labelX, labelY);
33 titleLabel->SetPosition(labelX, 15);
34 titleLabel->SetBackgroundColor(0x00000000); // Transparent
35 titleLabel->SetHoverColor(0x00000000);
36 titleLabel->SetText("Key Bindings");
37 titleLabel->SetTextSize(22);
38 titleLabel->SetTextColor(0xFFFFFFFF);
39 titleLabel->SetFont(-1);
40 _menu->AddButton(titleLabel);
41
42 // Instructions label
43 std::shared_ptr<UI::IButton> instructionsLabel = _uiFactory.CreateButton();
44 instructionsLabel->SetSize(500, 20);
45 instructionsLabel->SetAlign(UI::Align::CENTER_HORIZONTAL);
46 instructionsLabel->ApplyAlignment();
47 instructionsLabel->GetPosition(labelX, labelY);
48 instructionsLabel->SetPosition(labelX, 50);
49 instructionsLabel->SetBackgroundColor(0x00000000);
50 instructionsLabel->SetHoverColor(0x00000000);
51 instructionsLabel->SetText("Click a key to remap. Press ESC to cancel.");
52 instructionsLabel->SetTextSize(14);
53 instructionsLabel->SetTextColor(0xFFAAAAAA);
54 instructionsLabel->SetFont(-1);
55 _menu->AddButton(instructionsLabel);
56
57 // Create binding rows for each action
58 float startY = 80;
59 float rowHeight = 32;
60 int rowIndex = 0;
61
62 // Movement actions
63 CreateBindingRow(Input::GameAction::MOVE_UP, startY + rowHeight * static_cast<float>(rowIndex++));
64 CreateBindingRow(Input::GameAction::MOVE_DOWN, startY + rowHeight * static_cast<float>(rowIndex++));
65 CreateBindingRow(Input::GameAction::MOVE_LEFT, startY + rowHeight * static_cast<float>(rowIndex++));
66 CreateBindingRow(Input::GameAction::MOVE_RIGHT, startY + rowHeight * static_cast<float>(rowIndex++));
67
68 // Combat
69 CreateBindingRow(Input::GameAction::SHOOT, startY + rowHeight * static_cast<float>(rowIndex++));
70
71 // UI/System
72 CreateBindingRow(Input::GameAction::PAUSE_MENU, startY + rowHeight * static_cast<float>(rowIndex++));
73 CreateBindingRow(Input::GameAction::CHAT_OPEN, startY + rowHeight * static_cast<float>(rowIndex++));
74
75 // Menu Navigation
76 CreateBindingRow(Input::GameAction::MENU_NEXT, startY + rowHeight * static_cast<float>(rowIndex++));
78 startY + rowHeight * static_cast<float>(rowIndex++));
80 startY + rowHeight * static_cast<float>(rowIndex++));
81 CreateBindingRow(Input::GameAction::MENU_BACK, startY + rowHeight * static_cast<float>(rowIndex++));
82
83 // Bottom buttons
84 float bottomY = startY + rowHeight * static_cast<float>(rowIndex) + 15;
85
86 // Reset to Defaults button
88 CreateCenteredButton("Reset to Defaults", bottomY - (_graphics.GetWindowHeight() / 2.0f), 180, 40,
89 0xFF505050, 0xFF707070, [this]() {
90 Input::KeyBindings::getInstance().ResetToDefaults();
91 RefreshAllBindings();
92 if (_onBindingsChanged) {
93 _onBindingsChanged();
94 }
95 });
96 _resetButton->SetPosition(_graphics.GetWindowWidth() / 2.0f - 200, bottomY);
97 _menu->AddButton(_resetButton);
98
99 // Back button
100 _backButton = CreateCenteredButton("Back", bottomY - (_graphics.GetWindowHeight() / 2.0f), 120, 40,
101 0xFF404040, 0xFF606060, [this]() {
102 if (_onBack) {
103 _onBack();
104 }
105 });
106 _backButton->SetPosition(_graphics.GetWindowWidth() / 2.0f + 80, bottomY);
107 _menu->AddButton(_backButton);
108 }
109
110 void KeyBindingsMenu::CreateBindingRow(Input::GameAction action, float yOffset) {
111 auto &bindings = Input::KeyBindings::getInstance();
112 float centerX = static_cast<float>(_graphics.GetWindowWidth()) / 2.0f;
113
114 // Action name label
115 std::shared_ptr<UI::IButton> actionLabel = _uiFactory.CreateButton();
116 actionLabel->SetSize(150, 28);
117 actionLabel->SetPosition(centerX - 280, yOffset);
118 actionLabel->SetBackgroundColor(0x00000000);
119 actionLabel->SetHoverColor(0x00000000);
120 actionLabel->SetText(Input::KeyBindings::GetActionName(action));
121 actionLabel->SetTextSize(16);
122 actionLabel->SetTextColor(0xFFFFFFFF);
123 actionLabel->SetFont(-1);
124 _menu->AddButton(actionLabel);
125
126 // Primary key button
127 std::shared_ptr<UI::IButton> primaryButton = _uiFactory.CreateButton();
128 primaryButton->SetSize(120, 28);
129 primaryButton->SetPosition(centerX - 100, yOffset);
130 primaryButton->SetBackgroundColor(0xFF404040);
131 primaryButton->SetHoverColor(0xFF606060);
132 primaryButton->SetText(Input::KeyBindings::GetBindingName(bindings.GetPrimaryKey(action)));
133 primaryButton->SetTextSize(14);
134 primaryButton->SetTextColor(0xFFFFFFFF);
135 primaryButton->SetFont(-1);
136 primaryButton->SetCallback([this, action]() { StartKeyCapture(action, true); });
137 _menu->AddButton(primaryButton);
138
139 // Secondary key button
140 std::shared_ptr<UI::IButton> secondaryButton = _uiFactory.CreateButton();
141 secondaryButton->SetSize(120, 28);
142 secondaryButton->SetPosition(centerX + 40, yOffset);
143 secondaryButton->SetBackgroundColor(0xFF404040);
144 secondaryButton->SetHoverColor(0xFF606060);
145 int secondaryBinding = bindings.GetSecondaryKey(action);
146 secondaryButton->SetText(
147 secondaryBinding != KEY_NULL ? Input::KeyBindings::GetBindingName(secondaryBinding) : "-");
148 secondaryButton->SetTextSize(14);
149 secondaryButton->SetTextColor(0xFFCCCCCC);
150 secondaryButton->SetFont(-1);
151 secondaryButton->SetCallback([this, action]() { StartKeyCapture(action, false); });
152 _menu->AddButton(secondaryButton);
153
154 // Clear secondary button
155 std::shared_ptr<UI::IButton> clearButton = _uiFactory.CreateButton();
156 clearButton->SetSize(30, 28);
157 clearButton->SetPosition(centerX + 170, yOffset);
158 clearButton->SetBackgroundColor(0xFF603030);
159 clearButton->SetHoverColor(0xFF804040);
160 clearButton->SetText("X");
161 clearButton->SetTextSize(14);
162 clearButton->SetTextColor(0xFFFFFFFF);
163 clearButton->SetFont(-1);
164 clearButton->SetCallback([this, action]() {
166 UpdateBindingButtonText(action, false);
167 if (_onBindingsChanged) {
168 _onBindingsChanged();
169 }
170 });
171 _menu->AddButton(clearButton);
172
173 // Store button references
174 _bindingButtons[action] = {primaryButton, secondaryButton};
175 }
176
177 void KeyBindingsMenu::Update() {
178 if (!_menu || !_menu->IsVisible()) {
179 return;
180 }
181
182 // Handle key capture mode
183 if (_isCapturing) {
184 // Check for ESC to cancel
185 if (_graphics.IsKeyPressed(KEY_ESCAPE)) {
186 CancelKeyCapture();
187 return;
188 }
189
190 // Check for gamepad button presses first (on any connected gamepad)
191 for (int gp = 0; gp < 4; ++gp) {
192 if (_graphics.IsGamepadAvailable(gp)) {
193 for (int button = 0; button <= GAMEPAD_BUTTON_RIGHT_THUMB; ++button) {
194 if (_graphics.IsGamepadButtonPressed(gp, button)) {
195 // Convert gamepad button to binding value
196 int binding = Input::GamepadButtonToBinding(button);
197 HandleCapturedKey(binding);
198 return;
199 }
200 }
201 }
202 }
203
204 // Check for any keyboard key press
205 for (int key = KEY_SPACE; key <= KEY_KP_EQUAL; ++key) {
206 if (_graphics.IsKeyPressed(key)) {
207 HandleCapturedKey(key);
208 return;
209 }
210 }
211 // Also check letter keys
212 for (int key = KEY_A; key <= KEY_Z; ++key) {
213 if (_graphics.IsKeyPressed(key)) {
214 HandleCapturedKey(key);
215 return;
216 }
217 }
218
219 // Don't process menu updates during capture
220 return;
221 }
222
223 BaseMenu::Update();
224 }
225
226 void KeyBindingsMenu::Render() {
227 if (!_menu || !_menu->IsVisible()) {
228 return;
229 }
230
231 // Draw dim overlay if in overlay mode
232 if (_mode == Mode::OVERLAY) {
233 _graphics.DrawRectFilled(0, 0, _graphics.GetWindowWidth(), _graphics.GetWindowHeight(),
234 _overlayDimColor);
235 }
236
237 BaseMenu::Render();
238
239 // Draw capture overlay
240 if (_isCapturing) {
241 // Dim the whole screen
242 _graphics.DrawRectFilled(0, 0, _graphics.GetWindowWidth(), _graphics.GetWindowHeight(),
243 0xCC000000);
244
245 // Draw instruction box
246 int boxWidth = 400;
247 int boxHeight = 100;
248 int boxX = (_graphics.GetWindowWidth() - boxWidth) / 2;
249 int boxY = (_graphics.GetWindowHeight() - boxHeight) / 2;
250
251 _graphics.DrawRectFilled(boxX, boxY, boxWidth, boxHeight, 0xFF303030);
252 _graphics.DrawRect(boxX, boxY, boxWidth, boxHeight, 0xFF4080FF);
253
254 std::string actionName = Input::KeyBindings::GetActionName(_captureAction);
255 std::string slotName = _capturePrimary ? "Primary" : "Secondary";
256 std::string msg = "Press a key for: " + actionName + " (" + slotName + ")";
257
258 _graphics.DrawText(msg.c_str(), boxX + 20, boxY + 30, 18, 0xFFFFFFFF);
259 _graphics.DrawText("Press ESC to cancel", boxX + 120, boxY + 60, 14, 0xFFAAAAAA);
260 }
261 }
262
263 void KeyBindingsMenu::StartKeyCapture(Input::GameAction action, bool isPrimary) {
264 _isCapturing = true;
265 _captureAction = action;
266 _capturePrimary = isPrimary;
267
268 LOG_INFO("[KeyBindingsMenu] Started key capture for: ", Input::KeyBindings::GetActionName(action),
269 " (", isPrimary ? "primary" : "secondary", ")");
270 }
271
272 void KeyBindingsMenu::CancelKeyCapture() {
273 _isCapturing = false;
274 LOG_INFO("[KeyBindingsMenu] Key capture cancelled");
275 }
276
277 void KeyBindingsMenu::HandleCapturedKey(int key) {
278 auto &bindings = Input::KeyBindings::getInstance();
279
280 if (_capturePrimary) {
281 bindings.SetPrimaryKey(_captureAction, key);
282 } else {
283 bindings.SetSecondaryKey(_captureAction, key);
284 }
285
286 UpdateBindingButtonText(_captureAction, _capturePrimary);
287
288 LOG_INFO("[KeyBindingsMenu] Bound ", Input::KeyBindings::GetActionName(_captureAction), " ",
289 _capturePrimary ? "primary" : "secondary", " to: ", Input::KeyBindings::GetKeyName(key));
290
291 _isCapturing = false;
292
293 if (_onBindingsChanged) {
294 _onBindingsChanged();
295 }
296 }
297
298 void KeyBindingsMenu::UpdateBindingButtonText(Input::GameAction action, bool isPrimary) {
299 auto it = _bindingButtons.find(action);
300 if (it == _bindingButtons.end()) {
301 return;
302 }
303
304 auto &bindings = Input::KeyBindings::getInstance();
305
306 if (isPrimary && it->second.primaryButton) {
307 it->second.primaryButton->SetText(
308 Input::KeyBindings::GetBindingName(bindings.GetPrimaryKey(action)));
309 } else if (!isPrimary && it->second.secondaryButton) {
310 int secondaryBinding = bindings.GetSecondaryKey(action);
311 it->second.secondaryButton->SetText(
312 secondaryBinding != KEY_NULL ? Input::KeyBindings::GetBindingName(secondaryBinding) : "-");
313 }
314 }
315
316 void KeyBindingsMenu::RefreshAllBindings() {
317 for (const auto &[action, buttons] : _bindingButtons) {
318 UpdateBindingButtonText(action, true);
319 UpdateBindingButtonText(action, false);
320 }
321 }
322
323 void KeyBindingsMenu::SetMode(Mode mode) {
324 _mode = mode;
325 }
326
327 KeyBindingsMenu::Mode KeyBindingsMenu::GetMode() const {
328 return _mode;
329 }
330
331 void KeyBindingsMenu::SetOverlayDimColor(unsigned int color) {
332 _overlayDimColor = color;
333 }
334
335 unsigned int KeyBindingsMenu::GetOverlayDimColor() const {
336 return _overlayDimColor;
337 }
338
339 bool KeyBindingsMenu::ShouldDimBackground() const {
340 return _mode == Mode::OVERLAY;
341 }
342
343 void KeyBindingsMenu::SetOnBack(std::function<void()> callback) {
344 _onBack = std::move(callback);
345 }
346
347 void KeyBindingsMenu::SetOnBindingsChanged(std::function<void()> callback) {
348 _onBindingsChanged = std::move(callback);
349 }
350
351} // namespace Game
#define LOG_INFO(...)
Definition Logger.hpp:181
Base class for all menu implementations.
Definition BaseMenu.hpp:26
std::shared_ptr< UI::IMenu > _menu
Definition BaseMenu.hpp:101
std::shared_ptr< UI::IButton > CreateCenteredButton(const char *label, float offsetY, float width, float height, unsigned int backgroundColor, unsigned int hoverColor, std::function< void()> callback)
Create a button with standard styling and positioning.
Definition BaseMenu.cpp:60
UI::IUIFactory & _uiFactory
Definition BaseMenu.hpp:100
void CreateBindingRow(Input::GameAction action, float yOffset)
Create a binding row for an action.
Graphics::IGraphics & _graphics
void Initialize() override
Initialize UI elements.
Mode
Display mode for the menu.
std::unordered_map< Input::GameAction, BindingButtons > _bindingButtons
std::shared_ptr< UI::IButton > _resetButton
KeyBindingsMenu(UI::IUIFactory &uiFactory, Graphics::IGraphics &graphics)
Construct a new KeyBindingsMenu.
Abstract interface for graphics rendering operations.
Definition IGraphics.hpp:32
virtual int GetWindowHeight() const =0
Get current window height in pixels.
static KeyBindings & getInstance()
Get the singleton instance.
static std::string GetActionName(GameAction action)
Get human-readable name of an action.
void ClearSecondaryKey(GameAction action)
Clear the secondary key for an action.
static std::string GetBindingName(int binding)
Get human-readable name of a binding (keyboard key or gamepad button)
static std::string GetKeyName(int key)
Get human-readable name of a key.
Abstract factory interface for creating UI elements.
virtual std::unique_ptr< IButton > CreateButton()=0
Create a button instance.
GameAction
Enumeration of all bindable game actions.
int GamepadButtonToBinding(int button)
Convert a gamepad button to a binding value.
@ CENTER_HORIZONTAL
Center on the X axis.