Air-Trap 1.0.0
A multiplayer R-Type clone game engine built with C++23 and ECS architecture
Loading...
Searching...
No Matches
UIRenderSystem.cpp
Go to the documentation of this file.
1/*
2** EPITECH PROJECT, 2025
3** Air-Trap
4** File description:
5**
6 * UIRenderSystem.cpp
7*/
8
10
11#include "RType/ECS/ZipView.hpp"
12#include "RType/Logger.hpp"
13
15{
16
18 sf::RenderWindow &window)
19 : _registry(registry)
20 , _window(window)
21 {
22 }
23
25 {
26 _textures.clear();
27 log::info("UIRenderSystem: Texture cache cleared");
28 }
29
31 {
32 (void)dt;
39 }
40
42 {
43 auto buttonsResult = _registry.get<ecs::components::ui::Button>();
44 if (!buttonsResult)
45 return;
46
47 auto &buttons = buttonsResult.value().get();
48 for (const auto &entity : buttons.entities()) {
49 auto &button = buttons[entity];
50
51 sf::RectangleShape rect(sf::Vector2f(button.size.x, button.size.y));
52 rect.setPosition(
53 sf::Vector2f(button.position.x, button.position.y));
54
55 const uint8_t *color = button.idleColor;
56 if (button.state ==
58 color = button.hoverColor;
59 } else if (button.state ==
61 color = button.pressedColor;
62 }
63
64 rect.setFillColor(sf::Color(color[0], color[1], color[2]));
65 rect.setOutlineColor(sf::Color::White);
66 rect.setOutlineThickness(2.0f);
67
68 _window.draw(rect);
69
70 try {
71 sf::Font &font = loadFont("assets/fonts/main.ttf");
72 sf::Text text(font);
73 text.setString(sf::String::fromUtf8(button.text.begin(),
74 button.text.end()));
75 text.setCharacterSize(24);
76
77 sf::FloatRect textBounds = text.getLocalBounds();
78 text.setOrigin(sf::Vector2f(textBounds.size.x / 2.0f,
79 textBounds.size.y / 2.0f));
80 text.setPosition(sf::Vector2f(
81 button.position.x + button.size.x / 2.0f,
82 button.position.y + button.size.y / 2.0f - 5.0f));
83 text.setFillColor(sf::Color::White);
84
85 _window.draw(text);
86 } catch (const std::exception &e) {
87 log::error("Failed to render button text: {}", e.what());
88 }
89 }
90 }
91
93 {
94 auto textsResult = _registry.get<ecs::components::ui::Text>();
95 if (!textsResult)
96 return;
97
98 auto &texts = textsResult.value().get();
99 for (const auto &entity : texts.entities()) {
100 auto &textComp = texts[entity];
101
102 try {
103 sf::Font &font = loadFont(textComp.fontPath);
104 sf::Text text(font);
105 text.setString(sf::String::fromUtf8(textComp.content.begin(),
106 textComp.content.end()));
107 text.setCharacterSize(textComp.fontSize);
108 text.setPosition(
109 sf::Vector2f(textComp.position.x, textComp.position.y));
110 text.setFillColor(sf::Color(textComp.red, textComp.green,
111 textComp.blue, textComp.alpha));
112
113 _window.draw(text);
114 } catch (const std::exception &e) {
115 log::error("Failed to render text '{}': {}", textComp.content,
116 e.what());
117 }
118 }
119 }
120
121 sf::Font &UIRenderSystem::loadFont(const std::string &fontPath)
122 {
123 auto it = _fonts.find(fontPath);
124 if (it != _fonts.end()) {
125 return it->second;
126 }
127
128 sf::Font font;
129 if (!font.openFromFile(fontPath)) {
130 throw std::runtime_error("Failed to load font: " + fontPath);
131 }
132
133 _fonts[fontPath] = std::move(font);
134 return _fonts[fontPath];
135 }
136
138 {
139 auto slidersResult = _registry.get<ecs::components::ui::Slider>();
140 if (!slidersResult)
141 return;
142
143 auto &sliders = slidersResult.value().get();
144 for (const auto &entity : sliders.entities()) {
145 auto &slider = sliders[entity];
146
147 sf::RectangleShape track(
148 sf::Vector2f(slider.size.x, slider.size.y));
149 track.setPosition(
150 sf::Vector2f(slider.position.x, slider.position.y));
151 track.setFillColor(sf::Color(slider.trackColor[0],
152 slider.trackColor[1],
153 slider.trackColor[2]));
154 _window.draw(track);
155
156 float fillWidth = slider.size.x * slider.getNormalized();
157 sf::RectangleShape fill(sf::Vector2f(fillWidth, slider.size.y));
158 fill.setPosition(
159 sf::Vector2f(slider.position.x, slider.position.y));
160 fill.setFillColor(sf::Color(
161 slider.fillColor[0], slider.fillColor[1], slider.fillColor[2]));
162 _window.draw(fill);
163
164 float handleX = slider.position.x + fillWidth - 5.0f;
165 sf::RectangleShape handle(
166 sf::Vector2f(10.0f, slider.size.y + 8.0f));
167 handle.setPosition(sf::Vector2f(handleX, slider.position.y - 4.0f));
168 handle.setFillColor(sf::Color(slider.handleColor[0],
169 slider.handleColor[1],
170 slider.handleColor[2]));
171 _window.draw(handle);
172 }
173 }
174
176 {
177 auto dropdownsResult =
179 if (!dropdownsResult)
180 return;
181
182 auto &dropdowns = dropdownsResult.value().get();
183
184 for (const auto &entity : dropdowns.entities()) {
185 auto &dropdown = dropdowns[entity];
186
187 sf::RectangleShape button(
188 sf::Vector2f(dropdown.size.x, dropdown.size.y));
189 button.setPosition(
190 sf::Vector2f(dropdown.position.x, dropdown.position.y));
191 button.setFillColor(sf::Color(
192 dropdown.bgColor[0], dropdown.bgColor[1], dropdown.bgColor[2]));
193 button.setOutlineColor(sf::Color::White);
194 button.setOutlineThickness(2.0f);
195 _window.draw(button);
196
197 try {
198 sf::Font &font = loadFont("assets/fonts/main.ttf");
199 std::string selectedText = dropdown.getSelected();
200 sf::Text text(font);
201 text.setString(sf::String::fromUtf8(selectedText.begin(),
202 selectedText.end()));
203 text.setCharacterSize(20);
204 text.setPosition(sf::Vector2f(dropdown.position.x + 10.0f,
205 dropdown.position.y + 8.0f));
206 text.setFillColor(sf::Color(dropdown.textColor[0],
207 dropdown.textColor[1],
208 dropdown.textColor[2]));
209 _window.draw(text);
210
211 sf::Text arrow(font);
212 arrow.setString(dropdown.isOpen ? "^" : "v");
213 arrow.setCharacterSize(16);
214 arrow.setPosition(
215 sf::Vector2f(dropdown.position.x + dropdown.size.x - 30.0f,
216 dropdown.position.y + 10.0f));
217 arrow.setFillColor(sf::Color::White);
218 _window.draw(arrow);
219 } catch (const std::exception &e) {
220 log::error("Failed to render dropdown: {}", e.what());
221 }
222 }
223
224 for (const auto &entity : dropdowns.entities()) {
225 auto &dropdown = dropdowns[entity];
226
227 if (dropdown.isOpen) {
228 try {
229 sf::Font &font = loadFont("assets/fonts/main.ttf");
230 float optionY = dropdown.position.y + dropdown.size.y;
231
232 for (size_t i = 0; i < dropdown.options.size(); ++i) {
233 sf::RectangleShape optionBg(
234 sf::Vector2f(dropdown.size.x, dropdown.size.y));
235 optionBg.setPosition(
236 sf::Vector2f(dropdown.position.x, optionY));
237
238 if (static_cast<int>(i) == dropdown.hoveredIndex) {
239 optionBg.setFillColor(sf::Color(
240 dropdown.hoverColor[0], dropdown.hoverColor[1],
241 dropdown.hoverColor[2]));
242 } else {
243 optionBg.setFillColor(sf::Color(
244 dropdown.bgColor[0], dropdown.bgColor[1],
245 dropdown.bgColor[2]));
246 }
247 optionBg.setOutlineColor(sf::Color::White);
248 optionBg.setOutlineThickness(1.0f);
249 _window.draw(optionBg);
250
251 sf::Text optionText(font);
252 optionText.setString(
253 sf::String::fromUtf8(dropdown.options[i].begin(),
254 dropdown.options[i].end()));
255 optionText.setCharacterSize(20);
256 optionText.setPosition(sf::Vector2f(
257 dropdown.position.x + 10.0f, optionY + 8.0f));
258 optionText.setFillColor(sf::Color::White);
259 _window.draw(optionText);
260
261 optionY += dropdown.size.y;
262 }
263 } catch (const std::exception &e) {
264 log::error("Failed to render dropdown options: {}",
265 e.what());
266 }
267 }
268 }
269 }
270
272 {
273 auto inputsResult =
275 if (!inputsResult)
276 return;
277
278 auto &inputs = inputsResult.value().get();
279
280 for (const auto &entity : inputs.entities()) {
281 auto &input = inputs[entity];
282
283 if (input.isFocused) {
284 input.blinkTimer += dt;
285 if (input.blinkTimer >= input.blinkInterval) {
286 input.blinkTimer = 0.0f;
287 input.showCursor = !input.showCursor;
288 }
289 } else {
290 input.showCursor = false;
291 input.blinkTimer = 0.0f;
292 }
293
294 sf::RectangleShape box(sf::Vector2f(input.size.x, input.size.y));
295 box.setPosition(sf::Vector2f(input.position.x, input.position.y));
296 box.setFillColor(sf::Color(input.bgColor[0], input.bgColor[1],
297 input.bgColor[2], input.alpha));
298
299 const uint8_t *bc =
300 input.isFocused ? input.focusBorderColor : input.borderColor;
301 box.setOutlineColor(sf::Color(bc[0], bc[1], bc[2], input.alpha));
302 box.setOutlineThickness(2.0f);
303
304 _window.draw(box);
305
306 try {
307 sf::Font &font = loadFont(input.fontPath);
308
309 const bool empty = input.value.empty();
310 std::string display =
311 empty ? input.placeholder : input.getDisplayValue();
312
313 sf::Text text(font);
314 text.setString(
315 sf::String::fromUtf8(display.begin(), display.end()));
316 text.setCharacterSize(input.fontSize);
317 text.setPosition(sf::Vector2f(input.position.x + 10.0f,
318 input.position.y + 8.0f));
319
320 if (empty) {
321 text.setFillColor(sf::Color(
322 input.placeholderColor[0], input.placeholderColor[1],
323 input.placeholderColor[2], input.alpha));
324 } else {
325 text.setFillColor(
326 sf::Color(input.textColor[0], input.textColor[1],
327 input.textColor[2], input.alpha));
328 }
329
330 _window.draw(text);
331
332 if (input.isFocused && input.showCursor) {
333 sf::FloatRect bounds = text.getLocalBounds();
334 float cursorX =
335 input.position.x + 10.0f + bounds.size.x + 2.0f;
336 float cursorY = input.position.y + 10.0f;
337
338 sf::RectangleShape cursor(
339 sf::Vector2f(2.0f, static_cast<float>(input.fontSize)));
340 cursor.setPosition(sf::Vector2f(cursorX, cursorY));
341 cursor.setFillColor(sf::Color(255, 255, 255, input.alpha));
342 _window.draw(cursor);
343 }
344 } catch (const std::exception &e) {
345 log::error("Failed to render TextInput: {}", e.what());
346 }
347 }
348 }
349
351 {
352 auto spritesResult =
354 if (!spritesResult)
355 return;
356
357 auto &sprites = spritesResult.value().get();
358 for (const auto &entity : sprites.entities()) {
359 auto &spritePreview = sprites[entity];
360
361 try {
362 sf::Texture &texture = loadTexture(spritePreview.texturePath);
363 sf::Sprite sprite(texture);
364
365 // Custom sprites (from assets/sprites/custom/) use the full
366 // image Sprite sheet sprites need texture rect extraction
367 bool isCustomSprite =
368 spritePreview.texturePath.find("custom/") !=
369 std::string::npos;
370
371 if (!isCustomSprite) {
372 // Set texture rect to extract the specific part of the
373 // sprite sheet
374 sprite.setTextureRect(sf::IntRect(
375 sf::Vector2i(spritePreview.rectLeft,
376 spritePreview.rectTop),
377 sf::Vector2i(spritePreview.rectWidth,
378 spritePreview.rectHeight)));
379
380 // Debug log for sprite sheet extraction
381 if (spritePreview.texturePath.find("r-typesheet1.gif") !=
382 std::string::npos &&
383 spritePreview.rectLeft == 0 &&
384 spritePreview.rectTop == 0) {
385 log::info(
386 "Rendering Player Ship sprite: rect({},{}) "
387 "size({},{})",
388 spritePreview.rectLeft, spritePreview.rectTop,
389 spritePreview.rectWidth, spritePreview.rectHeight);
390 }
391 }
392 // Otherwise use the full texture (for custom sprites)
393
394 // Apply scale and position
395 sprite.setScale(sf::Vector2f(spritePreview.scale,
396 spritePreview.scale));
397 sprite.setPosition(
398 sf::Vector2f(spritePreview.x, spritePreview.y));
399
400 _window.draw(sprite);
401 } catch (const std::exception &e) {
402 log::error("Failed to render sprite preview '{}': {}",
403 spritePreview.texturePath, e.what());
404 }
405 }
406 }
407
408 sf::Texture &UIRenderSystem::loadTexture(const std::string &texturePath)
409 {
410 auto it = _textures.find(texturePath);
411 if (it != _textures.end()) {
412 return it->second;
413 }
414
415 sf::Texture texture;
416 if (!texture.loadFromFile(texturePath)) {
417 throw std::runtime_error("Failed to load texture: " + texturePath);
418 }
419
420 _textures[texturePath] = std::move(texture);
421 return _textures[texturePath];
422 }
423
424} // namespace rtp::client::systems
Logger declaration with support for multiple log levels.
void update(float dt) override
Update system logic for one frame.
std::unordered_map< std::string, sf::Font > _fonts
sf::Texture & loadTexture(const std::string &texturePath)
std::unordered_map< std::string, sf::Texture > _textures
sf::Font & loadFont(const std::string &fontPath)
UIRenderSystem(ecs::Registry &registry, sf::RenderWindow &window)
auto get(this const Self &self) -> std::expected< std::reference_wrapper< ConstLike< Self, SparseArray< T > > >, rtp::Error >
void error(LogFmt< std::type_identity_t< Args >... > fmt, Args &&...args) noexcept
Log an error message.
void info(LogFmt< std::type_identity_t< Args >... > fmt, Args &&...args) noexcept
Log an informational message.
Component representing a clickable button.
Definition Button.hpp:30
Component for dropdown menu selection.
Definition Dropdown.hpp:21
Component for a draggable slider control.
Definition Slider.hpp:19
Component for displaying a sprite preview in the UI.
Component representing text to render.
Definition Text.hpp:19