1 #include "lxgui/gui_root.hpp"
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"
15 #define DEBUG_LOG(msg)
20 utils::enable_observer_from_this<
root>(block),
21 frame_container(mgr.get_factory(), object_registry_, observer_from_this()),
23 renderer_(mgr.get_renderer()),
24 world_input_dispatcher_(mgr.get_world_input_dispatcher()) {
26 screen_dimensions_ = window.get_dimensions();
28 connections_.push_back(
29 window.on_window_resized.connect([&](
auto... args) { on_window_resized_(args...); }));
33 connections_.push_back(
35 if (!on_mouse_moved_(args)) {
36 world_input_dispatcher_.on_mouse_moved(args);
40 connections_.push_back(
42 if (!on_mouse_wheel_(args)) {
43 world_input_dispatcher_.on_mouse_wheel(args);
47 connections_.push_back(
49 if (!on_drag_start_(args)) {
50 world_input_dispatcher_.on_mouse_drag_start(args);
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);
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);
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);
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);
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);
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);
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);
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);
118 return vector2f(screen_dimensions_) / get_manager().get_interface_scaling_factor();
121 void root::render()
const {
122 renderer_.set_view(matrix4f::view(get_target_dimensions()));
124 if (caching_enabled_) {
125 renderer_.render_quad(screen_quad_);
127 for (
const auto& s : strata_list_) {
133 void root::create_caching_render_target_() {
136 target_->set_dimensions(screen_dimensions_);
138 target_ = renderer_.create_render_target(screen_dimensions_);
141 <<
"Unable to create render_target for GUI caching: " << e.
get_description()
144 caching_enabled_ =
false;
148 vector2f scaled_dimensions = get_target_dimensions();
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);
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);
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_);
166 strata_obj.target = renderer_.create_render_target(screen_dimensions_);
168 vector2f scaled_dimensions = get_target_dimensions();
170 auto& q = strata_obj.target_quad;
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);
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);
184 void root::update(
float delta) {
186 for (
auto& obj : get_root_frames()) {
193 bool redraw_flag = has_strata_list_changed_();
194 reset_strata_list_changed_flag_();
197 notify_hovered_frame_dirty();
199 if (caching_enabled_) {
200 DEBUG_LOG(
" Redraw strata...");
203 for (
auto& s : strata_list_) {
206 create_strata_cache_render_target_(s);
209 renderer_.begin(s.target);
212 get_manager().get_interface_scaling_factor();
214 renderer_.set_view(matrix4f::view(view));
216 s.target->clear(color::empty);
225 s.redraw_flag =
false;
229 create_caching_render_target_();
231 if (redraw_flag && target_) {
232 renderer_.begin(target_);
235 get_manager().get_interface_scaling_factor();
237 renderer_.set_view(matrix4f::view(view));
239 target_->clear(color::empty);
241 for (
auto&
strata : strata_list_) {
242 renderer_.render_quad(
strata.target_quad);
249 <<
"Unable to create render_target for strata: " << e.
get_description()
252 caching_enabled_ =
false;
257 void root::toggle_caching() {
258 caching_enabled_ = !caching_enabled_;
260 if (caching_enabled_) {
261 for (
auto& s : strata_list_)
262 s.redraw_flag =
true;
266 void root::enable_caching(
bool enable) {
267 if (caching_enabled_ != enable)
271 bool root::is_caching_enabled()
const {
272 return caching_enabled_;
275 void root::notify_scaling_factor_updated() {
276 for (
auto& obj : get_root_frames()) {
277 obj.notify_scaling_factor_updated();
281 create_caching_render_target_();
283 for (
auto& s : strata_list_) {
285 create_strata_cache_render_target_(s);
289 void root::update_hovered_frame_() {
290 const auto mouse_pos = get_manager().get_input_dispatcher().get_mouse_position();
292 utils::observer_ptr<frame> hovered_frame = find_topmost_frame([&](
const frame& obj) {
296 set_hovered_frame_(std::move(hovered_frame), mouse_pos);
299 void root::notify_hovered_frame_dirty() {
300 update_hovered_frame_();
303 void root::start_moving(
304 utils::observer_ptr<region> obj,
307 std::function<
void()> apply_constraint_func) {
308 sized_object_ =
nullptr;
309 moved_object_ = std::move(obj);
310 mouse_movement_ = vector2f::zero;
314 apply_constraint_func_ = std::move(apply_constraint_func);
317 movement_start_position_ = moved_anchor_->
offset;
319 const bounds2f borders = moved_object_->get_borders();
321 moved_object_->clear_all_anchors();
322 moved_object_->set_anchor(point::top_left,
"", borders.
top_left());
324 moved_anchor_ = &moved_object_->modify_anchor(point::top_left);
326 movement_start_position_ = borders.
top_left();
331 void root::stop_moving() {
332 moved_object_ =
nullptr;
333 moved_anchor_ =
nullptr;
336 bool root::is_moving(
const region& obj)
const {
337 return moved_object_.get() == &obj;
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;
346 const bounds2f borders = sized_object_->get_borders();
348 point opposite_point = point::center;
352 case point::top_left:
354 opposite_point = point::bottom_right;
356 is_resizing_from_right_ =
false;
357 is_resizing_from_bottom_ =
false;
359 case point::top_right:
361 opposite_point = point::bottom_left;
363 is_resizing_from_right_ =
true;
364 is_resizing_from_bottom_ =
false;
366 case point::bottom_right:
368 opposite_point = point::top_left;
370 is_resizing_from_right_ =
true;
371 is_resizing_from_bottom_ =
true;
373 case point::bottom_left:
375 opposite_point = point::top_right;
377 is_resizing_from_right_ =
false;
378 is_resizing_from_bottom_ =
true;
382 <<
"Cannot resize \"" << sized_object_->get_name() <<
"\" from its center."
384 sized_object_ =
nullptr;
388 sized_object_->clear_all_anchors();
389 sized_object_->set_anchor(opposite_point,
"", point::top_left, offset);
391 resize_start_ = sized_object_->get_apparent_dimensions();
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;
400 is_resizing_width_ =
true;
401 is_resizing_height_ =
true;
406 void root::stop_sizing() {
407 sized_object_ =
nullptr;
410 bool root::is_sizing(
const region& obj)
const {
411 return sized_object_.get() == &obj;
419 auto iter =
utils::find_if(list, [&](
const auto& ptr) {
return ptr.get() == &receiver; });
421 if (iter == list.end())
429 std::remove_if(list.begin(), list.end(), [](
const auto& ptr) { return ptr == nullptr; });
431 list.erase(end_iter, list.end());
435 utils::observer_ptr<frame> receiver, std::vector<utils::observer_ptr<frame>>& list) {
436 auto* raw_pointer = receiver.get();
444 list.push_back(std::move(receiver));
447 void root::request_focus(utils::observer_ptr<frame> receiver) {
448 auto old_focus = get_focused_frame();
450 auto new_focus = get_focused_frame();
452 if (old_focus != new_focus) {
454 old_focus->notify_focus(
false);
457 new_focus->notify_focus(
true);
461 void root::release_focus(
const frame& receiver) {
462 auto old_focus = get_focused_frame();
464 auto new_focus = get_focused_frame();
466 if (old_focus != new_focus) {
468 old_focus->notify_focus(
false);
471 new_focus->notify_focus(
true);
475 void root::clear_focus() {
476 auto old_focus = get_focused_frame();
477 focus_stack_.clear();
480 old_focus->notify_focus(
false);
483 bool root::is_focused()
const {
484 return get_focused_frame() !=
nullptr;
487 utils::observer_ptr<const frame> root::get_focused_frame()
const {
496 void root::clear_hovered_frame_() {
497 hovered_frame_ =
nullptr;
500 void root::set_hovered_frame_(utils::observer_ptr<frame> obj,
const vector2f& mouse_pos) {
501 if (obj == hovered_frame_)
504 auto old_hovered_frame = hovered_frame_;
505 hovered_frame_ = std::move(obj);
507 if (old_hovered_frame) {
508 old_hovered_frame->notify_mouse_in_frame(
false, mouse_pos);
511 if (hovered_frame_) {
512 hovered_frame_->notify_mouse_in_frame(
true, mouse_pos);
516 void root::on_window_resized_(
const vector2ui& dimensions) {
518 screen_dimensions_ = dimensions;
521 for (
auto& frame : get_root_frames()) {
522 frame.notify_borders_need_update();
523 frame.notify_renderer_need_redraw();
528 create_caching_render_target_();
530 for (
auto& strata : strata_list_) {
532 create_strata_cache_render_target_(strata);
535 notify_hovered_frame_dirty();
538 bool root::on_mouse_moved_(
const input::mouse_moved_data& args) {
539 notify_hovered_frame_dirty();
541 if (moved_object_ || sized_object_) {
542 DEBUG_LOG(
" Moved object...");
543 mouse_movement_ += args.motion;
547 switch (constraint_) {
548 case constraint::none:
549 moved_anchor_->offset = movement_start_position_ + mouse_movement_;
552 moved_anchor_->offset = movement_start_position_ +
vector2f(mouse_movement_.x, 0.0f);
555 moved_anchor_->offset = movement_start_position_ +
vector2f(0.0f, mouse_movement_.y);
560 if (apply_constraint_func_)
561 apply_constraint_func_();
566 moved_object_->notify_borders_need_update();
567 }
else if (sized_object_) {
569 if (is_resizing_from_right_)
570 width = std::max(0.0f, resize_start_.x + mouse_movement_.x);
572 width = std::max(0.0f, resize_start_.x - mouse_movement_.x);
575 if (is_resizing_from_bottom_)
576 height = std::max(0.0f, resize_start_.y + mouse_movement_.y);
578 height = std::max(0.0f, resize_start_.y - mouse_movement_.y);
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);
588 if (dragged_frame_) {
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);
597 if (hovered_frame_) {
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);
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();
618 data.add(args.motion);
619 data.add(args.position.x);
620 data.add(args.position.y);
621 hovered_frame->fire_script(
"OnMouseWheel", data);
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();
634 if (!hovered_frame) {
639 if (
auto* reg = hovered_frame->get_title_region().get();
640 reg && reg->is_in_region(args.position)) {
641 hovered_frame->start_moving();
646 if (hovered_frame->is_drag_enabled(button_name)) {
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);
653 dragged_frame_ = std::move(hovered_frame);
654 dragged_frame_->fire_script(
"OnDragStart", data);
660 bool root::on_drag_stop_(
const input::mouse_drag_stop_data& args) {
664 if (dragged_frame_) {
665 dragged_frame_->fire_script(
"OnDragStop");
666 dragged_frame_ =
nullptr;
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();
673 if (!hovered_frame) {
680 if (hovered_frame->is_drag_enabled(button_name)) {
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);
687 hovered_frame->fire_script(
"OnReceiveDrag", data);
693 bool root::on_text_entered_(
const input::text_entered_data& args) {
694 if (
auto focus = get_focused_frame()) {
696 data.add(utils::unicode_to_utf8(utils::ustring(1, args.character)));
697 data.add(args.character);
699 focus->fire_script(
"OnChar", data);
718 if (is_shift_pressed)
719 name.append(
"Shift-");
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();
733 std::string key_name = get_key_name(key_id, is_shift_pressed, is_ctrl_pressed, is_alt_pressed);
736 utils::observer_ptr<frame> topmost_frame = get_focused_frame();
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);
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);
756 topmost_frame->fire_script(
"OnKeyRepeat", data);
758 topmost_frame->fire_script(
"OnKeyDown", data);
761 topmost_frame->fire_script(
"OnKeyUp", data);
767 if (is_down && !is_repeat) {
770 if (get_key_binder().on_key_down(
771 key_id, is_shift_pressed, is_ctrl_pressed, is_alt_pressed)) {
774 }
catch (
const std::exception& e) {
775 std::string err = e.what();
777 get_manager().get_event_emitter().fire_event(
"LUA_ERROR", {err});
786 bool root::on_mouse_button_state_changed_(
789 bool is_double_click,
791 const vector2f& mouse_pos) {
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();
797 if (is_down && !is_double_click) {
798 if (!hovered_frame || hovered_frame != get_focused_frame())
803 start_click_frame_ = hovered_frame;
806 if (!hovered_frame) {
812 data.add(
static_cast<std::underlying_type_t<input::key>
>(button_id));
814 data.add(mouse_pos.x);
815 data.add(mouse_pos.y);
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())
823 hovered_frame->fire_script(
"OnMouseDown", data);
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;
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::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.
Root of the UI object hierarchy.
root(utils::control_block &block, manager &mgr)
Constructor.
manager & get_manager()
Returns the manager instance associated with this root.
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)
void release_focus_to_list(const frame &receiver, std::vector< utils::observer_ptr< frame >> &list)
std::string get_key_name(input::key key_id, bool is_shift_pressed, bool is_ctrl_pressed, bool is_alt_pressed)
range_impl::reverse_range< T > reverse(T &container)
Reverse traversal.
auto find_if(C &v, T &&f)
vector2< T > bottom_left() const noexcept
vector2< T > bottom_right() const noexcept
vector2< T > top_right() const noexcept
vector2< T > top_left() const noexcept