Unravel Engine C++ Reference
Loading...
Searching...
No Matches
ui_system_input.cpp
Go to the documentation of this file.
1#include "ui_system.h"
4
5#include <engine/ecs/ecs.h>
10
11#include <RmlUi/Core/Context.h>
12#include <RmlUi/Core/Element.h>
13#include <RmlUi/Core/ElementDocument.h>
14
15#include <RmlUi/Debugger/Debugger.h>
16
17namespace unravel
18{
19namespace
20{
21 auto is_keyboard_event(const os::event& event) -> bool
22 {
23 switch(event.type)
24 {
25 case os::events::key_down:
26 case os::events::key_up:
27 case os::events::text_input:
28 return true;
29 default:
30 return false;
31 }
32 }
33
34 auto is_release_event(const os::event& event) -> bool
35 {
36 switch(event.type)
37 {
38 case os::events::key_up:
39 return true;
40 case os::events::mouse_button:
41 return event.button.state_id == os::state::released;
42 default:
43 return false;
44 }
45 }
46
47 auto is_pointer_tracking_event(const os::event& event) -> bool
48 {
49 switch(event.type)
50 {
51 case os::events::mouse_button:
52 case os::events::mouse_motion:
53 case os::events::mouse_wheel:
54 return true;
55 default:
56 return false;
57 }
58 }
59
60 auto context_wants_keyboard_input(Rml::Context* context) -> bool
61 {
62 if(!context)
63 {
64 return false;
65 }
66
67 Rml::Element* focus = context->GetFocusElement();
68 if(!focus || focus == context->GetRootElement())
69 {
70 return false;
71 }
72
73 Rml::Element* leaf = focus->GetFocusLeafNode();
74 if(!leaf || leaf == context->GetRootElement())
75 {
76 return false;
77 }
78
79 const Rml::String& tag = leaf->GetTagName();
80 return tag == "input" || tag == "textarea" || tag == "select";
81 }
82
83 void blur_all_document_contexts(scene& scn, const Rml::Context* except, const ui_system& system)
84 {
85 scn.registry->view<ui_document_component>().each(
86 [&](entt::entity, ui_document_component& ui_comp)
87 {
88 if(ui_comp.context && ui_comp.context != except)
89 {
90 system.blur_ui_context(ui_comp.context);
91 }
92 });
93 }
94} // namespace
95
97{
98 auto& scene = ctx.get_cached<ecs>().get_scene();
99 const bool input_allowed = ctx.get_cached<input_system>().manager.is_input_allowed();
100
101 if(input_allowed && is_keyboard_event(event))
102 {
104 {
105 event = {};
106 return;
107 }
108 }
109 else if(input_allowed)
110 {
112 apply_mouse_press_focus(scene, event, pointer.target_context, input_allowed);
113 }
114}
115
116auto ui_system::find_keyboard_context(scene& scn) const -> Rml::Context*
117{
118 if(Rml::Debugger::IsVisible() && debug_context_ && context_wants_keyboard_input(debug_context_))
119 {
120 return debug_context_;
121 }
122
123 Rml::Context* keyboard_context = nullptr;
124 scn.registry->view<ui_document_component, active_component>().each(
125 [&](entt::entity, ui_document_component& ui_comp, active_component&)
126 {
127 if(ui_comp.context && ui_comp.is_enabled() && context_wants_keyboard_input(ui_comp.context))
128 {
129 keyboard_context = ui_comp.context;
130 }
131 });
132 return keyboard_context;
133}
134
136{
137 Rml::Context* keyboard_context = find_keyboard_context(scn);
138 if(!keyboard_context)
139 {
140 return false;
141 }
142
143 // Block game keyboard while a text field is focused, but always let releases through
144 // so keys/buttons pressed before UI focus cannot get stuck in the engine.
145 (void)RmlEngine::input_event_handler(keyboard_context, event);
146 return !is_release_event(event);
147}
148
151{
153
154 if(is_pointer_tracking_event(event))
155 {
156 refresh_mouse_state(ctx, scn);
157 }
158
159 if(Rml::Debugger::IsVisible() && debug_context_)
160 {
161 if(process_event(scn, debug_context_, event))
162 {
163 result.target_context = debug_context_;
164 }
165 }
166
167 scn.registry->view<ui_document_component, active_component>().each(
168 [&](entt::entity, ui_document_component& ui_comp, active_component&)
169 {
170 if(ui_comp.context && ui_comp.is_enabled())
171 {
172 if(process_event(scn, ui_comp.context, event))
173 {
174 result.target_context = ui_comp.context;
175 }
176 }
177 });
178
179 return result;
180}
181
183 os::event& event,
184 Rml::Context* target_context,
185 bool input_allowed)
186{
187 if(event.type != os::events::mouse_button)
188 {
189 return;
190 }
191
192 if(event.button.state_id == os::state::released)
193 {
194 // Always propagate mouse releases to the engine to clear pressed states.
195 return;
196 }
197
198 if(event.button.state_id != os::state::pressed)
199 {
200 return;
201 }
202
203 if(!target_context && input_allowed)
204 {
205 blur_ui_context(debug_context_);
206 scn.registry->view<ui_document_component>().each(
207 [&](entt::entity, ui_document_component& ui_comp)
208 {
209 blur_ui_context(ui_comp.context);
210 });
211 return;
212 }
213
214 if(target_context || !input_allowed)
215 {
216 const Rml::Context* except = (input_allowed && target_context) ? target_context : nullptr;
217 blur_all_document_contexts(scn, except, *this);
218
219 if(target_context)
220 {
221 event = {};
222 }
223 }
224}
225
226auto ui_system::is_not_root_element(scene& scn, Rml::Element* element) -> bool
227{
228 if(!element)
229 {
230 return false;
231 }
232 if(element->GetTagName() == "#root")
233 {
234 return false;
235 }
236
237 auto view = scn.registry->view<ui_document_component>();
238 for(auto entity : view)
239 {
240 auto& ui_comp = view.get<ui_document_component>(entity);
241 if(static_cast<Rml::Element*>(ui_comp.document) == element)
242 {
243 return false;
244 }
245 }
246 return true;
247}
248
249auto ui_system::process_mouse_move(scene& scn, Rml::Context* context, int x, int y) -> bool
250{
251 if(!context->ProcessMouseMove(x, y, 0))
252 {
253 auto hover_element = context->GetHoverElement();
254 if(is_not_root_element(scn, hover_element))
255 {
256 if(debug_target_context_ != context)
257 {
258 debug_target_context_ = context;
259 Rml::Debugger::SetContext(context);
260 }
261
262 return true;
263 }
264 }
265 return false;
266}
267
268auto ui_system::process_event(scene& scn, Rml::Context* context, os::event& event) -> bool
269{
270 if(!context)
271 {
272 return false;
273 }
274 const bool propagate = RmlEngine::input_event_handler(context, event);
275 if(!propagate)
276 {
277 if(is_keyboard_event(event))
278 {
279 return true;
280 }
281 }
282 if(propagate)
283 {
284 if(event.type == os::events::mouse_button || event.type == os::events::mouse_motion)
285 {
286 auto hover_element = context->GetHoverElement();
287 if(is_not_root_element(scn, hover_element))
288 {
289 return true;
290 }
291 }
292 }
293 return false;
294}
295
296void ui_system::blur_ui_context(Rml::Context* context) const
297{
298 if(!context)
299 {
300 return;
301 }
302
303 context->ProcessMouseLeave();
304
305 Rml::Element* focus = context->GetFocusElement();
306 if(focus && focus != context->GetRootElement())
307 {
308 focus->Blur();
309 }
310}
311
313{
314 auto& input = ctx.get_cached<input_system>();
315 if(!input.manager.is_input_allowed())
316 {
317 return;
318 }
319
320 const int mouse_x = static_cast<int>(input.manager.get_mouse().get_position().x);
321 const int mouse_y = static_cast<int>(input.manager.get_mouse().get_position().y);
322
323 const camera* scene_camera = nullptr;
324 entt::handle camera_entity;
325 scn.registry->view<camera_component, active_component>().each(
326 [&](entt::entity entity, camera_component&, active_component&)
327 {
328 if(!scene_camera)
329 {
330 camera_entity = scn.create_handle(entity);
331 scene_camera = &camera_entity.get<camera_component>().get_camera();
332 }
333 });
334
335 if(debug_context_)
336 {
337 process_mouse_move(scn, debug_context_, mouse_x, mouse_y);
338 }
339
341 [&](entt::entity entity, ui_document_component& ui_comp, active_component&)
342 {
343 if(!ui_comp.context || !ui_comp.is_enabled())
344 {
345 return;
346 }
347
349 {
350 process_mouse_move(scn, ui_comp.context, mouse_x, mouse_y);
351 return;
352 }
353
354 if(!scene_camera)
355 {
356 ui_comp.context->ProcessMouseLeave();
357 return;
358 }
359
360 auto handle = scn.create_handle(entity);
361 auto& transform = handle.get<transform_component>();
362 const auto scale = ui_comp.get_world_space_scale();
363 const auto quad_transform = transform.get_transform_global() * math::transform::scaling(scale);
364 math::vec2 pixel;
365 if(scene_camera->project_to_quad(math::vec2(static_cast<float>(mouse_x), static_cast<float>(mouse_y)),
366 quad_transform, ui_comp.size.width, ui_comp.size.height, pixel))
367 {
368 process_mouse_move(scn, ui_comp.context, static_cast<int>(pixel.x), static_cast<int>(pixel.y));
369 }
370 else
371 {
372 ui_comp.context->ProcessMouseLeave();
373 }
374 });
375}
376
377} // namespace unravel
event_type event
static auto scaling(const vec2_t &scale) noexcept -> transform_t
Create a scaling transform.
Class that contains core camera data, used for rendering and other purposes.
Class representing a camera. Contains functionality for manipulating and updating a camera....
Definition camera.h:62
auto project_to_quad(const math::vec2 &viewport_point, const math::transform &quad_transform, uint32_t quad_width, uint32_t quad_height, math::vec2 &pixel_out) const -> bool
Raycasts from viewport point onto a quad plane and returns pixel coordinates.
Definition camera.cpp:582
Component that handles transformations (position, rotation, scale, etc.) in the ACE framework.
float y
float x
uint16_t view
std::string tag
Definition hub.cpp:32
const aiScene * scene
auto input_event_handler(Rml::Context *context, const os::event &event) -> bool
Process ospp event and forward to RmlUi context.
@ screen_space_overlay
Screen-space overlay (viewport-sized or positioned)
std::vector< float > scale
entt::handle entity
auto get_cached() -> T &
Definition context.hpp:49
T width
Definition basetypes.hpp:55
T height
Definition basetypes.hpp:56
Manages the entity-component-system (ECS) operations for the ACE framework.
Definition ecs.h:12
Represents a scene in the ACE framework, managing entities and their relationships.
Definition scene.h:70
std::unique_ptr< entt::registry > registry
The registry that manages all entities in the scene.
Definition scene.h:187
auto create_handle(entt::entity e) -> entt::handle
Creates an entity in the scene.
Definition scene.cpp:428
Component that holds a reference to a UI document for RmlUi rendering.
auto get_world_space_scale() const -> math::vec2
usize32_t size
Document resolution (pixels). For screen-space: viewport size. For world-space: configurable (e....
auto is_enabled() const -> bool
Check if document is currently enabled.
Rml::Context * context
Per-document RmlUi context (owned by this component)
ui_render_mode render_mode
Rendering mode: screen or world.
void on_os_event(rtti::context &ctx, os::event &event)
Handle OS events and forward to RmlUi.
auto try_consume_keyboard_event(scene &scn, os::event &event) -> bool
void apply_mouse_press_focus(scene &scn, os::event &event, Rml::Context *target_context, bool input_allowed)
auto process_mouse_move(scene &scn, Rml::Context *context, int x, int y) -> bool
auto find_keyboard_context(scene &scn) const -> Rml::Context *
void blur_ui_context(Rml::Context *context) const
auto dispatch_pointer_event(rtti::context &ctx, scene &scn, os::event &event) -> ui_pointer_dispatch_result
void refresh_mouse_state(rtti::context &ctx, scene &scn)
auto is_not_root_element(scene &scn, Rml::Element *element) -> bool
auto process_event(scene &scn, Rml::Context *context, os::event &event) -> bool
gfx::uniform_handle handle
Definition uniform.cpp:9