lxgui
gui_edit_box.cpp
1 #include "lxgui/gui_edit_box.hpp"
2 
3 #include "lxgui/gui_alive_checker.hpp"
4 #include "lxgui/gui_font_string.hpp"
5 #include "lxgui/gui_localizer.hpp"
6 #include "lxgui/gui_manager.hpp"
7 #include "lxgui/gui_out.hpp"
8 #include "lxgui/gui_quad.hpp"
9 #include "lxgui/gui_region_tpl.hpp"
10 #include "lxgui/gui_texture.hpp"
11 #include "lxgui/input_window.hpp"
12 #include "lxgui/utils_range.hpp"
13 
14 #include <lxgui/extern_sol2_state.hpp>
15 
16 using namespace lxgui::input;
17 
18 namespace lxgui::gui {
19 
20 edit_box::edit_box(utils::control_block& block, manager& mgr, const frame_core_attributes& attr) :
21  frame(block, mgr, attr),
22  carret_timer_(blink_time_, utils::periodic_timer::start_type::first_tick, false) {
23 
24  initialize_(*this, attr);
25 
27 
28  enable_mouse();
31 }
32 
33 bool edit_box::can_use_script(const std::string& script_name) const {
34  return base::can_use_script(script_name) || script_name == "OnCursorChanged" ||
35  script_name == "OnEnterPressed" || script_name == "OnEscapePressed" ||
36  script_name == "OnSpacePressed" || script_name == "OnTabPressed" ||
37  script_name == "OnUpPressed" || script_name == "OnDownPressed" ||
38  script_name == "OnTextChanged" || script_name == "OnTextSet";
39 }
40 
41 void edit_box::copy_from(const region& obj) {
42  base::copy_from(obj);
43 
44  const edit_box* box_obj = down_cast<edit_box>(&obj);
45  if (!box_obj)
46  return;
47 
48  this->set_max_letters(box_obj->get_max_letters());
49  this->set_blink_time(box_obj->get_blink_time());
50  this->set_numeric_only(box_obj->is_numeric_only());
51  this->set_positive_only(box_obj->is_positive_only());
52  this->set_integer_only(box_obj->is_integer_only());
54  this->set_multi_line(box_obj->is_multi_line());
56  this->set_text_insets(box_obj->get_text_insets());
57 
58  if (const font_string* fs = box_obj->get_font_string().get()) {
60  attr.name = fs->get_raw_name();
61  attr.inheritance = {box_obj->get_font_string()};
62 
63  auto fnt = this->create_layered_region<font_string>(fs->get_draw_layer(), std::move(attr));
64 
65  if (fnt) {
66  fnt->set_manually_inherited(true);
67  fnt->notify_loaded();
68  this->set_font_string(fnt);
69  }
70  }
71 }
72 
73 void edit_box::update_(float delta) {
74  alive_checker checker(*this);
75 
76  base::update_(delta);
77  if (!checker.is_alive())
78  return;
79 
80  if (has_focus()) {
81  carret_timer_.update(delta);
82 
83  if (carret_timer_.ticks()) {
84  if (!carret_)
86 
87  if (carret_) {
88  if (carret_->is_shown())
89  carret_->hide();
90  else
91  carret_->show();
92  }
93  }
94  }
95 }
96 
97 void edit_box::fire_script(const std::string& script_name, const event_data& data) {
98  alive_checker checker(*this);
99 
100  // Do not fire OnKeyUp/OnKeyRepeat/OnKeyDown events when typing
101  bool bypass_event = false;
102  if (has_focus() &&
103  (script_name == "OnKeyUp" || script_name == "OnKeyDown" || script_name == "OnKeyRepeat")) {
104  bypass_event = true;
105  }
106  if (!has_focus() && (script_name == "OnChar")) {
107  bypass_event = true;
108  }
109 
110  if (!bypass_event) {
111  base::fire_script(script_name, data);
112  if (!checker.is_alive())
113  return;
114  }
115 
116  if ((script_name == "OnKeyDown" || script_name == "OnKeyRepeat") && has_focus()) {
117  key key_id = data.get<key>(0);
118  bool shift_is_pressed = data.get<bool>(1);
119  bool ctrl_is_pressed = data.get<bool>(2);
120 
121  if (key_id == key::k_return || key_id == key::k_numpadenter) {
122  fire_script("OnEnterPressed");
123  if (!checker.is_alive())
124  return;
125  } else if (key_id == key::k_tab) {
126  fire_script("OnTabPressed");
127  if (!checker.is_alive())
128  return;
129  } else if (key_id == key::k_up) {
130  fire_script("OnUpPressed");
131  if (!checker.is_alive())
132  return;
133  } else if (key_id == key::k_down) {
134  fire_script("OnDownPressed");
135  if (!checker.is_alive())
136  return;
137  } else if (key_id == key::k_space) {
138  fire_script("OnSpacePressed");
139  if (!checker.is_alive())
140  return;
141  } else if (key_id == key::k_escape) {
142  fire_script("OnEscapePressed");
143  if (!checker.is_alive())
144  return;
145  }
146 
147  process_key_(key_id, shift_is_pressed, ctrl_is_pressed);
148 
149  if (!checker.is_alive())
150  return;
151  } else if (script_name == "OnChar" && has_focus()) {
152  std::uint32_t c = data.get<std::uint32_t>(1);
153  if (add_char_(c)) {
154  fire_script("OnTextChanged");
155  if (!checker.is_alive())
156  return;
157 
158  fire_script("OnCursorChanged");
159  if (!checker.is_alive())
160  return;
161  }
162  } else if (script_name == "OnSizeChanged") {
166  } else if (script_name == "OnDragStart") {
168  get_letter_id_at_(vector2f(data.get<float>(2), data.get<float>(3)));
169  } else if (script_name == "OnDragMove") {
170  std::size_t pos = get_letter_id_at_(vector2f(data.get<float>(2), data.get<float>(3)));
171  if (pos != selection_end_pos_) {
172  if (pos != std::numeric_limits<std::size_t>::max()) {
174  iter_carret_pos_ = unicode_text_.begin() + pos;
176  } else {
177  std::size_t temp = selection_start_pos_;
178  unlight_text();
179  selection_start_pos_ = temp;
182  }
183 
184  fire_script("OnCursorChanged");
185  if (!checker.is_alive())
186  return;
187  }
188  } else if (script_name == "OnMouseDown") {
189  set_focus(true);
190  if (!checker.is_alive())
191  return;
192 
193  unlight_text();
194 
195  if (move_carret_at_({data.get<float>(2), data.get<float>(3)})) {
196  fire_script("OnCursorChanged");
197  if (!checker.is_alive())
198  return;
199  }
200  }
201 }
202 
203 void edit_box::set_text(const utils::ustring& content) {
204  if (content == unicode_text_)
205  return;
206 
207  unlight_text();
208  unicode_text_ = content;
209  check_text_();
214 
215  alive_checker checker(*this);
216 
217  fire_script("OnTextSet");
218  if (!checker.is_alive())
219  return;
220 
221  fire_script("OnTextChanged");
222  if (!checker.is_alive())
223  return;
224 
225  fire_script("OnCursorChanged");
226  if (!checker.is_alive())
227  return;
228 }
229 
230 const utils::ustring& edit_box::get_text() const {
231  return unicode_text_;
232 }
233 
236  selection_end_pos_ = 0u;
237  is_text_selected_ = false;
238 
239  if (highlight_)
240  highlight_->hide();
241 }
242 
243 void edit_box::highlight_text(std::size_t start, std::size_t end, bool force_update) {
244  if (!highlight_)
246 
247  if (!highlight_)
248  return;
249 
250  std::size_t left = std::min(start, end);
251  std::size_t right = std::max(start, end);
252 
253  if (selection_start_pos_ != start || selection_end_pos_ != end || force_update) {
254  if (left != right) {
255  is_text_selected_ = true;
256 
257  if (right >= display_pos_ && left < display_pos_ + displayed_text_.size() &&
258  font_string_ && font_string_->get_text_object()) {
259  text* text = font_string_->get_text_object();
260 
261  if (left < display_pos_)
262  left = 0;
263  else
264  left = left - display_pos_;
265 
266  float left_pos = text_insets_.left;
267  if (left < text->get_letter_count())
268  left_pos += text->get_letter_quad(left)[0].pos.x;
269 
271  float right_pos = text_insets_.left;
272  if (right < displayed_text_.size()) {
273  if (right < text->get_letter_count())
274  right_pos += text->get_letter_quad(right)[0].pos.x;
275  } else {
276  right = displayed_text_.size() - 1;
277  if (right < text->get_letter_count())
278  right_pos += text->get_letter_quad(right)[2].pos.x;
279  }
280 
281  highlight_->set_anchor(point::left, name_, vector2f(left_pos, 0));
282  highlight_->set_anchor(point::right, name_, point::left, vector2f(right_pos, 0));
283 
284  highlight_->show();
285  } else
286  highlight_->hide();
287  } else {
288  is_text_selected_ = false;
289  highlight_->hide();
290  }
291  }
292 
293  selection_start_pos_ = start;
294  selection_end_pos_ = end;
295 }
296 
298  if (highlight_color_ == c)
299  return;
300 
301  highlight_color_ = c;
302 
303  if (!highlight_)
305 
306  if (!highlight_)
307  return;
308 
309  highlight_->set_solid_color(highlight_color_);
310 }
311 
312 void edit_box::insert_after_cursor(const utils::ustring& content) {
313  if (content.empty())
314  return;
315 
316  if (is_numeric_only_ && !utils::is_number(content))
317  return;
318 
319  if (unicode_text_.size() + content.size() <= max_letters_) {
320  unlight_text();
321  unicode_text_.insert(iter_carret_pos_, content.begin(), content.end());
322  iter_carret_pos_ += content.size();
323 
327 
328  alive_checker checker(*this);
329  fire_script("OnTextChanged");
330  if (!checker.is_alive())
331  return;
332 
333  fire_script("OnCursorChanged");
334  if (!checker.is_alive())
335  return;
336  }
337 }
338 
339 std::size_t edit_box::get_cursor_position() const {
340  return iter_carret_pos_ - unicode_text_.begin();
341 }
342 
343 void edit_box::set_cursor_position(std::size_t pos) {
344  if (pos == get_cursor_position())
345  return;
346 
347  iter_carret_pos_ = unicode_text_.begin() + pos;
349 
350  alive_checker checker(*this);
351  fire_script("OnCursorChanged");
352  if (!checker.is_alive())
353  return;
354 }
355 
356 void edit_box::set_max_letters(std::size_t max_letters) {
357  if (max_letters == 0) {
358  max_letters_ = std::numeric_limits<std::size_t>::max();
359  return;
360  }
361 
362  if (max_letters_ != max_letters) {
363  max_letters_ = max_letters;
364 
365  const std::size_t carret_pos = iter_carret_pos_ - unicode_text_.begin();
366 
367  if (check_text_()) {
368  bool cursor_changed = false;
369  if (carret_pos > max_letters_) {
374  cursor_changed = true;
375  } else {
376  iter_carret_pos_ = unicode_text_.begin() + carret_pos;
377  }
378 
379  alive_checker checker(*this);
380  fire_script("OnTextChanged");
381  if (!checker.is_alive())
382  return;
383 
384  if (cursor_changed) {
385  fire_script("OnCursorChanged");
386  if (!checker.is_alive())
387  return;
388  }
389  }
390  }
391 }
392 
393 std::size_t edit_box::get_max_letters() const {
394  return max_letters_;
395 }
396 
397 std::size_t edit_box::get_letter_count() const {
398  return unicode_text_.size();
399 }
400 
401 void edit_box::set_blink_time(double blink_time) {
402  if (blink_time_ == blink_time)
403  return;
404 
405  blink_time_ = blink_time;
406  carret_timer_ =
408 }
409 
410 double edit_box::get_blink_time() const {
411  return blink_time_;
412 }
413 
414 void edit_box::set_numeric_only(bool numeric_only) {
415  if (is_numeric_only_ == numeric_only)
416  return;
417 
418  is_numeric_only_ = numeric_only;
419 
420  if (is_numeric_only_ && check_text_()) {
424 
425  alive_checker checker(*this);
426  fire_script("OnTextChanged");
427  if (!checker.is_alive())
428  return;
429 
430  fire_script("OnCursorChanged");
431  if (!checker.is_alive())
432  return;
433  }
434 }
435 
436 void edit_box::set_positive_only(bool positive_only) {
437  if (is_positive_only_ == positive_only)
438  return;
439 
440  is_positive_only_ = positive_only;
441 
446 
447  alive_checker checker(*this);
448  fire_script("OnTextChanged");
449  if (!checker.is_alive())
450  return;
451 
452  fire_script("OnCursorChanged");
453  if (!checker.is_alive())
454  return;
455  }
456 }
457 
458 void edit_box::set_integer_only(bool integer_only) {
459  if (is_integer_only_ == integer_only)
460  return;
461 
462  is_integer_only_ = integer_only;
463 
468 
469  alive_checker checker(*this);
470  fire_script("OnTextChanged");
471  if (!checker.is_alive())
472  return;
473 
474  fire_script("OnCursorChanged");
475  if (!checker.is_alive())
476  return;
477  }
478 }
479 
481  return is_numeric_only_;
482 }
483 
485  return is_positive_only_;
486 }
487 
489  return is_integer_only_;
490 }
491 
493  if (is_password_mode_ == enable)
494  return;
495 
496  is_password_mode_ = enable;
497 
501 }
502 
504  return is_password_mode_;
505 }
506 
507 void edit_box::set_multi_line(bool multi_line) {
508  if (is_multi_line_ == multi_line)
509  return;
510 
511  is_multi_line_ = multi_line;
512 
513  if (font_string_) {
514  font_string_->set_word_wrap_enabled(is_multi_line_);
515  font_string_->set_word_ellipsis_enabled(is_multi_line_);
516  }
517 
518  bool text_changed = check_text_();
519  if (text_changed) {
521  }
522 
526 
527  if (text_changed) {
528  alive_checker checker(*this);
529  fire_script("OnTextChanged");
530  if (!checker.is_alive())
531  return;
532 
533  fire_script("OnCursorChanged");
534  if (!checker.is_alive())
535  return;
536  }
537 }
538 
540  return is_multi_line_;
541 }
542 
543 void edit_box::set_max_history_lines(std::size_t max_history_lines) {
544  if (max_history_lines == 0) {
545  max_history_lines_ = std::numeric_limits<std::size_t>::max();
546  return;
547  }
548 
549  if (max_history_lines_ != max_history_lines) {
550  max_history_lines_ = max_history_lines;
551 
552  if (history_line_list_.size() > max_history_lines_) {
553  history_line_list_.erase(
554  history_line_list_.begin(),
556 
557  current_history_line_ = std::numeric_limits<std::size_t>::max();
558  }
559  }
560 }
561 
562 std::size_t edit_box::get_max_history_lines() const {
563  return max_history_lines_;
564 }
565 
566 void edit_box::add_history_line(const utils::ustring& history_line) {
567  if (is_multi_line_)
568  return;
569 
570  history_line_list_.push_back(history_line);
571 
572  if (history_line_list_.size() > max_history_lines_) {
573  history_line_list_.erase(
574  history_line_list_.begin(),
576  }
577 
578  current_history_line_ = std::numeric_limits<std::size_t>::max();
579 }
580 
581 const std::vector<utils::ustring>& edit_box::get_history_lines() const {
582  return history_line_list_;
583 }
584 
586  history_line_list_.clear();
587  current_history_line_ = std::numeric_limits<std::size_t>::max();
588 }
589 
590 void edit_box::set_arrows_ignored(bool arrows_ignored) {
591  are_arrows_ignored_ = arrows_ignored;
592 }
593 
594 void edit_box::set_text_insets(const bounds2f& insets) {
595  text_insets_ = insets;
596 
597  if (font_string_) {
598  font_string_->clear_all_anchors();
601 
605  }
606 }
607 
609  return text_insets_;
610 }
611 
612 void edit_box::notify_focus(bool focus) {
613  if (has_focus() == focus)
614  return;
615 
616  if (focus) {
617  if (!carret_)
618  create_carret_();
619 
620  if (carret_)
621  carret_->show();
622 
624  } else {
625  if (carret_)
626  carret_->hide();
627 
628  unlight_text();
629  }
630 
631  alive_checker checker(*this);
632  base::notify_focus(focus);
633  if (!checker.is_alive())
634  return;
635 
636  if (check_text_()) {
640 
641  fire_script("OnTextChanged");
642  if (!checker.is_alive())
643  return;
644 
645  fire_script("OnCursorChanged");
646  if (!checker.is_alive())
647  return;
648  }
649 }
650 
653 
654  if (font_string_) {
655  font_string_->notify_scaling_factor_updated();
656  create_carret_();
657  }
658 }
659 
660 void edit_box::set_font_string(utils::observer_ptr<font_string> fstr) {
661  font_string_ = std::move(fstr);
662  if (!font_string_)
663  return;
664 
665  font_string_->set_word_wrap_enabled(is_multi_line_);
666  font_string_->set_word_ellipsis_enabled(is_multi_line_);
667 
668  font_string_->set_dimensions(vector2f(0, 0));
669  font_string_->clear_all_anchors();
670 
673 
674  font_string_->disable_formatting();
675 
676  create_carret_();
677 }
678 
679 void edit_box::set_font(const std::string& font_name, float height) {
681 
682  if (font_string_)
683  font_string_->set_font(font_name, height);
684 
685  create_carret_();
686 }
687 
689  if (font_string_)
690  return;
691 
692  auto fnt = create_layered_region<font_string>(layer::artwork, "$parentFontString");
693  if (!fnt)
694  return;
695 
696  fnt->set_manually_inherited(true);
697  fnt->notify_loaded();
698  set_font_string(fnt);
699 }
700 
702  if (highlight_ || is_virtual())
703  return;
704 
705  auto highlight = create_layered_region<texture>(layer::highlight, "$parentHighlight");
706  if (!highlight)
707  return;
708 
709  highlight->set_manually_inherited(true);
710 
711  highlight->set_anchor(point::top, vector2f(0.0f, text_insets_.top));
712  highlight->set_anchor(point::bottom, vector2f(0.0f, -text_insets_.bottom));
713 
714  highlight->set_solid_color(highlight_color_);
715 
716  highlight->notify_loaded();
718 }
719 
721  if (!font_string_ || !font_string_->get_text_object() || is_virtual())
722  return;
723 
724  if (!carret_) {
725  auto carret = create_layered_region<texture>(layer::highlight, "$parentCarret");
726  if (!carret)
727  return;
728 
729  carret->set_manually_inherited(true);
730 
731  carret->set_anchor(point::center, point::left, vector2f(text_insets_.left - 1.0f, 0.0f));
732 
733  carret->notify_loaded();
734  carret_ = carret;
735  }
736 
737  quad quad = font_string_->get_text_object()->create_letter_quad(U'|');
738  for (std::size_t i = 0; i < 4; ++i)
739  quad.v[i].col = font_string_->get_text_color();
740 
741  carret_->set_quad(quad);
742 
744 }
745 
747  bool modified = false;
748  if (unicode_text_.size() > max_letters_) {
749  unicode_text_.resize(max_letters_);
750  modified = true;
751  }
752 
753  if (is_numeric_only_) {
754  const auto& locale = get_manager().get_localizer().get_locale();
755  if (!utils::is_number(locale, unicode_text_)) {
756  unicode_text_.clear();
757  return true;
758  }
759 
760  if (is_integer_only_ && !utils::is_integer(locale, unicode_text_)) {
761  unicode_text_.clear();
762  return true;
763  }
764 
765  if (is_positive_only_ &&
766  utils::from_string<double>(locale, unicode_text_).value_or(-1.0) < 0.0) {
767  unicode_text_.clear();
768  return true;
769  }
770  }
771 
772  return modified;
773 }
774 
776  if (!font_string_ || !font_string_->get_text_object())
777  return;
778 
779  if (is_password_mode_)
780  displayed_text_ = utils::ustring(unicode_text_.size(), U'*');
781  else
783 
784  if (!is_multi_line_) {
785  text* text_object = font_string_->get_text_object();
786 
787  if (!std::isinf(text_object->get_box_width())) {
788  displayed_text_.erase(0, display_pos_);
789 
790  while (!displayed_text_.empty() &&
791  text_object->get_string_width(displayed_text_) > text_object->get_box_width()) {
792  displayed_text_.erase(displayed_text_.size() - 1, 1);
793  }
794  }
795  } else {
796  // TODO: implement for multiline edit box
797  // https://github.com/cschreib/lxgui/issues/39
798  }
799 }
800 
802  if (!font_string_)
803  return;
804 
805  font_string_->set_text(displayed_text_);
806 
807  if (is_text_selected_)
809 }
810 
812  if (!font_string_ || !font_string_->get_text_object() || !carret_)
813  return;
814 
815  if (unicode_text_.empty()) {
816  point p;
817  float offset = 0.0f;
818 
819  switch (font_string_->get_alignment_x()) {
820  case alignment_x::left:
821  p = point::left;
822  offset = text_insets_.left - 1;
823  break;
824  case alignment_x::center: p = point::center; break;
825  case alignment_x::right:
826  p = point::right;
827  offset = -text_insets_.right - 1;
828  break;
829  default: p = point::left; break;
830  }
831 
832  carret_->set_anchor(point::center, p, vector2f(offset, 0.0f));
833  } else {
834  text* text = font_string_->get_text_object();
835  utils::ustring::iterator iter_display_carret;
836 
837  if (!is_multi_line_) {
838  std::size_t global_pos = iter_carret_pos_ - unicode_text_.begin();
839 
840  if (display_pos_ > global_pos) {
841  // The carret has been positioned before the start of the displayed string
842  float box_width = text->get_box_width();
843  float left_string_max_size = box_width * 0.25f;
844  float left_string_size = 0.0f;
845  utils::ustring left_string;
846 
847  utils::ustring::iterator iter = iter_carret_pos_;
848  while ((iter != unicode_text_.begin()) &&
849  (left_string_size < left_string_max_size)) {
850  --iter;
851  left_string.insert(left_string.begin(), *iter);
852  left_string_size = text->get_string_width(left_string);
853  }
854 
855  display_pos_ = iter - unicode_text_.begin();
858  }
859 
860  std::size_t carret_pos = global_pos - display_pos_;
861  if (carret_pos > displayed_text_.size()) {
862  // The carret has been positioned after the end of the displayed string
863  float box_width = text->get_box_width();
864  float left_string_max_size = box_width * 0.75f;
865  float left_string_size = 0.0f;
866  utils::ustring left_string;
867 
868  utils::ustring::iterator iter = iter_carret_pos_;
869  while ((iter_carret_pos_ != unicode_text_.begin()) &&
870  (left_string_size < left_string_max_size)) {
871  --iter;
872  left_string.insert(left_string.begin(), *iter);
873  left_string_size = text->get_string_width(left_string);
874  }
875 
876  display_pos_ = iter - unicode_text_.begin();
879 
880  carret_pos = global_pos - display_pos_;
881  }
882 
883  iter_display_carret = displayed_text_.begin() + carret_pos;
884  } else {
885  iter_display_carret =
887  }
888 
889  float y_offset = static_cast<float>((text->get_line_count() - 1)) *
891 
892  std::size_t index = iter_display_carret - displayed_text_.begin();
893 
894  float x_offset = text_insets_.left;
895  if (index < displayed_text_.size()) {
896  if (index < text->get_letter_count())
897  x_offset += text->get_letter_quad(index)[0].pos.x;
898  } else {
899  index = displayed_text_.size() - 1;
900  if (index < text->get_letter_count())
901  x_offset += text->get_letter_quad(index)[2].pos.x;
902  }
903 
904  carret_->set_anchor(point::center, point::left, vector2f(x_offset, y_offset));
905  }
906 
908  if (has_focus())
909  carret_->show();
910  else
911  carret_->hide();
912 }
913 
914 bool edit_box::add_char_(char32_t c) {
915  if (is_text_selected_)
916  remove_char_();
917 
919  return false;
920 
922 
926 
927  if (carret_)
928  carret_->show();
929 
931 
932  return true;
933 }
934 
936  if (is_text_selected_) {
938  std::size_t left = std::min(selection_start_pos_, selection_end_pos_);
939  std::size_t right = std::max(selection_start_pos_, selection_end_pos_);
940 
941  unicode_text_.erase(left, right - left);
942 
943  iter_carret_pos_ = unicode_text_.begin() + left;
944  }
945 
946  unlight_text();
947  } else {
948  if (iter_carret_pos_ == unicode_text_.end())
949  return false;
950 
952  }
953 
957 
958  if (carret_)
959  carret_->show();
960 
962 
963  return true;
964 }
965 
966 std::size_t edit_box::get_letter_id_at_(const vector2f& position) const {
967  if (!font_string_ || !font_string_->get_text_object())
968  return std::numeric_limits<std::size_t>::max();
969 
970  if (displayed_text_.empty())
971  return display_pos_;
972 
973  const text* text = font_string_->get_text_object();
974 
975  float local_x = position.x - borders_.left - text_insets_.left;
976  // float local_y = position.y - borders_.top - text_insets_.top;
977 
978  if (!is_multi_line_) {
979  if (position.x < borders_.left + text_insets_.left)
980  return display_pos_;
981  else if (position.x > borders_.right - text_insets_.right)
982  return displayed_text_.size() + display_pos_;
983 
984  std::size_t num_letters =
985  std::min<std::size_t>(text->get_letter_count(), displayed_text_.size());
986 
987  for (std::size_t index = 0u; index < num_letters; ++index) {
988  const auto& quad = text->get_letter_quad(index);
989  if (local_x < 0.5f * (quad[0].pos.x + quad[2].pos.x))
990  return index + display_pos_;
991  }
992 
993  return displayed_text_.size() + display_pos_;
994  } else {
995  // TODO: Implement for multi line edit_box
996  // https://github.com/cschreib/lxgui/issues/39
997  return display_pos_;
998  }
999 }
1000 
1001 bool edit_box::move_carret_at_(const vector2f& position) {
1002  std::size_t pos = get_letter_id_at_(position);
1003  if (pos != std::numeric_limits<std::size_t>::max()) {
1004  iter_carret_pos_ = unicode_text_.begin() + pos;
1006  return true;
1007  } else
1008  return false;
1009 }
1010 
1012  if (forward) {
1013  if (iter_carret_pos_ != unicode_text_.end()) {
1014  ++iter_carret_pos_;
1017 
1018  if (carret_)
1019  carret_->show();
1020 
1021  carret_timer_.zero();
1022 
1023  return true;
1024  } else
1025  return false;
1026  } else {
1027  if (iter_carret_pos_ != unicode_text_.begin()) {
1028  --iter_carret_pos_;
1031 
1032  if (carret_)
1033  carret_->show();
1034 
1035  carret_timer_.zero();
1036 
1037  return true;
1038  } else
1039  return false;
1040  }
1041 }
1042 
1044  if (is_multi_line_) {
1045  // TODO: Implement for multi line edit_box
1046  // https://github.com/cschreib/lxgui/issues/39
1047  return false;
1048  } else {
1049  utils::ustring::iterator iter_old = iter_carret_pos_;
1050 
1051  if (down)
1053  else
1054  iter_carret_pos_ = unicode_text_.begin();
1055 
1056  if (iter_old != iter_carret_pos_) {
1059 
1060  if (carret_)
1061  carret_->show();
1062 
1063  carret_timer_.zero();
1064 
1065  return true;
1066  }
1067 
1068  return false;
1069  }
1070 }
1071 
1072 void edit_box::process_key_(key key_id, bool shift_is_pressed, bool ctrl_is_pressed) {
1073  alive_checker checker(*this);
1074 
1075  if (key_id == key::k_return || key_id == key::k_numpadenter) {
1076  if (is_multi_line_) {
1077  event_data key_event;
1078  key_event.add(std::string("\n"));
1079  fire_script("OnChar", key_event);
1080  if (!checker.is_alive())
1081  return;
1082  }
1083  } else if (key_id == key::k_end) {
1084  std::size_t previous_carret_pos = get_cursor_position();
1086 
1087  if (shift_is_pressed) {
1088  if (is_text_selected_)
1090  else
1091  highlight_text(previous_carret_pos, iter_carret_pos_ - unicode_text_.begin());
1092  } else
1093  unlight_text();
1094 
1095  return;
1096  } else if (key_id == key::k_home) {
1097  std::size_t previous_carret_pos = get_cursor_position();
1098  set_cursor_position(0u);
1099 
1100  if (shift_is_pressed) {
1101  if (is_text_selected_)
1103  else
1104  highlight_text(previous_carret_pos, iter_carret_pos_ - unicode_text_.begin());
1105  } else
1106  unlight_text();
1107 
1108  return;
1109  } else if (key_id == key::k_back || key_id == key::k_delete) {
1110  if (is_text_selected_ || key_id == key::k_delete || move_carret_horizontally_(false)) {
1111  if (remove_char_()) {
1112  fire_script("OnTextChanged");
1113  if (!checker.is_alive())
1114  return;
1115 
1116  fire_script("OnCursorChanged");
1117  if (!checker.is_alive())
1118  return;
1119  }
1120  }
1121  } else if (
1122  key_id == key::k_left || key_id == key::k_right ||
1123  (is_multi_line_ && (key_id == key::k_up || key_id == key::k_down))) {
1124  if (!are_arrows_ignored_) {
1125  const std::size_t previous_carret_pos = iter_carret_pos_ - unicode_text_.begin();
1126 
1127  if (key_id == key::k_left || key_id == key::k_right) {
1128  if (is_text_selected_ && !shift_is_pressed) {
1129  std::size_t offset = 0;
1130  if (key_id == key::k_left)
1131  offset = std::min(selection_start_pos_, selection_end_pos_);
1132  else
1133  offset = std::max(selection_start_pos_, selection_end_pos_);
1134 
1135  iter_carret_pos_ = unicode_text_.begin() + offset;
1137 
1138  fire_script("OnCursorChanged");
1139  if (!checker.is_alive())
1140  return;
1141  } else {
1142  if (move_carret_horizontally_(key_id == key::k_right)) {
1143  fire_script("OnCursorChanged");
1144  if (!checker.is_alive())
1145  return;
1146  }
1147  }
1148  } else {
1149  if (is_multi_line_) {
1150  if (move_carret_vertically_(key_id == key::k_down)) {
1151  fire_script("OnCursorChanged");
1152  if (!checker.is_alive())
1153  return;
1154  }
1155  }
1156  }
1157 
1158  if (shift_is_pressed) {
1159  if (is_text_selected_) {
1160  std::size_t new_end_pos = iter_carret_pos_ - unicode_text_.begin();
1161  if (new_end_pos != selection_start_pos_)
1162  highlight_text(selection_start_pos_, new_end_pos);
1163  else
1164  unlight_text();
1165  } else
1166  highlight_text(previous_carret_pos, iter_carret_pos_ - unicode_text_.begin());
1167  } else
1168  unlight_text();
1169  }
1170  } else if (
1171  !is_multi_line_ && (key_id == key::k_up || key_id == key::k_down) &&
1172  !history_line_list_.empty()) {
1173  if (key_id == key::k_up) {
1174  if (current_history_line_ != 0u) {
1175  if (current_history_line_ == std::numeric_limits<std::size_t>::max())
1177  else
1179 
1181  if (!checker.is_alive())
1182  return;
1183  }
1184  } else {
1185  if (current_history_line_ != std::numeric_limits<std::size_t>::max()) {
1186  if (current_history_line_ + 1 == history_line_list_.size()) {
1187  current_history_line_ = std::numeric_limits<std::size_t>::max();
1188  set_text(U"");
1189  if (!checker.is_alive())
1190  return;
1191  } else {
1194  if (!checker.is_alive())
1195  return;
1196  }
1197  }
1198  }
1199  } else if (key_id == key::k_c && ctrl_is_pressed) {
1201  std::size_t min_pos = std::min(selection_start_pos_, selection_end_pos_);
1202  std::size_t max_pos = std::max(selection_start_pos_, selection_end_pos_);
1203  utils::ustring selected = unicode_text_.substr(min_pos, max_pos - min_pos);
1205  }
1206  } else if (key_id == key::k_v && ctrl_is_pressed) {
1207  bool text_added = false;
1208  for (char32_t c : get_manager().get_window().get_clipboard_content()) {
1209  if (!add_char_(c))
1210  break;
1211 
1212  text_added = true;
1213  }
1214 
1215  if (text_added) {
1216  fire_script("OnTextChanged");
1217  if (!checker.is_alive())
1218  return;
1219 
1220  fire_script("OnCursorChanged");
1221  if (!checker.is_alive())
1222  return;
1223  }
1224  }
1225 }
1226 
1227 const std::vector<std::string>& edit_box::get_type_list_() const {
1228  return get_type_list_impl_<edit_box>();
1229 }
1230 
1231 } // namespace lxgui::gui
Utility class for safe checking of region validity.
bool is_alive() const
Check if the wrapped region is still alive.
Holds a single color (float RGBA, 128 bits)
Definition: gui_color.hpp:12
A frame with an editable text box.
void set_integer_only(bool integer_only)
Makes this edit_box allow integer numbers only.
bool add_char_(char32_t c)
const bounds2f & get_text_insets() const
Returns the text insets.
std::size_t get_max_letters() const
Returns the maximum number of letters to allow in this edit_box.
void notify_scaling_factor_updated() override
Tells this region that the global interface scaling factor has changed.
void set_max_letters(std::size_t max_letters)
Sets the maximum number of letters to allow in this edit_box.
void set_text_insets(const bounds2f &insets)
Sets the insets used to render the content text.
const std::vector< std::string > & get_type_list_() const override
void add_history_line(const utils::ustring &history_line)
Adds a new history line to the history line list.
utils::ustring::iterator iter_carret_pos_
void clear_history_lines()
Clears the history line list.
void process_key_(input::key key_id, bool shift_is_pressed, bool ctrl_is_pressed)
void set_font_string(utils::observer_ptr< font_string > fstr)
Sets the font_string to use to render the content.
bool move_carret_vertically_(bool down=true)
double get_blink_time() const
Returns the carret's blink time.
void set_highlight_color(const color &c)
Sets the color of the highlight quad.
const utils::observer_ptr< font_string > & get_font_string()
Returns the font_string used to render the content.
bool is_positive_only() const
Checks if this edit_box allows positive numbers only.
utils::periodic_timer carret_timer_
std::size_t selection_start_pos_
void highlight_text(std::size_t start=0u, std::size_t end=std::numeric_limits< std::size_t >::max(), bool force_update=false)
Selects a portion of the content.
void set_cursor_position(std::size_t pos)
Moves the cursor to a chosen position.
void set_positive_only(bool positive_only)
Makes this edit_box allow positive numbers only.
std::size_t get_letter_count() const
Returns the number of letters in the content.
const utils::ustring & get_text() const
Returns the content of this edit_box.
std::size_t max_history_lines_
void set_font(const std::string &font_name, float height)
Sets the font (file and size) to render the content.
void set_text(const utils::ustring &content)
Sets the content of this edit_box.
bool move_carret_horizontally_(bool forward=true)
const std::vector< utils::ustring > & get_history_lines() const
Returns the history line list.
std::size_t get_max_history_lines() const
Returns the maximum number of history lines this edit_box can keep.
bool is_numeric_only() const
Checks if this edit_box allows numeric characters only.
std::size_t get_cursor_position() const
Returns the current position of the cursor.
utils::observer_ptr< font_string > font_string_
void insert_after_cursor(const utils::ustring &content)
Inserts some text after the cursor.
bool is_password_mode_enabled() const
Checks if this edit_box is in password mode.
bool is_integer_only() const
Checks if this edit_box allows integer numbers only.
bool move_carret_at_(const vector2f &position)
std::size_t display_pos_
void copy_from(const region &obj) override
Copies a region's parameters into this edit_box (inheritance).
void set_password_mode_enabled(bool enable)
Enables or disables password mode.
std::size_t selection_end_pos_
void notify_focus(bool focus) override
Notifies this frame that it has received or lost focus.
void set_numeric_only(bool numeric_only)
Makes this edit_box allow numeric characters only.
std::size_t max_letters_
std::size_t get_letter_id_at_(const vector2f &position) const
std::vector< utils::ustring > history_line_list_
void set_arrows_ignored(bool arrows_ignored)
Sets whether keyboard arrows move the carret or not.
void unlight_text()
Deselects the selected text, if any.
void set_blink_time(double blink_time)
Sets the carret's blink time.
utils::observer_ptr< texture > highlight_
void set_multi_line(bool multi_line)
Allows this edit_box to have several lines in it.
bool can_use_script(const std::string &script_name) const override
Returns 'true' if this edit_box can use a script.
std::size_t current_history_line_
utils::ustring displayed_text_
void update_(float delta) override
void fire_script(const std::string &script_name, const event_data &data=event_data{}) override
Calls a script.
utils::observer_ptr< texture > carret_
bool is_multi_line() const
Checks if this edit_box can have several lines in it.
void set_max_history_lines(std::size_t max_history_lines)
Sets the maximum number of history lines this edit_box can keep.
utils::ustring unicode_text_
Stores a variable number of arguments for an event.
const utils::variant & get(std::size_t index) const
Returns a parameter of this event.
void add(T &&value)
Adds a parameter to this event.
A layered_region that can draw text on the screen.
A region that can contain other regions and react to events.
Definition: gui_frame.hpp:255
void enable_keyboard()
Marks this frame as able to receive any keyboard input.
Definition: gui_frame.hpp:467
void copy_from(const region &obj) override
Copies a region's parameters into this frame (inheritance).
Definition: gui_frame.cpp:195
void enable_mouse()
Marks this frame as able to receive mouse input (click & move).
Definition: gui_frame.hpp:372
virtual void fire_script(const std::string &script_name, const event_data &data=event_data{})
Calls a script.
Definition: gui_frame.cpp:1105
virtual void update_(float delta)
Definition: gui_frame.cpp:1561
virtual bool can_use_script(const std::string &script_name) const
Returns 'true' if this frame can use a script.
Definition: gui_frame.cpp:183
bool has_focus() const
Check if this frame currently has focus.
Definition: gui_frame.cpp:1326
virtual void notify_focus(bool focus)
Notifies this frame that it has received or lost focus.
Definition: gui_frame.cpp:1330
void set_focus(bool focus)
Asks for focus for this frame.
Definition: gui_frame.cpp:1318
void notify_scaling_factor_updated() override
Tells this region that the global interface scaling factor has changed.
Definition: gui_frame.cpp:1488
void enable_drag(const std::string &button_name)
Tells this frame to react to mouse drag.
Definition: gui_frame.cpp:1154
const std::locale & get_locale() const
Returns the current locale (used to format numbers).
Manages the user interface.
Definition: gui_manager.hpp:44
localizer & get_localizer()
Returns the object used for localizing strings.
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
void initialize_(T &self, const region_core_attributes &attr)
Set up function to call in all derived class constructors.
bool is_virtual() const
Checks if this region is virtual.
Definition: gui_region.cpp:553
manager & get_manager()
Returns this region's manager.
Definition: gui_region.hpp:693
std::string name_
Definition: gui_region.hpp:804
Used to draw some text on the screen.
Definition: gui_text.hpp:29
float get_line_spacing() const
Returns this text's line spacing.
Definition: gui_text.cpp:484
float get_box_width() const
Returns the width of the text box.
Definition: gui_text.cpp:384
float get_string_width(const std::string &content) const
Returns the length of a provided string.
Definition: gui_text.cpp:411
const std::array< vertex, 4 > & get_letter_quad(std::size_t index) const
Returns the quad for the letter at the provided index (position, texture coords, color).
Definition: gui_text.cpp:1058
std::size_t get_line_count() const
Returns the number of text lines.
Definition: gui_text.cpp:406
float get_line_height() const
Returns the height of one line (constant).
Definition: gui_text.cpp:287
std::size_t get_letter_count() const
Returns the number of letters currently displayed.
Definition: gui_text.cpp:1053
void set_clipboard_content(const utils::ustring &content)
Replace the content of the clipboard.
@ first_tick
The timer will start when you first call ticks()
void zero()
Resets the timer but doesn't pause it.
bool ticks()
Checks if the timer's period has been reached.
void update(double delta)
Updates this timer (adds time).
vector2< float > vector2f
Holds 2D coordinates (as floats)
vector2< T > bottom_right() const noexcept
Definition: gui_bounds2.hpp:40
vector2< T > top_left() const noexcept
Definition: gui_bounds2.hpp:32
Struct holding all the core information about a frame necessary for its creation.
Simple structure holding four vertices and a material.
Definition: gui_quad.hpp:18
std::array< vertex, 4 > v
Definition: gui_quad.hpp:19
Struct holding all the core information about a region necessary for its creation.
std::vector< utils::observer_ptr< const region > > inheritance