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
194 // Forward event to RmlUi
195 if(RmlEngine::input_event_handler(ui_context_, event))
196 {
197 if(event.type == os::events::mouse_button || event.type == os::events::mouse_motion)
198 {
199 auto hover_element = ui_context_->GetHoverElement();
200 if(!is_root_element(ctx, hover_element))
201 {
202 event = {};
203 }
204 }
205 }
206
207 if(event.type == os::events::key_down)
208 {
209 if(event.key.code == os::key::code::f2)
210 {
211 bool new_visible = !Rml::Debugger::IsVisible();
212 Rml::Debugger::SetVisible(new_visible);
213 }
214 }
215
216 if(event.type == os::events::display_content_scale_changed)
217 {
218 // const auto& rend = ctx.get_cached<renderer>();
219 // const auto& window = rend.get_main_window();
220 // auto scale = window->get_window().get_display_scale();
221 // ui_context_->SetDensityIndependentPixelRatio(scale);
222
223 }
224
225 if(event.type == os::events::window)
226 {
227 if(event.window.type == os::window_event_id::size_changed)
228 {
229 // const auto& rend = ctx.get_cached<renderer>();
230 // const auto& window = rend.get_main_window();
231 // auto scale = window->get_window().get_display_scale();
232 // ui_context_->SetDensityIndependentPixelRatio(scale);
233 }
234 }
235}
236
237auto ui_system::is_root_element(rtti::context& ctx, Rml::Element* element) -> bool
238{
239 if(!ui_context_)
240 {
241 return false;
242 }
243
244 if(!element)
245 {
246 return false;
247 }
248
249 if(element->GetTagName() == "#root")
250 {
251 return true;
252 }
253
254 auto& ecs_system = ctx.get_cached<ecs>();
255 auto& scene = ecs_system.get_scene();
256 auto& registry = *scene.registry;
257
258 // Iterate over all entities with ui_document_component
259 auto view = registry.view<ui_document_component>();
260
261 for(auto entity : view)
262 {
263 auto& ui_comp = view.get<ui_document_component>(entity);
264 if(ui_comp.document == element)
265 {
266 return true;
267 }
268 }
269 return false;
270}
271
272void ui_system::load_font(const std::string& path)
273{
274 const Rml::String font_path = fs::resolve_protocol(path).string();
275 Rml::LoadFontFace(font_path, false);
276 fonts_loaded_.insert(path);
277}
278
280{
281 if(!ui_context_)
282 {
283 return;
284 }
285
286 // Load font
287 load_font("engine:/data/fonts/Inter/static/28pt/Inter-Thin.ttf");
288 load_font("engine:/data/fonts/Inter/static/28pt/Inter-ThinItalic.ttf");
289 load_font("engine:/data/fonts/Inter/static/28pt/Inter-ExtraLight.ttf");
290 load_font("engine:/data/fonts/Inter/static/28pt/Inter-ExtraLightItalic.ttf");
291 load_font("engine:/data/fonts/Inter/static/28pt/Inter-Light.ttf");
292 load_font("engine:/data/fonts/Inter/static/28pt/Inter-LightItalic.ttf");
293 load_font("engine:/data/fonts/Inter/static/28pt/Inter-Regular.ttf");
294 load_font("engine:/data/fonts/Inter/static/28pt/Inter-Italic.ttf");
295 load_font("engine:/data/fonts/Inter/static/28pt/Inter-Medium.ttf");
296 load_font("engine:/data/fonts/Inter/static/28pt/Inter-MediumItalic.ttf");
297 load_font("engine:/data/fonts/Inter/static/28pt/Inter-SemiBold.ttf");
298 load_font("engine:/data/fonts/Inter/static/28pt/Inter-SemiBoldItalic.ttf");
299 load_font("engine:/data/fonts/Inter/static/28pt/Inter-Bold.ttf");
300 load_font("engine:/data/fonts/Inter/static/28pt/Inter-BoldItalic.ttf");
301 load_font("engine:/data/fonts/Inter/static/28pt/Inter-ExtraBold.ttf");
302 load_font("engine:/data/fonts/Inter/static/28pt/Inter-ExtraBoldItalic.ttf");
303 load_font("engine:/data/fonts/Inter/static/28pt/Inter-Black.ttf");
304 load_font("engine:/data/fonts/Inter/static/28pt/Inter-BlackItalic.ttf");
305
306}
307
308void ui_system::on_create_component(entt::registry& r, entt::entity e)
309{
310}
311
312void ui_system::on_destroy_component(entt::registry& r, entt::entity e)
313{
314 auto& component = r.get<ui_document_component>(e);
315 if(component.document)
316 {
317 component.document->Close();
318 component.document = nullptr;
319 }
320}
321
323{
324 // This method would register ECS callbacks for ui_document_component
325 // creation and destruction. Implementation depends on your ECS system's
326 // callback mechanism. For now, this is a placeholder for the architecture.
327 APPLOG_INFO("UI component callbacks registered");
328}
329
330void ui_system::update_ui_document_components(rtti::context& ctx)
331{
332 if(!ctx.has<ecs>())
333 {
334 return;
335 }
336
337
338 auto& ev = ctx.get_cached<events>();
339
340 auto& input = ctx.get_cached<input_system>();
341 if(input.manager.is_input_allowed())
342 {
343 auto mouse_delta_x = input.manager.get_mouse().get_axis_value(0);
344 auto mouse_delta_y = input.manager.get_mouse().get_axis_value(1);
345
346 if(math::epsilonNotEqual(mouse_delta_x, 0.0f, math::epsilon<float>()) || math::epsilonNotEqual(mouse_delta_y, 0.0f, math::epsilon<float>()))
347 {
348 auto mouse_x = input.manager.get_mouse().get_position().x;
349 auto mouse_y = input.manager.get_mouse().get_position().y;
350 ui_context_->ProcessMouseMove(mouse_x, mouse_y, 0);
351 }
352 }
353
354
355 auto& ecs_system = ctx.get_cached<ecs>();
356 auto& scene = ecs_system.get_scene();
357 auto& registry = *scene.registry;
358
359 registry.view<camera_component>().each(
360 [&](auto e, auto&& camera)
361 {
362 auto viewport = camera.get_viewport_size();
363
364
365 auto& input = ctx.get_cached<input_system>();
366 auto work_zone = input.manager.get_work_zone();
367
368 RmlUi_Backend_Engine::set_viewport(viewport.width, viewport.height);
369 ui_context_->SetDimensions(Rml::Vector2i(viewport.width, viewport.height));
370
371
372 // Match UI physical size to window
373 float ratio_x = static_cast<float>( viewport.width) / static_cast<float>(work_zone.w);
374 float ratio_y = static_cast<float>( viewport.height) / static_cast<float>(work_zone.h);
375 ui_context_->SetDensityIndependentPixelRatio((ratio_x + ratio_y) * 0.5f); // usually same ratio both axes
376
377
378 });
379
380 // Iterate over all entities with ui_document_component
381 auto view = registry.view<ui_document_component>();
382
383 for(auto entity : view)
384 {
385 auto& ui_comp = view.get<ui_document_component>(entity);
386
387 bool active = registry.all_of<active_component>(entity);
388
389 if(!ev.is_playing)
390 {
391 if(ui_comp.version != ui_comp.asset.version())
392 {
393 if(ui_comp.document)
394 {
395 ui_comp.document->Close();
396 ui_comp.document = nullptr;
397 }
398 }
399 }
400
401 // Skip if no document path is specified
402 if(!ui_comp.asset)
403 {
404 continue;
405 }
406
407 // Load document if not already loaded
408 if(!ui_comp.is_loaded())
409 {
410 load_ui_document(ui_comp);
411 }
412
413 if(!ui_comp.document)
414 {
415 continue;
416 }
417
418 bool active_and_enabled = active && ui_comp.is_enabled();
419 if(active_and_enabled)
420 {
421 if(!ui_comp.document->IsVisible())
422 {
423 ui_comp.document->Show();
424 }
425 }
426 else
427 {
428 if( ui_comp.document->IsVisible())
429 {
430 ui_comp.document->Hide();
431 }
432 }
433
434 }
435}
436
437auto ui_system::load_ui_document(ui_document_component& component) -> bool
438{
439 if(!ui_context_)
440 {
441 APPLOG_ERROR("Cannot load UI document: RmlUi context not available");
442 return false;
443 }
444
445 auto asset = component.asset.get();
446 if(!asset)
447 {
448 APPLOG_WARNING("Cannot load document: document_path is empty");
449 return false;
450 }
451
452 // auto compiled_path = resolve_compiled_key(component.asset.id());
453
454 auto real = fs::resolve_protocol(component.asset.id());
455
456 Rml::String url_safe_path = Rml::StringUtilities::Replace(real.string(), ':', '|');
457
458 // Load the document using the existing load_document method
459 auto raw_document = ui_context_->LoadDocumentFromMemory(asset->content, url_safe_path);
460 if(!raw_document)
461 {
462 APPLOG_ERROR("Failed to load UI document: {}", component.asset.id());
463 return false;
464 }
465 raw_document->ReloadStyleSheet();
466
467 raw_document->SetId("body");
468
469 if(component.document)
470 {
471 component.document->Close();
472 }
473 // Create shared_ptr with custom deleter that properly closes the document
474 component.document = raw_document;
475 component.version = component.asset.version();
476
477 return true;
478}
479
480} // 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.