lxgui
Loading...
Searching...
No Matches
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
17namespace lxgui::gui {
18
19root::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
112root::~root() {
113 // Must be done before we destroy the registry
114 clear_frames_();
115}
116
117vector2f root::get_target_dimensions() const {
118 return vector2f(screen_dimensions_) / get_manager().get_interface_scaling_factor();
119}
120
121void 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
133void 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
162void 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
184void 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
257void 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
266void root::enable_caching(bool enable) {
267 if (caching_enabled_ != enable)
268 toggle_caching();
269}
270
271bool root::is_caching_enabled() const {
272 return caching_enabled_;
273}
274
275void 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
289void 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
299void root::notify_hovered_frame_dirty() {
300 update_hovered_frame_();
301}
302
303void 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
331void root::stop_moving() {
332 moved_object_ = nullptr;
333 moved_anchor_ = nullptr;
334}
335
336bool root::is_moving(const region& obj) const {
337 return moved_object_.get() == &obj;
338}
339
340void 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
406void root::stop_sizing() {
407 sized_object_ = nullptr;
408}
409
410bool root::is_sizing(const region& obj) const {
411 return sized_object_.get() == &obj;
412}
413
414void 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
447void 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
461void 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
475void 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
483bool root::is_focused() const {
484 return get_focused_frame() != nullptr;
485}
486
487utils::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
496void root::clear_hovered_frame_() {
497 hovered_frame_ = nullptr;
498}
499
500void 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
516void 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
538bool 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
611bool 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
629bool 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
660bool 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
693bool 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
707std::string
708get_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
727bool 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
786bool 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.
A region that can contain other regions and react to events.
bool is_in_region(const vector2f &position) const override
Checks if the provided coordinates are inside this frame.
bool is_mouse_move_enabled() const
Checks if this frame can receive mouse movement input.
Manages the user interface.
const input::window & get_window() const
Returns the window in which this gui is being displayed.
const input::dispatcher & get_input_dispatcher() const
Returns the input manager associated to this gui.
The base class of all elements in the GUI.
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)
std::ostream out
void release_focus_to_list(const frame &receiver, std::vector< utils::observer_ptr< frame > > &list)
Definition gui_root.cpp:414
void request_focus_to_list(utils::observer_ptr< frame > receiver, std::vector< utils::observer_ptr< frame > > &list)
Definition gui_root.cpp:434
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.
range_impl::reverse_range< T > reverse(T &container)
Reverse traversal.
auto find_if(C &v, T &&f)
Definition utils_std.hpp:19
vector2< T > bottom_left() const noexcept
vector2< T > bottom_right() const noexcept
vector2< T > top_right() const noexcept
vector2< T > top_left() const noexcept
Data for on_mouse_drag_start signal.
Data for on_mouse_moved signal.
Data for on_mouse_wheel signal.