R-Type
Distributed multiplayer game engine in C++
Loading...
Searching...
No Matches
ConnectionMenu.cpp
Go to the documentation of this file.
1/*
2** EPITECH PROJECT, 2025
3** r-type
4** File description:
5** ConnectionMenu - Menu for entering connection details
6*/
7
9#include <regex>
10#include <sstream>
11#include "../common/Logger/Logger.hpp"
12
13namespace Game {
15 : BaseMenu(uiFactory), _graphics(graphics) {}
16
18 std::function<void(const std::string &, const std::string &, const std::string &)> onJoin) {
19 _onJoin = std::move(onJoin);
20 }
21
22 void ConnectionMenu::SetOnBack(std::function<void()> onBack) {
23 _onBack = std::move(onBack);
24 }
25
27 if (!_menu) {
28 return;
29 }
30
31 const float inputWidth = 300.0f;
32 const float inputHeight = 40.0f;
33 const float spacing = 20.0f;
34 const float buttonWidth = 140.0f;
35 const float buttonHeight = 45.0f;
36 const float buttonSpacing = 20.0f;
37
38 const float startY = 150.0f; // Start from top with margin
39
40 // Clear menu and create text inputs
41 _menu->Clear();
42
43 // --- Nickname Input ---
45 _nicknameInput->SetSize(inputWidth, inputHeight);
47 _nicknameInput->ApplyAlignment();
48
49 float x, y;
50 _nicknameInput->GetPosition(x, y);
51 _nicknameInput->SetPosition(x, startY);
52
53 _nicknameInput->SetPlaceholder("Enter your nickname...");
54 _nicknameInput->SetMaxLength(20);
55 _nicknameInput->SetValidationRegex("[a-zA-Z0-9_-]+");
56 _nicknameInput->SetTextSize(18);
57 _nicknameInput->SetBackgroundColor(0xFF2A2A2A);
58 _nicknameInput->SetBorderColor(0xFF505050);
59 _nicknameInput->SetActiveBorderColor(0xFF4CAF50);
60 _nicknameInput->SetTextColor(0xFFFFFFFF);
61 _nicknameInput->SetPlaceholderColor(0xFF808080);
62 _nicknameInput->SetOnTextChanged([this](const std::string &text) {
63 LOG_INFO("[ConnectionMenu] Nickname: " + text);
64 ClearError(); // Clear error when user types
65 });
66
67 // --- IP Input ---
69 _ipInput->SetSize(inputWidth, inputHeight);
71 _ipInput->ApplyAlignment();
72
73 _ipInput->GetPosition(x, y);
74 float ipY = startY + inputHeight + spacing;
75 _ipInput->SetPosition(x, ipY);
76
77 _ipInput->SetPlaceholder("127.0.0.1");
78 _ipInput->SetMaxLength(15);
79 _ipInput->SetValidationRegex("[0-9.]+");
80 _ipInput->SetTextSize(18);
81 _ipInput->SetBackgroundColor(0xFF2A2A2A);
82 _ipInput->SetBorderColor(0xFF505050);
83 _ipInput->SetActiveBorderColor(0xFF4CAF50);
84 _ipInput->SetTextColor(0xFFFFFFFF);
85 _ipInput->SetPlaceholderColor(0xFF808080);
86 _ipInput->SetOnTextChanged([this](const std::string &text) {
87 LOG_INFO("[ConnectionMenu] IP: " + text);
88 ClearError(); // Clear error when user types
89 });
90
91 // --- Port Input ---
93 _portInput->SetSize(inputWidth, inputHeight);
95 _portInput->ApplyAlignment();
96
97 _portInput->GetPosition(x, y);
98 float portY = ipY + inputHeight + spacing;
99 _portInput->SetPosition(x, portY);
100
101 _portInput->SetPlaceholder("4242");
102 _portInput->SetMaxLength(5);
103 _portInput->SetValidationRegex("[0-9]+");
104 _portInput->SetTextSize(18);
105 _portInput->SetBackgroundColor(0xFF2A2A2A);
106 _portInput->SetBorderColor(0xFF505050);
107 _portInput->SetActiveBorderColor(0xFF4CAF50);
108 _portInput->SetTextColor(0xFFFFFFFF);
109 _portInput->SetPlaceholderColor(0xFF808080);
110 _portInput->SetOnTextChanged([this](const std::string &text) {
111 LOG_INFO("[ConnectionMenu] Port: " + text);
112 ClearError(); // Clear error when user types
113 });
114
115 // --- Buttons (Join and Back) ---
116 _buttonsY = portY + inputHeight + spacing * 2; // Store for error message positioning
117 _buttonHeight = buttonHeight;
118 float totalButtonWidth = buttonWidth * 2 + buttonSpacing;
119 float joinButtonX = -(totalButtonWidth / 2.0f);
120 float backButtonX = joinButtonX + buttonWidth + buttonSpacing;
121
122 // Helper to create buttons with horizontal alignment
123 auto createHorizontalButton = [&](const char *label, float posX, unsigned int backgroundColor,
124 unsigned int hoverColor, std::function<void()> callback) {
125 auto button = _uiFactory.CreateButton();
126 button->SetSize(buttonWidth, buttonHeight);
127 button->SetAlign(UI::Align::CENTER_HORIZONTAL);
128 button->ApplyAlignment();
129
130 float bx, by;
131 button->GetPosition(bx, by);
132 button->SetPosition(bx + posX + (totalButtonWidth / 2.0f) - (buttonWidth / 2.0f), _buttonsY);
133
134 button->SetBackgroundColor(backgroundColor);
135 button->SetHoverColor(hoverColor);
136 button->SetText(label);
137 button->SetTextSize(18);
138 button->SetTextColor(0xFFFFFFFF);
139 button->SetFont(-1);
140 button->SetCallback(WrapWithClickSound(std::move(callback)));
141 return button;
142 };
143
144 _menu->AddButton(createHorizontalButton("JOIN", joinButtonX, 0xFF4CAF50, 0xFF66BB6A,
145 [this]() { OnJoinClicked(); }));
146
147 _menu->AddButton(createHorizontalButton("BACK", backButtonX, 0xFF424242, 0xFF616161,
148 [this]() { OnBackClicked(); }));
149 }
150
153 if (_nicknameInput) {
154 _nicknameInput->Update();
155 }
156 if (_ipInput) {
157 _ipInput->Update();
158 }
159 if (_portInput) {
160 _portInput->Update();
161 }
162 }
163
165 if (!_menu || !_menu->IsVisible()) {
166 return;
167 }
168
169 // Render the inputs and buttons
170 if (_nicknameInput) {
171 _nicknameInput->Render();
172 }
173 if (_ipInput) {
174 _ipInput->Render();
175 }
176 if (_portInput) {
177 _portInput->Render();
178 }
179
181
182 // Render error message if present
183 if (_hasError && !_errorMessage.empty()) {
184 int screenWidth = _graphics.GetScreenWidth();
185
186 const int fontSize = 16;
187 const int errorMargin = 15; // Spacing below buttons
188
189 // Position error message below the buttons
190 const int errorY = static_cast<int>(_buttonsY + _buttonHeight + errorMargin);
191
192 // Estimate text width and center it
193 int textWidth = static_cast<int>(_errorMessage.length() * fontSize * 0.6);
194 int errorX = (screenWidth - textWidth) / 2;
195
196 // Draw error message in red
197 _graphics.DrawText(_errorMessage.c_str(), errorX, errorY, fontSize, 0xFFFF0000);
198 }
199 }
200
202 std::string nickname = _nicknameInput ? _nicknameInput->GetText() : "";
203 std::string ip = _ipInput ? _ipInput->GetText() : "";
204 std::string port = _portInput ? _portInput->GetText() : "";
205
206 // Validate inputs BEFORE applying defaults
207 std::string errorMsg;
208
209 // Validate nickname
210 if (!ValidateNickname(nickname, errorMsg)) {
211 _errorMessage = errorMsg;
212 _hasError = true;
214 if (_nicknameInput) {
215 _nicknameInput->SetBorderColor(0xFFFF0000); // Red border
216 _nicknameInput->SetActiveBorderColor(0xFFFF0000);
217 }
218 return;
219 }
220
221 // Validate IP
222 if (!ValidateIP(ip, errorMsg)) {
223 _errorMessage = errorMsg;
224 _hasError = true;
226 if (_ipInput) {
227 _ipInput->SetBorderColor(0xFFFF0000); // Red border
228 _ipInput->SetActiveBorderColor(0xFFFF0000);
229 }
230 return;
231 }
232
233 // Validate port
234 if (!ValidatePort(port, errorMsg)) {
235 _errorMessage = errorMsg;
236 _hasError = true;
238 if (_portInput) {
239 _portInput->SetBorderColor(0xFFFF0000); // Red border
240 _portInput->SetActiveBorderColor(0xFFFF0000);
241 }
242 return;
243 }
244
245 // All validations passed - now apply defaults if needed
246 if (nickname.empty()) {
247 nickname = "Player";
248 }
249 if (ip.empty()) {
250 ip = "127.0.0.1";
251 }
252 if (port.empty()) {
253 port = "4242";
254 }
255
256 LOG_INFO("[ConnectionMenu] Join clicked!");
257 LOG_INFO("[ConnectionMenu] Nickname: " + nickname);
258 LOG_INFO("[ConnectionMenu] IP: " + ip);
259 LOG_INFO("[ConnectionMenu] Port: " + port);
260
261 if (_onJoin) {
262 _onJoin(nickname, ip, port);
263 }
264 }
265
267 LOG_INFO("[ConnectionMenu] Back button clicked!");
268 if (_onBack) {
269 _onBack();
270 }
271 }
272
273 bool ConnectionMenu::ValidateNickname(const std::string &nickname, std::string &errorMsg) {
274 if (nickname.empty()) {
275 errorMsg = "Nickname cannot be empty";
276 return false;
277 }
278 if (nickname.length() < 3) {
279 errorMsg = "Nickname must be at least 3 characters";
280 return false;
281 }
282 return true;
283 }
284
285 bool ConnectionMenu::ValidateIP(const std::string &ip, std::string &errorMsg) {
286 if (ip.empty()) {
287 errorMsg = "IP address cannot be empty";
288 return false;
289 }
290
291 // Regex for valid IP: xxx.xxx.xxx.xxx where xxx is 0-255
292 std::regex ipPattern(
293 "^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}"
294 "(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$");
295
296 if (!std::regex_match(ip, ipPattern)) {
297 errorMsg = "Invalid IP address format (expected: xxx.xxx.xxx.xxx)";
298 return false;
299 }
300
301 return true;
302 }
303
304 bool ConnectionMenu::ValidatePort(const std::string &port, std::string &errorMsg) {
305 if (port.empty()) {
306 errorMsg = "Port cannot be empty";
307 return false;
308 }
309
310 // Check if all characters are digits
311 for (char c : port) {
312 if (!std::isdigit(c)) {
313 errorMsg = "Port must contain only digits";
314 return false;
315 }
316 }
317
318 // Convert to int and check range
319 try {
320 int portNum = std::stoi(port);
321 if (portNum < 1 || portNum > 65535) {
322 errorMsg = "Port must be between 1 and 65535";
323 return false;
324 }
325 } catch (...) {
326 errorMsg = "Invalid port number";
327 return false;
328 }
329
330 return true;
331 }
332
334 _hasError = false;
335 _errorMessage.clear();
336
337 // Reset border colors to normal
338 if (_nicknameInput) {
339 _nicknameInput->SetBorderColor(0xFF505050);
340 _nicknameInput->SetActiveBorderColor(0xFF4CAF50);
341 }
342 if (_ipInput) {
343 _ipInput->SetBorderColor(0xFF505050);
344 _ipInput->SetActiveBorderColor(0xFF4CAF50);
345 }
346 if (_portInput) {
347 _portInput->SetBorderColor(0xFF505050);
348 _portInput->SetActiveBorderColor(0xFF4CAF50);
349 }
350
352 }
353} // namespace Game
#define LOG_INFO(...)
Definition Logger.hpp:181
Base class for all menu implementations.
Definition BaseMenu.hpp:26
virtual void Update()
Update menu state (should be called every frame).
Definition BaseMenu.cpp:16
std::shared_ptr< UI::IMenu > _menu
Definition BaseMenu.hpp:101
virtual void Render()
Render menu (should be called every frame).
Definition BaseMenu.cpp:22
std::function< void()> WrapWithClickSound(std::function< void()> callback)
Wrap a callback to play click sound before executing.
Definition BaseMenu.cpp:48
UI::IUIFactory & _uiFactory
Definition BaseMenu.hpp:100
void Render() override
Render menu (should be called every frame).
std::unique_ptr< UI::ITextInput > _ipInput
std::function< void(const std::string &, const std::string &, const std::string &)> _onJoin
void Update() override
Update menu state (should be called every frame).
void SetOnBack(std::function< void()> onBack)
Set callback triggered when the Back button is clicked.
std::function< void()> _onBack
bool ValidateIP(const std::string &ip, std::string &errorMsg)
bool ValidateNickname(const std::string &nickname, std::string &errorMsg)
void Initialize() override
Initialize menu (must be implemented by derived classes).
ConnectionMenu(UI::IUIFactory &uiFactory, Graphics::IGraphics &graphics)
std::unique_ptr< UI::ITextInput > _portInput
Graphics::IGraphics & _graphics
std::unique_ptr< UI::ITextInput > _nicknameInput
bool ValidatePort(const std::string &port, std::string &errorMsg)
void SetOnJoin(std::function< void(const std::string &, const std::string &, const std::string &)> onJoin)
Set callback triggered when the Join button is clicked.
Abstract interface for graphics rendering operations.
Definition IGraphics.hpp:32
virtual void DrawText(int fontHandle, const char *text, int x, int y, int fontSize, unsigned int color)=0
Draw text using a loaded font.
virtual int GetScreenWidth() const =0
Get the screen width (same as window width)
Abstract factory interface for creating UI elements.
virtual std::unique_ptr< ITextInput > CreateTextInput()=0
Create a text input instance.
virtual std::unique_ptr< IButton > CreateButton()=0
Create a button instance.
@ CENTER_HORIZONTAL
Center on the X axis.