lxgui
gui_root.cpp
1 #include "lxgui/gui_root.hpp"
2 
3 #include "lxgui/gui_frame.hpp"
4 #include "lxgui/gui_manager.hpp"
5 #include "lxgui/gui_out.hpp"
6 #include "lxgui/gui_registry.hpp"
7 #include "lxgui/gui_renderer.hpp"
8 #include "lxgui/input_dispatcher.hpp"
9 #include "lxgui/input_window.hpp"
10 #include "lxgui/input_world_dispatcher.hpp"
11 #include "lxgui/utils_range.hpp"
12 #include "lxgui/utils_std.hpp"
13 
14 // #define DEBUG_LOG(msg) gui::out << (msg) << std::endl
15 #define DEBUG_LOG(msg)
16 
17 namespace lxgui::gui {
18 
19 root::root(utils::control_block& block, manager& mgr) :
20  utils::enable_observer_from_this<root>(block),
21  frame_container(mgr.get_factory(), object_registry_, observer_from_this()),
22  manager_(mgr),
23  renderer_(mgr.get_renderer()),
24  world_input_dispatcher_(mgr.get_world_input_dispatcher()) {
25  auto& window = get_manager().get_window();
26  screen_dimensions_ = window.get_dimensions();
27 
28  connections_.push_back(
29  window.on_window_resized.connect([&](auto... args) { on_window_resized_(args...); }));
30 
31  auto& input_dispatcher = get_manager().get_input_dispatcher();
32 
33  connections_.push_back(
34  input_dispatcher.on_mouse_moved.connect([&](const input::mouse_moved_data& args) {
35  if (!on_mouse_moved_(args)) {
36  world_input_dispatcher_.on_mouse_moved(args);
37  }
38  }));
39 
40  connections_.push_back(
41  input_dispatcher.on_mouse_wheel.connect([&](const input::mouse_wheel_data& args) {
42  if (!on_mouse_wheel_(args)) {
43  world_input_dispatcher_.on_mouse_wheel(args);
44  }
45  }));
46 
47  connections_.push_back(
48  input_dispatcher.on_mouse_drag_start.connect([&](const input::mouse_drag_start_data& args) {
49  if (!on_drag_start_(args)) {
50  world_input_dispatcher_.on_mouse_drag_start(args);
51  }
52  }));
53 
54  connections_.push_back(
55  input_dispatcher.on_mouse_drag_stop.connect([&](const input::mouse_drag_stop_data& args) {
56  if (!on_drag_stop_(args)) {
57  world_input_dispatcher_.on_mouse_drag_stop(args);
58  }
59  }));
60 
61  connections_.push_back(
62  input_dispatcher.on_text_entered.connect([&](const input::text_entered_data& args) {
63  if (!on_text_entered_(args)) {
64  world_input_dispatcher_.on_text_entered(args);
65  }
66  }));
67 
68  connections_.push_back(
69  input_dispatcher.on_mouse_pressed.connect([&](const input::mouse_pressed_data& args) {
70  if (!on_mouse_button_state_changed_(args.button, true, false, false, args.position)) {
71  world_input_dispatcher_.on_mouse_pressed(args);
72  }
73  }));
74 
75  connections_.push_back(
76  input_dispatcher.on_mouse_released.connect([&](const input::mouse_released_data& args) {
77  if (!on_mouse_button_state_changed_(
78  args.button, false, false, args.was_dragged, args.position)) {
79  world_input_dispatcher_.on_mouse_released(args);
80  }
81  }));
82 
83  connections_.push_back(input_dispatcher.on_mouse_double_clicked.connect(
84  [&](const input::mouse_double_clicked_data& args) {
85  if (!on_mouse_button_state_changed_(args.button, true, true, false, args.position)) {
86  world_input_dispatcher_.on_mouse_double_clicked(args);
87  }
88  }));
89 
90  connections_.push_back(
91  input_dispatcher.on_key_pressed.connect([&](const input::key_pressed_data& args) {
92  if (!on_key_state_changed_(args.key, true, false)) {
93  world_input_dispatcher_.on_key_pressed(args);
94  }
95  }));
96 
97  connections_.push_back(input_dispatcher.on_key_pressed_repeat.connect(
98  [&](const input::key_pressed_repeat_data& args) {
99  if (!on_key_state_changed_(args.key, true, true)) {
100  world_input_dispatcher_.on_key_pressed_repeat(args);
101  }
102  }));
103 
104  connections_.push_back(
105  input_dispatcher.on_key_released.connect([&](const input::key_released_data& args) {
106  if (!on_key_state_changed_(args.key, false, false)) {
107  world_input_dispatcher_.on_key_released(args);
108  }
109  }));
110 }
111 
112 root::~root() {
113  // Must be done before we destroy the registry
114  clear_frames_();
115 }
116 
117 vector2f root::get_target_dimensions() const {
118  return vector2f(screen_dimensions_) / get_manager().get_interface_scaling_factor();
119 }
120 
121 void root::render() const {
122  renderer_.set_view(matrix4f::view(get_target_dimensions()));
123 
124  if (caching_enabled_) {
125  renderer_.render_quad(screen_quad_);
126  } else {
127  for (const auto& s : strata_list_) {
128  render_strata_(s);
129  }
130  }
131 }
132 
133 void root::create_caching_render_target_() {
134  try {
135  if (target_)
136  target_->set_dimensions(screen_dimensions_);
137  else
138  target_ = renderer_.create_render_target(screen_dimensions_);
139  } catch (const utils::exception& e) {
140  gui::out << gui::error << "gui::root: "
141  << "Unable to create render_target for GUI caching: " << e.get_description()
142  << std::endl;
143 
144  caching_enabled_ = false;
145  return;
146  }
147 
148  vector2f scaled_dimensions = get_target_dimensions();
149 
150  screen_quad_.mat = renderer_.create_material(target_);
151  screen_quad_.v[0].pos = vector2f::zero;
152  screen_quad_.v[1].pos = vector2f(scaled_dimensions.x, 0);
153  screen_quad_.v[2].pos = scaled_dimensions;
154  screen_quad_.v[3].pos = vector2f(0, scaled_dimensions.y);
155 
156  screen_quad_.v[0].uvs = screen_quad_.mat->get_canvas_uv(vector2f(0, 0), true);
157  screen_quad_.v[1].uvs = screen_quad_.mat->get_canvas_uv(vector2f(1, 0), true);
158  screen_quad_.v[2].uvs = screen_quad_.mat->get_canvas_uv(vector2f(1, 1), true);
159  screen_quad_.v[3].uvs = screen_quad_.mat->get_canvas_uv(vector2f(0, 1), true);
160 }
161 
162 void root::create_strata_cache_render_target_(strata_data& strata_obj) {
163  if (strata_obj.target)
164  strata_obj.target->set_dimensions(screen_dimensions_);
165  else
166  strata_obj.target = renderer_.create_render_target(screen_dimensions_);
167 
168  vector2f scaled_dimensions = get_target_dimensions();
169 
170  auto& q = strata_obj.target_quad;
171 
172  q.mat = renderer_.create_material(strata_obj.target);
173  q.v[0].pos = vector2f::zero;
174  q.v[1].pos = vector2f(scaled_dimensions.x, 0);
175  q.v[2].pos = scaled_dimensions;
176  q.v[3].pos = vector2f(0, scaled_dimensions.y);
177 
178  q.v[0].uvs = q.mat->get_canvas_uv(vector2f(0, 0), true);
179  q.v[1].uvs = q.mat->get_canvas_uv(vector2f(1, 0), true);
180  q.v[2].uvs = q.mat->get_canvas_uv(vector2f(1, 1), true);
181  q.v[3].uvs = q.mat->get_canvas_uv(vector2f(0, 1), true);
182 }
183 
184 void root::update(float delta) {
185  // Update logics on root frames from parent to children.
186  for (auto& obj : get_root_frames()) {
187  obj.update(delta);
188  }
189 
190  // Removed destroyed frames
191  garbage_collect();
192 
193  bool redraw_flag = has_strata_list_changed_();
194  reset_strata_list_changed_flag_();
195 
196  if (redraw_flag)
197  notify_hovered_frame_dirty();
198 
199  if (caching_enabled_) {
200  DEBUG_LOG(" Redraw strata...");
201 
202  try {
203  for (auto& s : strata_list_) {
204  if (s.redraw_flag) {
205  if (!s.target)
206  create_strata_cache_render_target_(s);
207 
208  if (s.target) {
209  renderer_.begin(s.target);
210 
211  vector2f view = vector2f(s.target->get_canvas_dimensions()) /
212  get_manager().get_interface_scaling_factor();
213 
214  renderer_.set_view(matrix4f::view(view));
215 
216  s.target->clear(color::empty);
217  render_strata_(s);
218 
219  renderer_.end();
220  }
221 
222  redraw_flag = true;
223  }
224 
225  s.redraw_flag = false;
226  }
227 
228  if (!target_)
229  create_caching_render_target_();
230 
231  if (redraw_flag && target_) {
232  renderer_.begin(target_);
233 
234  vector2f view = vector2f(target_->get_canvas_dimensions()) /
235  get_manager().get_interface_scaling_factor();
236 
237  renderer_.set_view(matrix4f::view(view));
238 
239  target_->clear(color::empty);
240 
241  for (auto& strata : strata_list_) {
242  renderer_.render_quad(strata.target_quad);
243  }
244 
245  renderer_.end();
246  }
247  } catch (const utils::exception& e) {
248  gui::out << gui::error << "gui::root: "
249  << "Unable to create render_target for strata: " << e.get_description()
250  << std::endl;
251 
252  caching_enabled_ = false;
253  }
254  }
255 }
256 
257 void root::toggle_caching() {
258  caching_enabled_ = !caching_enabled_;
259 
260  if (caching_enabled_) {
261  for (auto& s : strata_list_)
262  s.redraw_flag = true;
263  }
264 }
265 
266 void root::enable_caching(bool enable) {
267  if (caching_enabled_ != enable)
268  toggle_caching();
269 }
270 
271 bool root::is_caching_enabled() const {
272  return caching_enabled_;
273 }
274 
275 void root::notify_scaling_factor_updated() {
276  for (auto& obj : get_root_frames()) {
277  obj.notify_scaling_factor_updated();
278  }
279 
280  if (target_)
281  create_caching_render_target_();
282 
283  for (auto& s : strata_list_) {
284  if (s.target)
285  create_strata_cache_render_target_(s);
286  }
287 }
288 
289 void root::update_hovered_frame_() {
290  const auto mouse_pos = get_manager().get_input_dispatcher().get_mouse_position();
291 
292  utils::observer_ptr<frame> hovered_frame = find_topmost_frame([&](const frame& obj) {
293  return obj.is_in_region(mouse_pos) && obj.is_mouse_move_enabled();
294  });
295 
296  set_hovered_frame_(std::move(hovered_frame), mouse_pos);
297 }
298 
299 void root::notify_hovered_frame_dirty() {
300  update_hovered_frame_();
301 }
302 
303 void root::start_moving(
304  utils::observer_ptr<region> obj,
305  anchor* a,
307  std::function<void()> apply_constraint_func) {
308  sized_object_ = nullptr;
309  moved_object_ = std::move(obj);
310  mouse_movement_ = vector2f::zero;
311 
312  if (moved_object_) {
313  constraint_ = constraint;
314  apply_constraint_func_ = std::move(apply_constraint_func);
315  if (a) {
316  moved_anchor_ = a;
317  movement_start_position_ = moved_anchor_->offset;
318  } else {
319  const bounds2f borders = moved_object_->get_borders();
320 
321  moved_object_->clear_all_anchors();
322  moved_object_->set_anchor(point::top_left, "", borders.top_left());
323 
324  moved_anchor_ = &moved_object_->modify_anchor(point::top_left);
325 
326  movement_start_position_ = borders.top_left();
327  }
328  }
329 }
330 
331 void root::stop_moving() {
332  moved_object_ = nullptr;
333  moved_anchor_ = nullptr;
334 }
335 
336 bool root::is_moving(const region& obj) const {
337  return moved_object_.get() == &obj;
338 }
339 
340 void root::start_sizing(utils::observer_ptr<region> obj, point p) {
341  moved_object_ = nullptr;
342  sized_object_ = std::move(obj);
343  mouse_movement_ = vector2f::zero;
344 
345  if (sized_object_) {
346  const bounds2f borders = sized_object_->get_borders();
347 
348  point opposite_point = point::center;
349  vector2f offset;
350 
351  switch (p) {
352  case point::top_left:
353  case point::top:
354  opposite_point = point::bottom_right;
355  offset = borders.bottom_right();
356  is_resizing_from_right_ = false;
357  is_resizing_from_bottom_ = false;
358  break;
359  case point::top_right:
360  case point::right:
361  opposite_point = point::bottom_left;
362  offset = borders.bottom_left();
363  is_resizing_from_right_ = true;
364  is_resizing_from_bottom_ = false;
365  break;
366  case point::bottom_right:
367  case point::bottom:
368  opposite_point = point::top_left;
369  offset = borders.top_left();
370  is_resizing_from_right_ = true;
371  is_resizing_from_bottom_ = true;
372  break;
373  case point::bottom_left:
374  case point::left:
375  opposite_point = point::top_right;
376  offset = borders.top_right();
377  is_resizing_from_right_ = false;
378  is_resizing_from_bottom_ = true;
379  break;
380  case point::center:
381  gui::out << gui::error << "gui::manager: "
382  << "Cannot resize \"" << sized_object_->get_name() << "\" from its center."
383  << std::endl;
384  sized_object_ = nullptr;
385  return;
386  }
387 
388  sized_object_->clear_all_anchors();
389  sized_object_->set_anchor(opposite_point, "", point::top_left, offset);
390 
391  resize_start_ = sized_object_->get_apparent_dimensions();
392 
393  if (p == point::left || p == point::right) {
394  is_resizing_width_ = true;
395  is_resizing_height_ = false;
396  } else if (p == point::top || p == point::bottom) {
397  is_resizing_width_ = false;
398  is_resizing_height_ = true;
399  } else {
400  is_resizing_width_ = true;
401  is_resizing_height_ = true;
402  }
403  }
404 }
405 
406 void root::stop_sizing() {
407  sized_object_ = nullptr;
408 }
409 
410 bool root::is_sizing(const region& obj) const {
411  return sized_object_.get() == &obj;
412 }
413 
414 void release_focus_to_list(const frame& receiver, std::vector<utils::observer_ptr<frame>>& list) {
415  if (list.empty())
416  return;
417 
418  // Find receiver in the list
419  auto iter = utils::find_if(list, [&](const auto& ptr) { return ptr.get() == &receiver; });
420 
421  if (iter == list.end())
422  return;
423 
424  // Set it to null
425  *iter = nullptr;
426 
427  // Clean up null entries
428  auto end_iter =
429  std::remove_if(list.begin(), list.end(), [](const auto& ptr) { return ptr == nullptr; });
430 
431  list.erase(end_iter, list.end());
432 }
433 
435  utils::observer_ptr<frame> receiver, std::vector<utils::observer_ptr<frame>>& list) {
436  auto* raw_pointer = receiver.get();
437  if (!raw_pointer)
438  return;
439 
440  // Check if this receiver was already in the focus stack and remove it
441  release_focus_to_list(*raw_pointer, list);
442 
443  // Add receiver at the top of the stack
444  list.push_back(std::move(receiver));
445 }
446 
447 void root::request_focus(utils::observer_ptr<frame> receiver) {
448  auto old_focus = get_focused_frame();
449  request_focus_to_list(std::move(receiver), focus_stack_);
450  auto new_focus = get_focused_frame();
451 
452  if (old_focus != new_focus) {
453  if (old_focus)
454  old_focus->notify_focus(false);
455 
456  if (new_focus)
457  new_focus->notify_focus(true);
458  }
459 }
460 
461 void root::release_focus(const frame& receiver) {
462  auto old_focus = get_focused_frame();
463  release_focus_to_list(receiver, focus_stack_);
464  auto new_focus = get_focused_frame();
465 
466  if (old_focus != new_focus) {
467  if (old_focus)
468  old_focus->notify_focus(false);
469 
470  if (new_focus)
471  new_focus->notify_focus(true);
472  }
473 }
474 
475 void root::clear_focus() {
476  auto old_focus = get_focused_frame();
477  focus_stack_.clear();
478 
479  if (old_focus)
480  old_focus->notify_focus(false);
481 }
482 
483 bool root::is_focused() const {
484  return get_focused_frame() != nullptr;
485 }
486 
487 utils::observer_ptr<const frame> root::get_focused_frame() const {
488  for (const auto& ptr : utils::range::reverse(focus_stack_)) {
489  if (ptr)
490  return ptr;
491  }
492 
493  return nullptr;
494 }
495 
496 void root::clear_hovered_frame_() {
497  hovered_frame_ = nullptr;
498 }
499 
500 void root::set_hovered_frame_(utils::observer_ptr<frame> obj, const vector2f& mouse_pos) {
501  if (obj == hovered_frame_)
502  return;
503 
504  auto old_hovered_frame = hovered_frame_;
505  hovered_frame_ = std::move(obj);
506 
507  if (old_hovered_frame) {
508  old_hovered_frame->notify_mouse_in_frame(false, mouse_pos);
509  }
510 
511  if (hovered_frame_) {
512  hovered_frame_->notify_mouse_in_frame(true, mouse_pos);
513  }
514 }
515 
516 void root::on_window_resized_(const vector2ui& dimensions) {
517  // Update internal window size
518  screen_dimensions_ = dimensions;
519 
520  // Notify all frames anchored to the window edges
521  for (auto& frame : get_root_frames()) {
522  frame.notify_borders_need_update();
523  frame.notify_renderer_need_redraw();
524  }
525 
526  // Resize caching render targets
527  if (target_)
528  create_caching_render_target_();
529 
530  for (auto& strata : strata_list_) {
531  if (strata.target)
532  create_strata_cache_render_target_(strata);
533  }
534 
535  notify_hovered_frame_dirty();
536 }
537 
538 bool root::on_mouse_moved_(const input::mouse_moved_data& args) {
539  notify_hovered_frame_dirty();
540 
541  if (moved_object_ || sized_object_) {
542  DEBUG_LOG(" Moved object...");
543  mouse_movement_ += args.motion;
544  }
545 
546  if (moved_object_) {
547  switch (constraint_) {
548  case constraint::none:
549  moved_anchor_->offset = movement_start_position_ + mouse_movement_;
550  break;
551  case constraint::x:
552  moved_anchor_->offset = movement_start_position_ + vector2f(mouse_movement_.x, 0.0f);
553  break;
554  case constraint::y:
555  moved_anchor_->offset = movement_start_position_ + vector2f(0.0f, mouse_movement_.y);
556  break;
557  default: break;
558  }
559 
560  if (apply_constraint_func_)
561  apply_constraint_func_();
562 
563  // As a result of applying constraints, object may have been deleted,
564  // so check again before use
565  if (moved_object_)
566  moved_object_->notify_borders_need_update();
567  } else if (sized_object_) {
568  float width;
569  if (is_resizing_from_right_)
570  width = std::max(0.0f, resize_start_.x + mouse_movement_.x);
571  else
572  width = std::max(0.0f, resize_start_.x - mouse_movement_.x);
573 
574  float height;
575  if (is_resizing_from_bottom_)
576  height = std::max(0.0f, resize_start_.y + mouse_movement_.y);
577  else
578  height = std::max(0.0f, resize_start_.y - mouse_movement_.y);
579 
580  if (is_resizing_width_ && is_resizing_height_)
581  sized_object_->set_dimensions(vector2f(width, height));
582  else if (is_resizing_width_)
583  sized_object_->set_width(width);
584  else if (is_resizing_height_)
585  sized_object_->set_height(height);
586  }
587 
588  if (dragged_frame_) {
589  event_data data;
590  data.add(args.motion.x);
591  data.add(args.motion.y);
592  data.add(args.position.x);
593  data.add(args.position.y);
594  dragged_frame_->fire_script("OnDragMove", data);
595  }
596 
597  if (hovered_frame_) {
598  event_data data;
599  data.add(args.motion.x);
600  data.add(args.motion.y);
601  data.add(args.position.x);
602  data.add(args.position.y);
603  hovered_frame_->fire_script("OnMouseMove", data);
604  return true;
605  }
606 
607  // Forward to the world
608  return false;
609 }
610 
611 bool root::on_mouse_wheel_(const input::mouse_wheel_data& args) {
612  utils::observer_ptr<frame> hovered_frame = find_topmost_frame([&](const frame& obj) {
613  return obj.is_in_region(args.position) && obj.is_mouse_wheel_enabled();
614  });
615 
616  if (hovered_frame) {
617  event_data data;
618  data.add(args.motion);
619  data.add(args.position.x);
620  data.add(args.position.y);
621  hovered_frame->fire_script("OnMouseWheel", data);
622  return true;
623  }
624 
625  // Forward to the world
626  return false;
627 }
628 
629 bool root::on_drag_start_(const input::mouse_drag_start_data& args) {
630  utils::observer_ptr<frame> hovered_frame = find_topmost_frame([&](const frame& obj) {
631  return obj.is_in_region(args.position) && obj.is_mouse_click_enabled();
632  });
633 
634  if (!hovered_frame) {
635  // Forward to the world
636  return false;
637  }
638 
639  if (auto* reg = hovered_frame->get_title_region().get();
640  reg && reg->is_in_region(args.position)) {
641  hovered_frame->start_moving();
642  }
643 
644  std::string button_name = std::string(input::get_mouse_button_codename(args.button));
645 
646  if (hovered_frame->is_drag_enabled(button_name)) {
647  event_data data;
648  data.add(static_cast<std::underlying_type_t<input::key>>(args.button));
649  data.add(button_name);
650  data.add(args.position.x);
651  data.add(args.position.y);
652 
653  dragged_frame_ = std::move(hovered_frame);
654  dragged_frame_->fire_script("OnDragStart", data);
655  }
656 
657  return true;
658 }
659 
660 bool root::on_drag_stop_(const input::mouse_drag_stop_data& args) {
661  stop_moving();
662  stop_sizing();
663 
664  if (dragged_frame_) {
665  dragged_frame_->fire_script("OnDragStop");
666  dragged_frame_ = nullptr;
667  }
668 
669  utils::observer_ptr<frame> hovered_frame = find_topmost_frame([&](const frame& obj) {
670  return obj.is_in_region(args.position) && obj.is_mouse_click_enabled();
671  });
672 
673  if (!hovered_frame) {
674  // Forward to the world
675  return false;
676  }
677 
678  std::string button_name = std::string(input::get_mouse_button_codename(args.button));
679 
680  if (hovered_frame->is_drag_enabled(button_name)) {
681  event_data data;
682  data.add(static_cast<std::underlying_type_t<input::key>>(args.button));
683  data.add(button_name);
684  data.add(args.position.x);
685  data.add(args.position.y);
686 
687  hovered_frame->fire_script("OnReceiveDrag", data);
688  }
689 
690  return true;
691 }
692 
693 bool root::on_text_entered_(const input::text_entered_data& args) {
694  if (auto focus = get_focused_frame()) {
695  event_data data;
696  data.add(utils::unicode_to_utf8(utils::ustring(1, args.character)));
697  data.add(args.character);
698 
699  focus->fire_script("OnChar", data);
700  return true;
701  }
702 
703  // Forward to the world
704  return false;
705 }
706 
707 std::string
708 get_key_name(input::key key_id, bool is_shift_pressed, bool is_ctrl_pressed, bool is_alt_pressed) {
709  std::string name;
710 
711  if (key_id != input::key::k_lcontrol && key_id != input::key::k_rcontrol &&
712  key_id != input::key::k_lshift && key_id != input::key::k_rshift &&
713  key_id != input::key::k_lmenu && key_id != input::key::k_rmenu) {
714  if (is_ctrl_pressed)
715  name = "Ctrl-";
716  if (is_alt_pressed)
717  name.append("Alt-");
718  if (is_shift_pressed)
719  name.append("Shift-");
720  }
721 
722  name.append(input::get_key_codename(key_id));
723 
724  return name;
725 }
726 
727 bool root::on_key_state_changed_(input::key key_id, bool is_down, bool is_repeat) {
728  const auto& input_dispatcher = get_manager().get_input_dispatcher();
729  bool is_shift_pressed = input_dispatcher.shift_is_pressed();
730  bool is_ctrl_pressed = input_dispatcher.ctrl_is_pressed();
731  bool is_alt_pressed = input_dispatcher.alt_is_pressed();
732 
733  std::string key_name = get_key_name(key_id, is_shift_pressed, is_ctrl_pressed, is_alt_pressed);
734 
735  // First, give priority to the focused frame
736  utils::observer_ptr<frame> topmost_frame = get_focused_frame();
737 
738  // If no focused frame with keyboard enabled, look top-down for a frame that captures this key
739  if (!topmost_frame || !topmost_frame->is_keyboard_enabled()) {
740  topmost_frame = find_topmost_frame([&](const frame& frame) {
741  return frame.is_keyboard_enabled() && frame.is_key_capture_enabled(key_name);
742  });
743  }
744 
745  // If a frame is found, capture input and return
746  if (topmost_frame) {
747  event_data data;
748  data.add(static_cast<std::underlying_type_t<input::key>>(key_id));
749  data.add(is_shift_pressed);
750  data.add(is_ctrl_pressed);
751  data.add(is_alt_pressed);
752  data.add(key_name);
753 
754  if (is_down) {
755  if (is_repeat) {
756  topmost_frame->fire_script("OnKeyRepeat", data);
757  } else {
758  topmost_frame->fire_script("OnKeyDown", data);
759  }
760  } else {
761  topmost_frame->fire_script("OnKeyUp", data);
762  }
763 
764  return true;
765  }
766 
767  if (is_down && !is_repeat) {
768  // If no frame is found, try the key_binder
769  try {
770  if (get_key_binder().on_key_down(
771  key_id, is_shift_pressed, is_ctrl_pressed, is_alt_pressed)) {
772  return true;
773  }
774  } catch (const std::exception& e) {
775  std::string err = e.what();
776  gui::out << gui::error << err << std::endl;
777  get_manager().get_event_emitter().fire_event("LUA_ERROR", {err});
778  return true;
779  }
780  }
781 
782  // Forward to the world
783  return false;
784 }
785 
786 bool root::on_mouse_button_state_changed_(
787  input::mouse_button button_id,
788  bool is_down,
789  bool is_double_click,
790  bool was_dragged,
791  const vector2f& mouse_pos) {
792 
793  utils::observer_ptr<frame> hovered_frame = find_topmost_frame([&](const frame& frame) {
794  return frame.is_in_region(mouse_pos) && frame.is_mouse_click_enabled();
795  });
796 
797  if (is_down && !is_double_click) {
798  if (!hovered_frame || hovered_frame != get_focused_frame())
799  clear_focus();
800  }
801 
802  if (is_down) {
803  start_click_frame_ = hovered_frame;
804  }
805 
806  if (!hovered_frame) {
807  // Forward to the world
808  return false;
809  }
810 
811  event_data data;
812  data.add(static_cast<std::underlying_type_t<input::key>>(button_id));
813  data.add(std::string(input::get_mouse_button_codename(button_id)));
814  data.add(mouse_pos.x);
815  data.add(mouse_pos.y);
816 
817  if (is_double_click) {
818  hovered_frame->fire_script("OnDoubleClick", data);
819  } else if (is_down) {
820  if (auto* top_level = hovered_frame->get_top_level_parent().get())
821  top_level->raise();
822 
823  hovered_frame->fire_script("OnMouseDown", data);
824  } else {
825  data.add(was_dragged);
826  data.add(start_click_frame_ == hovered_frame);
827  hovered_frame->fire_script("OnMouseUp", data);
828  start_click_frame_ = nullptr;
829  }
830 
831  return true;
832 }
833 
834 } // namespace lxgui::gui
Stores a position for a UI region.
Definition: gui_anchor.hpp:97
A region that can contain other regions and react to events.
Definition: gui_frame.hpp:255
bool is_in_region(const vector2f &position) const override
Checks if the provided coordinates are inside this frame.
Definition: gui_frame.cpp:819
bool is_mouse_move_enabled() const
Checks if this frame can receive mouse movement input.
Definition: gui_frame.cpp:843
Manages the user interface.
Definition: gui_manager.hpp:44
const input::dispatcher & get_input_dispatcher() const
Returns the input manager associated to this gui.
const input::window & get_window() const
Returns the window in which this gui is being displayed.
The base class of all elements in the GUI.
Definition: gui_region.hpp:161
Root of the UI object hierarchy.
Definition: gui_root.hpp:39
root(utils::control_block &block, manager &mgr)
Constructor.
Definition: gui_root.cpp:19
manager & get_manager()
Returns the manager instance associated with this root.
Definition: gui_root.hpp:245
const std::string & get_description() const
Returns the message of the exception.
vector2< float > vector2f
Holds 2D coordinates (as floats)
void request_focus_to_list(utils::observer_ptr< frame > receiver, std::vector< utils::observer_ptr< frame >> &list)
Definition: gui_root.cpp:434
std::ostream out
void release_focus_to_list(const frame &receiver, std::vector< utils::observer_ptr< frame >> &list)
Definition: gui_root.cpp:414
const std::string error
Definition: gui_out.cpp:7
std::string get_key_name(input::key key_id, bool is_shift_pressed, bool is_ctrl_pressed, bool is_alt_pressed)
Definition: gui_root.cpp:708
@ k_lcontrol
Enter on main keyboard.
@ k_rshift
/ on main keyboard
@ k_rcontrol
Enter on numeric keypad.
std::string_view get_mouse_button_codename(mouse_button button_id)
Returns a standard English name for the provided mouse button.
Definition: input_keys.cpp:7
std::string_view get_key_codename(key key_id)
Returns a standard English name for the provided key.
Definition: input_keys.cpp:105
range_impl::reverse_range< T > reverse(T &container)
Reverse traversal.
Definition: utils_range.hpp:50
auto find_if(C &v, T &&f)
Definition: utils_std.hpp:19
vector2< T > bottom_left() const noexcept
Definition: gui_bounds2.hpp:44
vector2< T > bottom_right() const noexcept
Definition: gui_bounds2.hpp:40
vector2< T > top_right() const noexcept
Definition: gui_bounds2.hpp:36
vector2< T > top_left() const noexcept
Definition: gui_bounds2.hpp:32
Data for on_mouse_drag_start signal.
Data for on_mouse_moved signal.
Data for on_mouse_wheel signal.