Unravel Engine C++ Reference
Loading...
Searching...
No Matches
ui_system.cpp
Go to the documentation of this file.
1#include "ui_system.h"
8#include "glm/ext/scalar_constants.hpp"
9#include "glm/gtc/epsilon.hpp"
10
11
12#include <engine/events.h>
13#include <engine/input/input.h>
17
18#include <logging/logging.h>
19
20
21#include <RmlUi/Core/Context.h>
22#include <RmlUi/Core/Core.h>
23#include <RmlUi/Core/ElementDocument.h>
24#include <RmlUi/Core/FileInterface.h>
25#include <RmlUi/Core/StreamMemory.h>
26#include <RmlUi/Core/StyleSheetContainer.h>
27#include <RmlUi/Debugger/Debugger.h>
28#include <RmlUi/Core.h>
29
30#include <engine/ecs/ecs.h>
31#include <engine/engine.h>
33
34#include <string_utils/utils.h>
36
37
38namespace unravel
39{
40
41
43{
44 APPLOG_TRACE("{}::{}", hpp::type_name_str(*this), __func__);
45
46 // Connect to engine events
47 auto& ev = ctx.get_cached<events>();
48 ev.on_frame_update.connect(sentinel_, 500, this, &ui_system::on_frame_update);
49 // ev.on_frame_render.connect(sentinel_, -100, this, &ui_system::on_frame_render); // Render UI last
50 ev.on_os_event.connect(sentinel_, 100, this, &ui_system::on_os_event);
51
52 // Initialize RmlUi backend
53 if(!RmlUi_Backend_Engine::initialize(ctx, "UnravelEngine UI"))
54 {
55 APPLOG_ERROR("Failed to initialize RmlUi backend");
56 return false;
57 }
58
59 // Create main UI context
60 // Get viewport size from renderer
61 int width = 1024, height = 768;
62 if(ctx.has<renderer>())
63 {
64 const auto& rend = ctx.get_cached<renderer>();
65 const auto& window = rend.get_main_window();
66 if(window)
67 {
68 auto size = window->get_window().get_size();
69 width = static_cast<int>(size.w);
70 height = static_cast<int>(size.h);
71 }
72 }
73
74 // Create RmlUi context
75 ui_context_ = Rml::CreateContext("main", Rml::Vector2i(width, height));
76 if(!ui_context_)
77 {
78 APPLOG_ERROR("Failed to create RmlUi context");
80 return false;
81 }
82
83 // auto primary_display = os::display::get_primary_display_index();
84 // auto scale = os::display::get_content_scale(primary_display);
85 // ui_context_->SetDensityIndependentPixelRatio(scale);
86
87 Rml::Debugger::Initialise(ui_context_);
88
89 APPLOG_INFO("UI system initialized successfully ({}x{})", width, height);
90
91 // Register component callbacks
92 register_component_callbacks(ctx);
93
94 // Load test UI document
95 load_fonts();
96
97 return true;
98}
99
101{
102 APPLOG_TRACE("{}::{}", hpp::type_name_str(*this), __func__);
103
104 // Remove RmlUi context
105 if(ui_context_)
106 {
107
108 auto& ecs_system = ctx.get_cached<ecs>();
109 auto& scene = ecs_system.get_scene();
110 auto& registry = *scene.registry;
111
112 // Iterate over all entities with ui_document_component
113 auto view = registry.view<ui_document_component>();
114
115 for(auto entity : view)
116 {
117 auto& ui_comp = view.get<ui_document_component>(entity);
118 if(ui_comp.document)
119 {
120 ui_comp.document->Close();
121 ui_comp.document = nullptr;
122 }
123 }
124
125
126 Rml::RemoveContext("main");
127 ui_context_ = nullptr;
128 }
129
130 Rml::Debugger::Shutdown();
131
132 // Shutdown RmlUi backend
134
135 APPLOG_INFO("UI system deinitialized successfully");
136 return true;
137}
138
140{
141 if(!ui_context_)
142 {
143 return;
144 }
145
146 APP_SCOPE_PERF("UI/System Update");
147
148 // Process all UI document components
149 update_ui_document_components(ctx);
150
151 // Update RmlUi context
152 ui_context_->Update();
153}
154
156{
157 if(!ui_context_)
158 {
159 return;
160 }
161
162 APP_SCOPE_PERF("UI/System Render");
163
164 // Begin UI rendering
166
167 // Render RmlUi context
168 ui_context_->Render();
169
171}
172
173
174auto ui_system::get_context() -> Rml::Context*
175{
176 return ui_context_;
177}
178
179
181{
182 if(!ui_context_)
183 {
184 return;
185 }
186
187 // Get engine window for event processing
188 if(!ctx.has<renderer>())
189 {
190 return;
191 }
192
193 // Forward event to RmlUi
194 if(RmlEngine::input_event_handler(ui_context_, event))
195 {
196 if(event.type == os::events::mouse_button || event.type == os::events::mouse_motion)
197 {
198 auto hover_element = ui_context_->GetHoverElement();
199 if(!is_root_element(ctx, hover_element))
200 {
201 event = {};
202 }
203 }
204 }
205
206 if(event.type == os::events::key_down)
207 {
208 if(event.key.code == os::key::code::f2)
209 {
210 bool new_visible = !Rml::Debugger::IsVisible();
211 Rml::Debugger::SetVisible(new_visible);
212 }
213 }
214
215 if(event.type == os::events::display_content_scale_changed)
216 {
217 // const auto& rend = ctx.get_cached<renderer>();
218 // const auto& window = rend.get_main_window();
219 // auto scale = window->get_window().get_display_scale();
220 // ui_context_->SetDensityIndependentPixelRatio(scale);
221
222 }
223
224 if(event.type == os::events::window)
225 {
226 if(event.window.type == os::window_event_id::size_changed)
227 {
228 // const auto& rend = ctx.get_cached<renderer>();
229 // const auto& window = rend.get_main_window();
230 // auto scale = window->get_window().get_display_scale();
231 // ui_context_->SetDensityIndependentPixelRatio(scale);
232 }
233 }
234}
235
236auto ui_system::is_root_element(rtti::context& ctx, Rml::Element* element) -> bool
237{
238 if(!ui_context_)
239 {
240 return false;
241 }
242
243 if(!element)
244 {
245 return false;
246 }
247
248 if(element->GetTagName() == "#root")
249 {
250 return true;
251 }
252
253 auto& ecs_system = ctx.get_cached<ecs>();
254 auto& scene = ecs_system.get_scene();
255 auto& registry = *scene.registry;
256
257 // Iterate over all entities with ui_document_component
258 auto view = registry.view<ui_document_component>();
259
260 for(auto entity : view)
261 {
262 auto& ui_comp = view.get<ui_document_component>(entity);
263 if(ui_comp.document == element)
264 {
265 return true;
266 }
267 }
268 return false;
269}
270
272{
273 if(!ui_context_)
274 {
275 return;
276 }
277
278
279 auto load_font = [&](const std::string& path) -> void
280 {
281 const Rml::String font_path = fs::resolve_protocol(path).string();
282 Rml::LoadFontFace(font_path, false);
283 };
284
285 // Load font
286 load_font("engine:/data/fonts/Inter/static/28pt/Inter-Thin.ttf");
287 load_font("engine:/data/fonts/Inter/static/28pt/Inter-ThinItalic.ttf");
288 load_font("engine:/data/fonts/Inter/static/28pt/Inter-ExtraLight.ttf");
289 load_font("engine:/data/fonts/Inter/static/28pt/Inter-ExtraLightItalic.ttf");
290 load_font("engine:/data/fonts/Inter/static/28pt/Inter-Light.ttf");
291 load_font("engine:/data/fonts/Inter/static/28pt/Inter-LightItalic.ttf");
292 load_font("engine:/data/fonts/Inter/static/28pt/Inter-Regular.ttf");
293 load_font("engine:/data/fonts/Inter/static/28pt/Inter-Italic.ttf");
294 load_font("engine:/data/fonts/Inter/static/28pt/Inter-Medium.ttf");
295 load_font("engine:/data/fonts/Inter/static/28pt/Inter-MediumItalic.ttf");
296 load_font("engine:/data/fonts/Inter/static/28pt/Inter-SemiBold.ttf");
297 load_font("engine:/data/fonts/Inter/static/28pt/Inter-SemiBoldItalic.ttf");
298 load_font("engine:/data/fonts/Inter/static/28pt/Inter-Bold.ttf");
299 load_font("engine:/data/fonts/Inter/static/28pt/Inter-BoldItalic.ttf");
300 load_font("engine:/data/fonts/Inter/static/28pt/Inter-ExtraBold.ttf");
301 load_font("engine:/data/fonts/Inter/static/28pt/Inter-ExtraBoldItalic.ttf");
302 load_font("engine:/data/fonts/Inter/static/28pt/Inter-Black.ttf");
303 load_font("engine:/data/fonts/Inter/static/28pt/Inter-BlackItalic.ttf");
304
305}
306
307void ui_system::on_create_component(entt::registry& r, entt::entity e)
308{
309}
310
311void ui_system::on_destroy_component(entt::registry& r, entt::entity e)
312{
313 auto& component = r.get<ui_document_component>(e);
314 if(component.document)
315 {
316 component.document->Close();
317 component.document = nullptr;
318 }
319}
320
322{
323 // This method would register ECS callbacks for ui_document_component
324 // creation and destruction. Implementation depends on your ECS system's
325 // callback mechanism. For now, this is a placeholder for the architecture.
326 APPLOG_INFO("UI component callbacks registered");
327}
328
329void ui_system::update_ui_document_components(rtti::context& ctx)
330{
331 if(!ctx.has<ecs>())
332 {
333 return;
334 }
335
336
337 auto& ev = ctx.get_cached<events>();
338
339 // if(ev.is_playing)
340 {
341 auto& input = ctx.get_cached<input_system>();
342
343
344 auto mouse_delta_x = input.manager.get_mouse().get_axis_value(0);
345 auto mouse_delta_y = input.manager.get_mouse().get_axis_value(1);
346
347 if(math::epsilonNotEqual(mouse_delta_x, 0.0f, math::epsilon<float>()) || math::epsilonNotEqual(mouse_delta_y, 0.0f, math::epsilon<float>()))
348 {
349 auto mouse_x = input.manager.get_mouse().get_position().x;
350 auto mouse_y = input.manager.get_mouse().get_position().y;
351 ui_context_->ProcessMouseMove(mouse_x, mouse_y, 0);
352 }
353 }
354
355
356 auto& ecs_system = ctx.get_cached<ecs>();
357 auto& scene = ecs_system.get_scene();
358 auto& registry = *scene.registry;
359
360 registry.view<camera_component>().each(
361 [&](auto e, auto&& camera)
362 {
363 auto viewport = camera.get_viewport_size();
364 RmlUi_Backend_Engine::set_viewport(viewport.width, viewport.height);
365 ui_context_->SetDimensions(Rml::Vector2i(viewport.width, viewport.height));
366
367
368 });
369
370 // Iterate over all entities with ui_document_component
371 auto view = registry.view<ui_document_component>();
372
373 for(auto entity : view)
374 {
375 auto& ui_comp = view.get<ui_document_component>(entity);
376
377 bool active = registry.all_of<active_component>(entity);
378
379 if(!ev.is_playing)
380 {
381 if(ui_comp.version != ui_comp.asset.version())
382 {
383 if(ui_comp.document)
384 {
385 ui_comp.document->Close();
386 ui_comp.document = nullptr;
387 }
388 }
389 }
390
391 // Skip if no document path is specified
392 if(!ui_comp.asset)
393 {
394 continue;
395 }
396
397 // Load document if not already loaded
398 if(!ui_comp.is_loaded())
399 {
400 load_ui_document(ui_comp);
401 }
402
403 if(!ui_comp.document)
404 {
405 continue;
406 }
407
408 bool active_and_enabled = active && ui_comp.is_enabled();
409 if(active_and_enabled)
410 {
411 if(!ui_comp.document->IsVisible())
412 {
413 ui_comp.document->Show();
414 }
415 }
416 else
417 {
418 if( ui_comp.document->IsVisible())
419 {
420 ui_comp.document->Hide();
421 }
422 }
423
424 }
425}
426
427auto ui_system::load_ui_document(ui_document_component& component) -> bool
428{
429 if(!ui_context_)
430 {
431 APPLOG_ERROR("Cannot load UI document: RmlUi context not available");
432 return false;
433 }
434
435 auto asset = component.asset.get();
436 if(!asset)
437 {
438 APPLOG_WARNING("Cannot load document: document_path is empty");
439 return false;
440 }
441
442 // auto compiled_path = resolve_compiled_key(component.asset.id());
443
444 auto real = fs::resolve_protocol(component.asset.id());
445
446 Rml::String url_safe_path = Rml::StringUtilities::Replace(real.string(), ':', '|');
447
448 // Load the document using the existing load_document method
449 auto raw_document = ui_context_->LoadDocumentFromMemory(asset->content, url_safe_path);
450 if(!raw_document)
451 {
452 APPLOG_ERROR("Failed to load UI document: {}", component.asset.id());
453 return false;
454 }
455 raw_document->ReloadStyleSheet();
456
457 raw_document->SetId("body");
458
459 if(component.document)
460 {
461 component.document->Close();
462 }
463 // Create shared_ptr with custom deleter that properly closes the document
464 component.document = raw_document;
465 component.version = component.asset.version();
466
467 return true;
468}
469
470} // namespace unravel
event_type event
std::chrono::duration< float > delta_t
#define APPLOG_WARNING(...)
Definition logging.h:19
#define APPLOG_ERROR(...)
Definition logging.h:20
#define APPLOG_INFO(...)
Definition logging.h:18
#define APPLOG_TRACE(...)
Definition logging.h:17
path resolve_protocol(const path &_path)
Given the specified path/filename, resolve the final full filename. This will be based on either the ...
auto input_event_handler(Rml::Context *context, const os::event &event) -> bool
Process ospp event and forward to RmlUi context.
void set_viewport(int width, int height)
Update viewport size (call on window resize)
auto initialize(rtti::context &ctx, const char *window_name, int width, int height) -> bool
Initialize the RmlUi backend with engine context.
void shutdown()
Shutdown the RmlUi backend and cleanup resources.
void begin_frame()
Prepare render state for RmlUi rendering Call this before rendering RmlUi context.
void present_frame(const gfx::frame_buffer::ptr &framebuffer)
Present the rendered frame Call this after rendering RmlUi context.
#define APP_SCOPE_PERF(name)
Create a scoped performance timer that only accepts string literals.
Definition profiler.h:160
entt::entity entity
auto get_cached() -> T &
Definition context.hpp:49
auto has() const -> bool
Definition context.hpp:28
Manages the entity-component-system (ECS) operations for the ACE framework.
Definition ecs.h:12
hpp::event< void(rtti::context &, delta_t)> on_frame_update
Definition events.h:16
auto get_main_window() const -> render_window *
Definition renderer.cpp:230
Represents a scene in the ACE framework, managing entities and their relationships.
Definition scene.h:21
std::unique_ptr< entt::registry > registry
The registry that manages all entities in the scene.
Definition scene.h:117
Component that holds a reference to a UI document for RmlUi rendering.
void on_os_event(rtti::context &ctx, os::event &event)
Handle OS events and forward to RmlUi.
auto init(rtti::context &ctx) -> bool
Initializes the UI system with the given context.
Definition ui_system.cpp:42
static void on_create_component(entt::registry &r, entt::entity e)
void on_frame_update(rtti::context &ctx, delta_t dt)
Update UI system (called every frame)
void on_frame_render(const gfx::frame_buffer::ptr &output, delta_t dt)
Render UI (called every frame after update)
auto get_context() -> Rml::Context *
Get the main RmlUi context.
static void on_destroy_component(entt::registry &r, entt::entity e)
auto deinit(rtti::context &ctx) -> bool
Deinitializes the UI system with the given context.
void register_component_callbacks(rtti::context &ctx)
Register component creation/destruction callbacks with ECS.
void load_fonts()
Load fonts.