The Battle for Wesnoth  1.17.14+dev
menu.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2003 - 2022
3  by David White <dave@whitevine.net>
4  Part of the Battle for Wesnoth Project https://www.wesnoth.org/
5 
6  This program is free software; you can redistribute it and/or modify
7  it under the terms of the GNU General Public License as published by
8  the Free Software Foundation; either version 2 of the License, or
9  (at your option) any later version.
10  This program is distributed in the hope that it will be useful,
11  but WITHOUT ANY WARRANTY.
12 
13  See the COPYING file for more details.
14 */
15 
16 #define GETTEXT_DOMAIN "wesnoth-lib"
17 
18 #include "widgets/menu.hpp"
19 
20 #include "draw.hpp"
21 #include "font/sdl_ttf_compat.hpp"
22 #include "font/standard_colors.hpp"
23 #include "game_config.hpp"
24 #include "language.hpp"
25 #include "lexical_cast.hpp"
26 #include "picture.hpp"
27 #include "sdl/rect.hpp"
28 #include "sdl/texture.hpp"
29 #include "sound.hpp"
30 #include "utils/general.hpp"
31 #include "video.hpp"
32 #include "wml_separators.hpp"
33 
34 #include <numeric>
35 
36 namespace gui {
37 
39  : alpha_sort_()
40  , numeric_sort_()
41  , id_sort_()
42  , redirect_sort_()
43  , pos_sort_()
44 {
45  set_id_sort(-1);
46 }
47 
49 {
50  alpha_sort_.insert(column);
51  return *this;
52 }
53 
55 {
56  numeric_sort_.insert(column);
57  return *this;
58 }
59 
61 {
62  id_sort_.insert(column);
63  return *this;
64 }
65 
67 {
68  if(column != to) {
69  redirect_sort_.emplace(column, to);
70  }
71 
72  return *this;
73 }
74 
75 menu::basic_sorter& menu::basic_sorter::set_position_sort(int column, const std::vector<int>& pos)
76 {
77  pos_sort_[column] = pos;
78  return *this;
79 }
80 
82 {
83  const std::map<int,int>::const_iterator redirect = redirect_sort_.find(column);
84  if(redirect != redirect_sort_.end()) {
85  return column_sortable(redirect->second);
86  }
87 
88  return alpha_sort_.count(column) == 1 || numeric_sort_.count(column) == 1 ||
89  pos_sort_.count(column) == 1 || id_sort_.count(column) == 1;
90 }
91 
92 bool menu::basic_sorter::less(int column, const item& row1, const item& row2) const
93 {
94  const std::map<int,int>::const_iterator redirect = redirect_sort_.find(column);
95  if(redirect != redirect_sort_.end()) {
96  return less(redirect->second,row1,row2);
97  }
98 
99  if(id_sort_.count(column) == 1) {
100  return row1.id < row2.id;
101  }
102 
103  if(column < 0 || column >= int(row2.fields.size())) {
104  return false;
105  }
106 
107  if(column >= int(row1.fields.size())) {
108  return true;
109  }
110 
111  const std::string& item1 = row1.fields[column];
112  const std::string& item2 = row2.fields[column];
113 
114  if(alpha_sort_.count(column) == 1) {
115  std::string::const_iterator begin1 = item1.begin(), end1 = item1.end(),
116  begin2 = item2.begin(), end2 = item2.end();
117  while(begin1 != end1 && is_wml_separator(*begin1)) {
118  ++begin1;
119  }
120 
121  while(begin2 != end2 && is_wml_separator(*begin2)) {
122  ++begin2;
123  }
124 
125  return std::lexicographical_compare(begin1,end1,begin2,end2,utils::chars_less_insensitive);
126  } else if(numeric_sort_.count(column) == 1) {
127  int val_1 = lexical_cast_default<int>(item1, 0);
128  int val_2 = lexical_cast_default<int>(item2, 0);
129 
130  return val_1 > val_2;
131  }
132 
133  const std::map<int,std::vector<int>>::const_iterator itor = pos_sort_.find(column);
134  if(itor != pos_sort_.end()) {
135  const std::vector<int>& pos = itor->second;
136  if(row1.id >= pos.size()) {
137  return false;
138  }
139 
140  if(row2.id >= pos.size()) {
141  return true;
142  }
143 
144  return pos[row1.id] < pos[row2.id];
145  }
146 
147  return false;
148 }
149 
150 menu::menu(const std::vector<std::string>& items,
151  bool click_selects, int max_height, int max_width,
152  const sorter* sorter_obj, style *menu_style, const bool auto_join)
153 : scrollarea(auto_join), silent_(false),
154  max_height_(max_height), max_width_(max_width),
155  max_items_(-1), item_height_(-1),
156  heading_height_(-1),
157  selected_(0), click_selects_(click_selects), out_(false),
158  previous_button_(true), show_result_(false),
159  double_clicked_(false),
160  num_selects_(true),
162  last_was_doubleclick_(false), use_ellipsis_(false),
163  sorter_(sorter_obj), sortby_(-1), sortreversed_(false), highlight_heading_(-1)
164 {
165  style_ = (menu_style) ? menu_style : &default_style;
166  style_->init();
167  fill_items(items, true);
168 }
169 
171 {
172 }
173 
174 void menu::fill_items(const std::vector<std::string>& items, bool strip_spaces)
175 {
176  for(std::vector<std::string>::const_iterator itor = items.begin();
177  itor != items.end(); ++itor) {
178 
179  if(itor->empty() == false && (*itor)[0] == HEADING_PREFIX) {
180  heading_ = utils::quoted_split(itor->substr(1),COLUMN_SEPARATOR, !strip_spaces);
181  continue;
182  }
183 
184  const std::size_t id = items_.size();
185  item_pos_.push_back(id);
186  const item new_item(utils::quoted_split(*itor, COLUMN_SEPARATOR, !strip_spaces),id);
187  items_.push_back(new_item);
188 
189  //make sure there is always at least one item
190  if(items_.back().fields.empty()) {
191  items_.back().fields.push_back(" ");
192  }
193 
194  //if the first character in an item is an asterisk,
195  //it means this item should be selected by default
196  std::string& first_item = items_.back().fields.front();
197  if(first_item.empty() == false && first_item[0] == DEFAULT_ITEM) {
198  selected_ = id;
199  first_item.erase(first_item.begin());
200  }
201  }
202 
203  if(sortby_ >= 0) {
204  do_sort();
205  }
206  update_size();
207 }
208 
209 namespace {
210 
211 class sort_func
212 {
213 public:
214  sort_func(const menu::sorter& pred, int column) : pred_(&pred), column_(column)
215  {}
216 
217  bool operator()(const menu::item& a, const menu::item& b) const
218  {
219  return pred_->less(column_,a,b);
220  }
221 
222 private:
223  const menu::sorter* pred_;
224  int column_;
225 };
226 
227 }
228 
230 {
231  if(sorter_ == nullptr || sorter_->column_sortable(sortby_) == false) {
232  return;
233  }
234 
235  const int selectid = selection();
236 
237  std::stable_sort(items_.begin(), items_.end(), sort_func(*sorter_, sortby_));
238  if (sortreversed_)
239  std::reverse(items_.begin(), items_.end());
240 
241  recalculate_pos();
242 
243  if(selectid >= 0 && selectid < int(item_pos_.size())) {
244  move_selection_to(selectid, true, NO_MOVE_VIEWPORT);
245  }
246 
247  queue_redraw();
248 }
249 
251 {
252  std::size_t sz = items_.size();
253  item_pos_.resize(sz);
254  for(std::size_t i = 0; i != sz; ++i)
255  item_pos_[items_[i].id] = i;
256  assert_pos();
257 }
258 
260 {
261  std::size_t sz = items_.size();
262  assert(item_pos_.size() == sz);
263  for(std::size_t n = 0; n != sz; ++n) {
264  assert(item_pos_[n] < sz && n == items_[item_pos_[n]].id);
265  }
266 }
267 
269 {
270  set_full_size(items_.size());
272 }
273 
275 {
276  int h = heading_height();
277  for(std::size_t i = get_position(),
278  i_end = std::min(items_.size(), i + max_items_onscreen());
279  i < i_end; ++i)
280  h += get_item_rect(i).h;
281  h = std::max(h, height());
282  if (max_height_ > 0 && h > (max_height_)) {
283  h = max_height_;
284  }
285 
286  use_ellipsis_ = false;
287  const std::vector<int>& widths = column_widths();
288  int w = std::accumulate(widths.begin(), widths.end(), 0);
289  if (items_.size() > max_items_onscreen())
290  w += scrollbar_width();
291  w = std::max(w, width());
292  if (max_width_ > 0 && w > (max_width_)) {
293  use_ellipsis_ = true;
294  w = max_width_;
295  }
296 
298  set_measurements(w, h);
299 }
300 
301 int menu::selection() const
302 {
303  if (selected_ >= items_.size()) {
304  return -1;
305  }
306 
307  return items_[selected_].id;
308 }
309 
310 void menu::set_inner_location(const SDL_Rect& /*rect*/)
311 {
312  itemRects_.clear();
314 }
315 
316 void menu::change_item(int pos1, int pos2,const std::string& str)
317 {
318  if(pos1 < 0 || pos1 >= int(item_pos_.size()) ||
319  pos2 < 0 || pos2 >= int(items_[item_pos_[pos1]].fields.size())) {
320  return;
321  }
322 
323  items_[item_pos_[pos1]].fields[pos2] = str;
324  queue_redraw();
325 }
326 
327 void menu::erase_item(std::size_t index)
328 {
329  std::size_t nb_items = items_.size();
330  if (index >= nb_items)
331  return;
332  --nb_items;
333 
334  clear_item(nb_items);
335 
336  // fix ordered positions of items
337  std::size_t pos = item_pos_[index];
338  item_pos_.erase(item_pos_.begin() + index);
339  items_.erase(items_.begin() + pos);
340  for(std::size_t i = 0; i != nb_items; ++i) {
341  std::size_t &n1 = item_pos_[i], &n2 = items_[i].id;
342  if (n1 > pos) --n1;
343  if (n2 > index) --n2;
344  }
345  assert_pos();
346 
347  if (selected_ >= nb_items)
348  selected_ = nb_items - 1;
349 
352  itemRects_.clear();
353  queue_redraw();
354 }
355 
356 void menu::set_heading(const std::vector<std::string>& heading)
357 {
358  itemRects_.clear();
359  column_widths_.clear();
360 
361  heading_ = heading;
362  max_items_ = -1;
363 
364  queue_redraw();
365 }
366 
367 void menu::set_items(const std::vector<std::string>& items, bool strip_spaces, bool keep_viewport)
368 {
369 
370  const bool scrolled_to_max = (has_scrollbar() && get_position() == get_max_position());
371  items_.clear();
372  item_pos_.clear();
373  itemRects_.clear();
374  column_widths_.clear();
375  //undrawn_items_.clear();
376  max_items_ = -1; // Force recalculation of the max items.
377  item_height_ = -1; // Force recalculation of the item height.
378 
379  if (!keep_viewport || selected_ >= items.size()) {
380  selected_ = 0;
381  }
382 
383  fill_items(items, strip_spaces);
384  if(!keep_viewport) {
385  set_position(0);
386  } else if(scrolled_to_max) {
388  }
389 
391 
392  if(!keep_viewport) {
394  }
395  queue_redraw();
396 }
397 
398 void menu::set_max_height(const int new_max_height)
399 {
400  max_height_ = new_max_height;
401  itemRects_.clear();
402  max_items_ = -1;
403  update_size();
404 }
405 
406 void menu::set_max_width(const int new_max_width)
407 {
408  max_width_ = new_max_width;
409  itemRects_.clear();
410  column_widths_.clear();
411  update_size();
412 }
413 
414 std::size_t menu::max_items_onscreen() const
415 {
416  if(max_items_ != -1) {
417  return std::size_t(max_items_);
418  }
419 
420  const std::size_t max_height = (
421  max_height_ == -1
422  ? (video::game_canvas_size().y * 66) / 100
423  : max_height_
424  ) - heading_height();
425 
426  std::vector<int> heights;
427  std::size_t n;
428  for(n = 0; n != items_.size(); ++n) {
429  heights.push_back(get_item_height(n));
430  }
431 
432  std::sort(heights.begin(),heights.end(),std::greater<int>());
433  std::size_t sum = 0;
434  for(n = 0; n != items_.size() && sum < max_height; ++n) {
435  sum += heights[n];
436  }
437 
438  if(sum > max_height && n > 1)
439  --n;
440 
441  return max_items_ = n;
442 }
443 
445 {
446  if(click_selects_)
447  return;
449 }
450 
451 void menu::set_selection_pos(std::size_t new_selected, bool silent, SELECTION_MOVE_VIEWPORT move_viewport)
452 {
453  if (new_selected >= items_.size())
454  return;
455 
456  bool changed = false;
457  if (new_selected != selected_) {
459  invalidate_row_pos(new_selected);
460  selected_ = new_selected;
461  changed = true;
462  }
463 
464  if(move_viewport == MOVE_VIEWPORT) {
466  if(!silent_ && !silent && changed) {
468  }
469  }
470 }
471 
472 void menu::move_selection_up(std::size_t dep)
473 {
474  set_selection_pos(selected_ > dep ? selected_ - dep : 0);
475 }
476 
477 void menu::move_selection_down(std::size_t dep)
478 {
479  std::size_t nb_items = items_.size();
480  set_selection_pos(selected_ + dep >= nb_items ? nb_items - 1 : selected_ + dep);
481 }
482 
483 // private function with control over sound and viewport
484 void menu::move_selection_to(std::size_t id, bool silent, SELECTION_MOVE_VIEWPORT move_viewport)
485 {
486  if(id < item_pos_.size()) {
487  set_selection_pos(item_pos_[id], silent, move_viewport);
488  }
489 }
490 
491 // public function
492 void menu::move_selection(std::size_t id)
493 {
494  if(id < item_pos_.size()) {
496  }
497 }
498 
499 // public function
501 {
502  if(id < item_pos_.size()) {
504  }
505 }
506 
508 {
509  set_selection_pos(0, true);
510 }
511 
512 void menu::key_press(SDL_Keycode key)
513 {
514  if (!click_selects_) {
515  switch(key) {
516  case SDLK_UP:
518  break;
519  case SDLK_DOWN:
521  break;
522  case SDLK_PAGEUP:
524  break;
525  case SDLK_PAGEDOWN:
527  break;
528  case SDLK_HOME:
530  break;
531  case SDLK_END:
532  set_selection_pos(items_.size() - 1);
533  break;
534  //case SDLK_RETURN:
535  // double_clicked_ = true;
536  // break;
537  default:
538  break;
539  }
540  }
541 
542  if (num_selects_ && key >= SDLK_1 && key <= SDLK_9)
543  set_selection_pos(key - SDLK_1);
544 }
545 
546 bool menu::requires_event_focus(const SDL_Event* event) const
547 {
548  if(!focus_ || height() == 0 || hidden()) {
549  return false;
550  }
551  if(event == nullptr) {
552  //when event is not specified, signal that focus may be desired later
553  return true;
554  }
555 
556  if(event->type == SDL_KEYDOWN) {
557  SDL_Keycode key = event->key.keysym.sym;
558  if (!click_selects_) {
559  switch(key) {
560  case SDLK_UP:
561  case SDLK_DOWN:
562  case SDLK_PAGEUP:
563  case SDLK_PAGEDOWN:
564  case SDLK_HOME:
565  case SDLK_END:
566  return true;
567  default:
568  break;
569  }
570  }
571  if (num_selects_ && key >= SDLK_1 && key <= SDLK_9) {
572  return true;
573  }
574  }
575  //mouse events are processed regardless of focus
576  return false;
577 }
578 
579 void menu::handle_event(const SDL_Event& event)
580 {
582  if (height()==0 || hidden())
583  return;
584 
585  if(event.type == SDL_KEYDOWN) {
586  // Only pass key events if we have the focus
587  if (focus(&event))
588  key_press(event.key.keysym.sym);
589  } else if(!mouse_locked() && ((event.type == SDL_MOUSEBUTTONDOWN &&
590  (event.button.button == SDL_BUTTON_LEFT || event.button.button == SDL_BUTTON_RIGHT)) ||
591  event.type == DOUBLE_CLICK_EVENT)) {
592 
593  int x = 0;
594  int y = 0;
595  if(event.type == SDL_MOUSEBUTTONDOWN) {
596  x = event.button.x;
597  y = event.button.y;
598  } else {
599  x = reinterpret_cast<std::size_t>(event.user.data1);
600  y = reinterpret_cast<std::size_t>(event.user.data2);
601  }
602 
603  const int item = hit(x,y);
604  if(item != -1) {
605  set_focus(true);
606  move_selection_to(item);
607 
608  if(click_selects_) {
609  show_result_ = true;
610  }
611 
612  if(event.type == DOUBLE_CLICK_EVENT) {
614  ignore_next_doubleclick_ = false;
615  } else {
616  double_clicked_ = true;
617  last_was_doubleclick_ = true;
618  if(!silent_) {
620  }
621  }
622  } else if (last_was_doubleclick_) {
623  // If we have a double click as the next event, it means
624  // this double click was generated from a click that
625  // already has helped in generating a double click.
626  SDL_Event ev;
627  SDL_PeepEvents(&ev, 1, SDL_PEEKEVENT, DOUBLE_CLICK_EVENT, DOUBLE_CLICK_EVENT);
628  if (ev.type == DOUBLE_CLICK_EVENT) {
630  }
631  last_was_doubleclick_ = false;
632  }
633  }
634 
635 
636  if(sorter_ != nullptr) {
637  const int heading = hit_heading(x,y);
638  if(heading >= 0 && sorter_->column_sortable(heading)) {
639  sort_by(heading);
640  }
641  }
642  } else if(!mouse_locked() && event.type == SDL_MOUSEMOTION) {
643  if(click_selects_) {
644  const int item = hit(event.motion.x,event.motion.y);
645  const bool out = (item == -1);
646  if (out_ != out) {
647  out_ = out;
649  }
650  if (item != -1) {
651  move_selection_to(item);
652  }
653  }
654 
655  const int heading_item = hit_heading(event.motion.x,event.motion.y);
656  if(heading_item != highlight_heading_) {
657  highlight_heading_ = heading_item;
659  }
660  }
661 }
662 
664 {
665  if(show_result_) {
666  show_result_ = false;
667  return selected_;
668  } else {
669  return -1;
670  }
671 }
672 
674 {
675  bool old = double_clicked_;
676  double_clicked_ = false;
677  return old;
678 }
679 
680 void menu::set_click_selects(bool value)
681 {
682  click_selects_ = value;
683 }
684 
686 {
687  num_selects_ = value;
688 }
689 
690 void menu::scroll(unsigned int)
691 {
692  itemRects_.clear();
693  queue_redraw();
694 }
695 
697 {
698  if(sortby_ >= 0) {
699  //clear an existing sort
700  sort_by(-1);
701  }
702  sorter_ = s;
703  sortreversed_ = false;
704  sortby_ = -1;
705 }
706 
707 void menu::sort_by(int column)
708 {
709  const bool already_sorted = (column == sortby_);
710 
711  if(already_sorted) {
712  if(sortreversed_ == false) {
713  sortreversed_ = true;
714  } else {
715  sortreversed_ = false;
716  sortby_ = -1;
717  }
718  } else {
719  sortby_ = column;
720  sortreversed_ = false;
721  }
722 
723  do_sort();
724  itemRects_.clear();
725  queue_redraw();
726 }
727 
728 SDL_Rect menu::style::item_size(const std::string& item) const {
729  SDL_Rect res {0,0,0,0};
730  std::vector<std::string> img_text_items = utils::split(item, IMG_TEXT_SEPARATOR, utils::REMOVE_EMPTY);
731  for (std::vector<std::string>::const_iterator it = img_text_items.begin();
732  it != img_text_items.end(); ++it) {
733  if (res.w > 0 || res.h > 0) {
734  // Not the first item, add the spacing.
735  res.w += 5;
736  }
737  const std::string str = *it;
738  if (!str.empty() && str[0] == IMAGE_PREFIX) {
739  const std::string image_name(str.begin()+1,str.end());
740  const point image_size = image::get_size(image_name);
741  if (image_size.x && image_size.y) {
742  int w = image_size.x;
743  int h = image_size.y;
744  adjust_image_bounds(w, h);
745  res.w += w;
746  res.h = std::max<int>(h, res.h);
747  }
748  }
749  else {
750  const SDL_Rect area {0,0,10000,10000};
751  const SDL_Rect font_size =
752  font::pango_draw_text(false, area, get_font_size(),
753  font::NORMAL_COLOR, str, 0, 0);
754  res.w += font_size.w;
755  res.h = std::max<int>(font_size.h, res.h);
756  }
757  }
758  return res;
759 }
760 
761 void menu::style::draw_row_bg(menu& /*menu_ref*/, const std::size_t /*row_index*/, const SDL_Rect& rect, ROW_TYPE type)
762 {
763  int rgb = 0;
764  double alpha = 0.0;
765 
766  switch(type) {
767  case NORMAL_ROW:
768  rgb = normal_rgb_;
769  alpha = normal_alpha_;
770  break;
771  case SELECTED_ROW:
772  rgb = selected_rgb_;
773  alpha = selected_alpha_;
774  break;
775  case HEADING_ROW:
776  rgb = heading_rgb_;
777  alpha = heading_alpha_;
778  break;
779  }
780 
781  // FIXME: make this clearer
782  color_t c((rgb & 0xff0000) >> 16, (rgb & 0xff00) >> 8, rgb & 0xff);
783  c.a = 255 * alpha;
784 
785  draw::fill(rect, c);
786 }
787 
788 void menu::style::draw_row(menu& menu_ref, const std::size_t row_index, const SDL_Rect& rect, ROW_TYPE type)
789 {
790  if(rect.w == 0 || rect.h == 0) {
791  return;
792  }
793  draw_row_bg(menu_ref, row_index, rect, type);
794 
795  SDL_Rect minirect = rect;
796  if(type != HEADING_ROW) {
797  minirect.x += thickness_;
798  minirect.y += thickness_;
799  minirect.w -= 2*thickness_;
800  minirect.h -= 2*thickness_;
801  }
802  menu_ref.draw_row(row_index, minirect, type);
803 }
804 
805 
806 
807 void menu::column_widths_item(const std::vector<std::string>& row, std::vector<int>& widths) const
808 {
809  for(std::size_t col = 0; col != row.size(); ++col) {
810  const SDL_Rect res = style_->item_size(row[col]);
811  std::size_t text_trailing_space = (item_ends_with_image(row[col])) ? 0 : style_->get_cell_padding();
812 
813  if(col == widths.size()) {
814  widths.push_back(res.w + text_trailing_space);
815  } else if(static_cast<std::size_t>(res.w) > widths[col] - text_trailing_space) {
816  widths[col] = res.w + text_trailing_space;
817  }
818  }
819 }
820 
821 bool menu::item_ends_with_image(const std::string& item) const
822 {
823  std::string::size_type pos = item.find_last_of(IMG_TEXT_SEPARATOR);
824  pos = (pos == std::string::npos) ? 0 : pos+1;
825  return(item.size() > pos && item.at(pos) == IMAGE_PREFIX);
826 }
827 
828 const std::vector<int>& menu::column_widths() const
829 {
830  if(column_widths_.empty()) {
832  for(std::size_t row = 0; row != items_.size(); ++row) {
834  }
835  }
836 
837  return column_widths_;
838 }
839 
841 {
842  SDL_Rect rect = get_item_rect(item);
843  if (rect.w == 0)
844  return;
845  queue_redraw(rect);
846 }
847 
848 void menu::draw_row(const std::size_t row_index, const SDL_Rect& loc, ROW_TYPE type)
849 {
850  //called from style, draws one row's contents in a generic and adaptable way
851  const std::vector<std::string>& row = (type == HEADING_ROW) ? heading_ : items_[row_index].fields;
852  rect area = video::game_canvas();
853  rect column = inner_location();
854  const std::vector<int>& widths = column_widths();
855  bool lang_rtl = current_language_rtl();
856  int dir = (lang_rtl) ? -1 : 1;
857 
858  int xpos = loc.x;
859  if(lang_rtl) {
860  xpos += loc.w;
861  }
862 
863  for(std::size_t i = 0; i != row.size(); ++i) {
864 
865  if(lang_rtl) {
866  xpos -= widths[i];
867  }
868  if(type == HEADING_ROW) {
869  rect draw_rect {xpos, loc.y, widths[i], loc.h };
870 
871  if(highlight_heading_ == int(i)) {
872  draw::fill(draw_rect, {255,255,255,77});
873  } else if(sortby_ == int(i)) {
874  draw::fill(draw_rect, {255,255,255,26});
875  }
876  }
877 
878  const int last_x = xpos;
879  column.w = widths[i];
880  std::string str = row[i];
881  std::vector<std::string> img_text_items = utils::split(str, IMG_TEXT_SEPARATOR, utils::REMOVE_EMPTY);
882  for (std::vector<std::string>::const_iterator it = img_text_items.begin();
883  it != img_text_items.end(); ++it) {
884  str = *it;
885  if (!str.empty() && str[0] == IMAGE_PREFIX) {
886  const std::string image_name(str.begin()+1,str.end());
887  const texture img = image::get_texture(image_name);
888  int img_w = img.w();
889  int img_h = img.h();
890  style_->adjust_image_bounds(img_w, img_h);
891  const int remaining_width = max_width_ < 0 ? area.w :
892  std::min<int>(max_width_, ((lang_rtl)? xpos - loc.x : loc.x + loc.w - xpos));
893  if(img && img_w <= remaining_width
894  && loc.y + img_h < area.h) {
895  const std::size_t y = loc.y + (loc.h - img_h)/2;
896  const std::size_t w = img_w + 5;
897  const std::size_t x = xpos + ((lang_rtl) ? widths[i] - w : 0);
898  draw::blit(img, {int(x), int(y), img_w, img_h});
899  if(!lang_rtl)
900  xpos += w;
901  column.w -= w;
902  }
903  } else {
904  column.x = xpos;
905 
906  const auto text_size = font::pango_line_size(str, style_->get_font_size());
907  const std::size_t y = loc.y + (loc.h - text_size.second)/2;
908  const std::size_t padding = 2;
909  rect text_loc = column;
910  text_loc.w = loc.w - (xpos - loc.x) - 2 * style_->get_thickness();
911  text_loc.h = text_size.second;
913  (type == HEADING_ROW ? xpos+padding : xpos), y);
914 
915  if(type == HEADING_ROW && sortby_ == int(i)) {
916  const texture sort_tex(image::get_texture(sortreversed_ ? "buttons/sliders/slider_arrow_blue.png" :
917  "buttons/sliders/slider_arrow_blue.png~ROTATE(180)"));
918  if(sort_tex && sort_tex.w() <= widths[i] && sort_tex.h() <= loc.h) {
919  const int sort_x = xpos + widths[i] - sort_tex.w() - padding;
920  const int sort_y = loc.y + loc.h/2 - sort_tex.h()/2;
921  SDL_Rect dest = {sort_x, sort_y, sort_tex.w(), sort_tex.h()};
922  draw::blit(sort_tex, dest);
923  }
924  }
925 
926  xpos += dir * (text_size.first + 5);
927  }
928  }
929  if(lang_rtl)
930  xpos = last_x;
931  else
932  xpos = last_x + widths[i];
933  }
934 }
935 
937 {
938  SDL_Rect heading_rect = inner_location();
939  heading_rect.h = heading_height();
940  style_->draw_row(*this,0,heading_rect,HEADING_ROW);
941 
942  for(std::size_t i = 0; i != item_pos_.size(); ++i) {
945  }
946 }
947 
948 int menu::hit(int x, int y) const
949 {
950  const SDL_Rect& loc = inner_location();
951  if (x >= loc.x && x < loc.x + loc.w && y >= loc.y && y < loc.y + loc.h) {
952  for(std::size_t i = 0; i != items_.size(); ++i) {
953  const SDL_Rect& rect = get_item_rect(i);
954  if (y >= rect.y && y < rect.y + rect.h)
955  return i;
956  }
957  }
958 
959  return -1;
960 }
961 
962 int menu::hit_column(int x) const
963 {
964  const std::vector<int>& widths = column_widths();
965  int j = -1, j_end = widths.size();
966  for(x -= location().x; x >= 0; x -= widths[j]) {
967  if(++j == j_end) {
968  return -1;
969  }
970  }
971  return j;
972 }
973 
974 int menu::hit_heading(int x, int y) const
975 {
976  const std::size_t height = heading_height();
977  const SDL_Rect& loc = inner_location();
978  if(y >= loc.y && static_cast<std::size_t>(y) < loc.y + height) {
979  return hit_column(x);
980  } else {
981  return -1;
982  }
983 }
984 
985 SDL_Rect menu::get_item_rect(int item) const
986 {
987  return get_item_rect_internal(item_pos_[item]);
988 }
989 
990 SDL_Rect menu::get_item_rect_internal(std::size_t item) const
991 {
992  unsigned int first_item_on_screen = get_position();
993  if (item < first_item_on_screen ||
994  item >= first_item_on_screen + max_items_onscreen()) {
995  return sdl::empty_rect;
996  }
997 
998  const std::map<int,SDL_Rect>::const_iterator i = itemRects_.find(item);
999  if(i != itemRects_.end())
1000  return i->second;
1001 
1002  const SDL_Rect& loc = inner_location();
1003 
1004  int y = loc.y + heading_height();
1005  if (item != first_item_on_screen) {
1006  const SDL_Rect& prev = get_item_rect_internal(item-1);
1007  y = prev.y + prev.h;
1008  }
1009 
1010  rect res(loc.x, y, loc.w, get_item_height(item));
1011 
1012  const point canvas_size = video::game_canvas_size();
1013 
1014  if(res.x > canvas_size.x) {
1015  return sdl::empty_rect;
1016  } else if(res.x + res.w > canvas_size.x) {
1017  res.w = canvas_size.x - res.x;
1018  }
1019 
1020  if(res.y > canvas_size.y) {
1021  return sdl::empty_rect;
1022  } else if(res.y + res.h > canvas_size.y) {
1023  res.h = canvas_size.y - res.y;
1024  }
1025 
1026  //only insert into the cache if the menu's co-ordinates have
1027  //been initialized
1028  if (loc.x > 0 && loc.y > 0)
1029  itemRects_.emplace(item, res);
1030 
1031  return res;
1032 }
1033 
1034 std::size_t menu::get_item_height_internal(const std::vector<std::string>& item) const
1035 {
1036  std::size_t res = 0;
1037  for(std::vector<std::string>::const_iterator i = item.begin(); i != item.end(); ++i) {
1038  SDL_Rect rect = style_->item_size(*i);
1039  res = std::max<int>(rect.h,res);
1040  }
1041 
1042  return res;
1043 }
1044 
1045 std::size_t menu::heading_height() const
1046 {
1047  if(heading_height_ == -1) {
1049  }
1050 
1051  return std::min<unsigned int>(heading_height_,max_height_);
1052 }
1053 
1054 std::size_t menu::get_item_height(int) const
1055 {
1056  if(item_height_ != -1)
1057  return std::size_t(item_height_);
1058 
1059  std::size_t max_height = 0;
1060  for(std::size_t n = 0; n != items_.size(); ++n) {
1061  max_height = std::max<int>(max_height,get_item_height_internal(items_[n].fields));
1062  }
1063 
1064  return item_height_ = max_height;
1065 }
1066 
1067 void menu::invalidate_row(std::size_t id)
1068 {
1069  if(id >= items_.size()) {
1070  return;
1071  }
1072 
1074 }
1075 
1076 void menu::invalidate_row_pos(std::size_t pos)
1077 {
1078  if(pos >= items_.size()) {
1079  return;
1080  }
1081 
1082  invalidate_row(items_[pos].id);
1083 }
1084 
1086 {
1087  rect heading_rect = inner_location();
1088  heading_rect.h = heading_height();
1089  queue_redraw(heading_rect);
1090 }
1091 
1092 }
Drawing functions, for drawing things on the screen.
basic_sorter & set_alpha_sort(int column)
Definition: menu.cpp:48
std::size_t id
Definition: menu.hpp:108
void sort_by(int column)
Definition: menu.cpp:707
bool last_was_doubleclick_
Definition: menu.hpp:277
char const IMG_TEXT_SEPARATOR
void set_shown_size(unsigned h)
Definition: scrollarea.cpp:106
std::size_t get_font_size() const
Definition: menu_style.cpp:56
void column_widths_item(const std::vector< std::string > &row, std::vector< int > &widths) const
Definition: menu.cpp:807
void draw_contents() override
Definition: menu.cpp:936
void set_numeric_keypress_selection(bool value)
Definition: menu.cpp:685
void invalidate_row_pos(std::size_t pos)
Definition: menu.cpp:1076
void adjust_position(unsigned pos)
Definition: scrollarea.cpp:96
int w() const
The draw-space width of the texture, in pixels.
Definition: texture.hpp:105
void set_selection_pos(std::size_t pos, bool silent=false, SELECTION_MOVE_VIEWPORT move_viewport=MOVE_VIEWPORT)
Definition: menu.cpp:451
virtual void init()
Definition: menu.hpp:42
SDL_Rect get_item_rect(int item) const
Definition: menu.cpp:985
bool hidden() const
Definition: widget.cpp:161
int max_height_
Definition: menu.hpp:226
void move_selection(std::size_t id)
Definition: menu.cpp:492
unsigned scrollbar_width() const
Definition: scrollarea.cpp:142
bool num_selects_
variable which determines whether a numeric keypress should select an item on the dialog ...
Definition: menu.hpp:272
void queue_redraw()
Indicate that the widget should be redrawn.
Definition: widget.cpp:215
std::map< int, int > redirect_sort_
Definition: menu.hpp:136
int selection() const
Definition: menu.cpp:301
const std::vector< int > & column_widths() const
Definition: menu.cpp:828
New lexcical_cast header.
#define a
std::size_t get_item_height(int item) const
Definition: menu.cpp:1054
int h() const
The draw-space height of the texture, in pixels.
Definition: texture.hpp:114
int sortby_
Definition: menu.hpp:283
ROW_TYPE
Definition: menu.hpp:35
basic_sorter & set_position_sort(int column, const std::vector< int > &pos)
Definition: menu.cpp:75
std::vector< std::string > heading_
Definition: menu.hpp:235
std::pair< int, int > pango_line_size(const std::string &line, int font_size, font::pango_text::FONT_STYLE font_style)
Determine the width and height of a line of text given a certain font size.
General purpose widgets.
std::size_t max_items_onscreen() const
Definition: menu.cpp:414
void do_sort()
Definition: menu.cpp:229
virtual SDL_Rect item_size(const std::string &item) const
Definition: menu.cpp:728
void move_selection_keeping_viewport(std::size_t id)
Definition: menu.cpp:500
menu(const std::vector< std::string > &items, bool click_selects=false, int max_height=-1, int max_width=-1, const sorter *sorter_obj=nullptr, style *menu_style=nullptr, const bool auto_join=true)
Definition: menu.cpp:150
const std::vector< std::string > items
basic_sorter & set_redirect_sort(int column, int to)
Definition: menu.cpp:66
void set_focus(bool focus)
Definition: widget.cpp:128
SELECTION_MOVE_VIEWPORT
Definition: menu.hpp:298
const std::string menu_select
bool show_result_
Definition: menu.hpp:246
#define h
virtual bool column_sortable(int column) const =0
std::vector< int > column_widths_
Definition: menu.hpp:238
void adjust_viewport_to_selection()
Definition: menu.cpp:444
point get_size(const locator &i_locator, bool skip_cache)
Returns the width and height of an image.
Definition: picture.cpp:970
int hit(int x, int y) const
Definition: menu.cpp:948
void set_sorter(sorter *s)
Definition: menu.cpp:696
int item_height_
Definition: menu.hpp:227
Wrapper class to encapsulate creation and management of an SDL_Texture.
Definition: texture.hpp:32
void rect(const SDL_Rect &rect)
Draw a rectangle.
Definition: draw.cpp:141
char const IMAGE_PREFIX
void update_size()
Definition: menu.cpp:274
void set_click_selects(bool value)
Definition: menu.cpp:680
void set_measurements(int w, int h)
Definition: widget.cpp:108
bool ignore_next_doubleclick_
Definition: menu.hpp:276
#define b
void reset_selection()
Definition: menu.cpp:507
bool sortreversed_
Definition: menu.hpp:284
void move_selection_down(std::size_t dep)
Definition: menu.cpp:477
rect game_canvas()
The game canvas area, in drawing coordinates.
Definition: video.cpp:421
void set_full_size(unsigned h)
Definition: scrollarea.cpp:113
void adjust_image_bounds(int &w, int &h) const
Definition: menu_style.cpp:66
bool has_scrollbar() const
Definition: scrollarea.cpp:33
char const DEFAULT_ITEM
void scroll(unsigned int pos) override
Definition: menu.cpp:690
void set_position(unsigned pos)
Definition: scrollarea.cpp:91
bool current_language_rtl()
Definition: language.cpp:55
void fill_items(const std::vector< std::string > &items, bool strip_spaces)
Set new items to show.
Definition: menu.cpp:174
bool focus(const SDL_Event *event)
Definition: widget.cpp:136
bool out_
Definition: menu.hpp:242
void update_scrollbar_grip_height()
Definition: menu.cpp:268
basic_sorter & set_id_sort(int column)
Definition: menu.cpp:60
virtual void draw_row(menu &menu_ref, const std::size_t row_index, const SDL_Rect &rect, ROW_TYPE type)
Definition: menu.cpp:788
std::set< int > id_sort_
Definition: menu.hpp:135
std::vector< std::string > fields
Definition: menu.hpp:107
std::size_t heading_height() const
Definition: menu.cpp:1045
int process()
Definition: menu.cpp:663
The basic class for representing 8-bit RGB or RGBA colour values.
Definition: color.hpp:58
int max_width_
Definition: menu.hpp:226
std::vector< std::size_t > item_pos_
Definition: menu.hpp:233
const rect & location() const
Definition: widget.cpp:123
void set_heading(const std::vector< std::string > &heading)
Definition: menu.cpp:356
static style & default_style
Definition: menu.hpp:95
void set_max_width(const int new_max_width)
Definition: menu.cpp:406
int highlight_heading_
Definition: menu.hpp:285
virtual void erase_item(std::size_t index)
Definition: menu.cpp:327
void set_inner_location(const SDL_Rect &rect) override
Definition: menu.cpp:310
const color_t NORMAL_COLOR
std::set< int > numeric_sort_
Definition: menu.hpp:135
bool item_ends_with_image(const std::string &item) const
Definition: menu.cpp:821
virtual void set_items(const std::vector< std::string > &items, bool strip_spaces=true, bool keep_viewport=false)
Set new items to show and redraw/recalculate everything.
Definition: menu.cpp:367
void change_item(int pos1, int pos2, const std::string &str)
Definition: menu.cpp:316
std::size_t get_cell_padding() const
Definition: menu_style.cpp:57
#define DOUBLE_CLICK_EVENT
Definition: events.hpp:24
void invalidate_row(std::size_t id)
Definition: menu.cpp:1067
virtual void draw_row(const std::size_t row_index, const SDL_Rect &rect, ROW_TYPE type)
Definition: menu.cpp:848
int hit_heading(int x, int y) const
Definition: menu.cpp:974
std::size_t i
Definition: function.cpp:968
bool is_wml_separator(char c)
virtual void handle_event(const SDL_Event &event) override
Definition: menu.cpp:579
bool previous_button_
Definition: menu.hpp:243
const std::string & id() const
Definition: widget.cpp:198
void assert_pos()
Definition: menu.cpp:259
virtual bool less(int column, const item &row1, const item &row2) const
Definition: menu.cpp:92
void invalidate_heading()
Definition: menu.cpp:1085
static map_location::DIRECTION s
std::vector< std::string > quoted_split(const std::string &val, char c, int flags, char quote)
This function is identical to split(), except it does not split when it otherwise would if the previo...
point game_canvas_size()
The size of the game canvas, in drawing coordinates / game pixels.
Definition: video.cpp:426
virtual void handle_event(const SDL_Event &event)
Definition: scrollarea.cpp:147
bool focus_
Definition: widget.hpp:91
rect pango_draw_text(bool actually_draw, const rect &area, int size, const color_t &color, const std::string &text, int x, int y, bool use_tooltips, pango_text::FONT_STYLE style)
Draws text on the screen.
unsigned get_max_position() const
Definition: scrollarea.cpp:86
bool use_ellipsis_
Definition: menu.hpp:280
An abstract description of a rectangle with integer coordinates.
Definition: rect.hpp:46
Holds a 2D point.
Definition: point.hpp:24
void move_selection_to(std::size_t id, bool silent=false, SELECTION_MOVE_VIEWPORT move_viewport=MOVE_VIEWPORT)
Definition: menu.cpp:484
int width() const
Definition: widget.cpp:113
int w
int height() const
Definition: widget.cpp:118
std::size_t index(const std::string &str, const std::size_t index)
Codepoint index corresponding to the nth character in a UTF-8 string.
Definition: unicode.cpp:72
bool mouse_locked() const
Definition: widget.cpp:64
bool double_clicked()
Definition: menu.cpp:673
basic_sorter & set_numeric_sort(int column)
Definition: menu.cpp:54
virtual void draw_row_bg(menu &menu_ref, const std::size_t row_index, const SDL_Rect &rect, ROW_TYPE type)
Definition: menu.cpp:761
void blit(const texture &tex, const SDL_Rect &dst)
Draws a texture, or part of a texture, at the given location.
Definition: draw.cpp:301
bool chars_less_insensitive(char a, char b)
Definition: general.hpp:25
std::set< int > alpha_sort_
Definition: menu.hpp:135
const std::string button_press
void key_press(SDL_Keycode key)
Definition: menu.cpp:512
std::size_t get_item_height_internal(const std::vector< std::string > &item) const
Definition: menu.cpp:1034
std::map< int, std::vector< int > > pos_sort_
Definition: menu.hpp:137
char const HEADING_PREFIX
Contains the SDL_Rect helper code.
virtual bool column_sortable(int column) const
Definition: menu.cpp:81
char const COLUMN_SEPARATOR
bool click_selects_
Definition: menu.hpp:241
~menu()
Default implementation, but defined out-of-line for efficiency reasons.
Definition: menu.cpp:170
constexpr const SDL_Rect empty_rect
Definition: rect.hpp:30
std::vector< std::string > split(const config_attribute_value &val)
map_location prev
Definition: astarsearch.cpp:66
SDL_Rect get_item_rect_internal(std::size_t pos) const
Definition: menu.cpp:990
std::vector< item > items_
Definition: menu.hpp:232
void play_UI_sound(const std::string &files)
Definition: sound.cpp:1066
int hit_column(int x) const
Definition: menu.cpp:962
const sorter * sorter_
Definition: menu.hpp:282
bool requires_event_focus(const SDL_Event *event=nullptr) const override
Definition: menu.cpp:546
bool double_clicked_
Definition: menu.hpp:248
std::size_t selected_
Definition: menu.hpp:240
void clear_item(int item)
Definition: menu.cpp:840
rect inner_location() const
Definition: scrollarea.cpp:134
bool silent_
Definition: menu.hpp:209
int heading_height_
Definition: menu.hpp:236
texture get_texture(const image::locator &i_locator, TYPE type, bool skip_cache)
Returns an image texture suitable for hardware-accelerated rendering.
Definition: picture.cpp:1128
mock_char c
void recalculate_pos()
Definition: menu.cpp:250
static map_location::DIRECTION n
Transitional API for porting SDL_ttf-based code to Pango.
void fill(const SDL_Rect &rect, uint8_t r, uint8_t g, uint8_t b, uint8_t a)
Fill an area with the given colour.
Definition: draw.cpp:41
int max_items_
Definition: menu.hpp:227
void set_max_height(const int new_max_height)
Set a new max height for this menu.
Definition: menu.cpp:398
std::map< int, SDL_Rect > itemRects_
Definition: menu.hpp:255
unsigned get_position() const
Definition: scrollarea.cpp:81
style * style_
Definition: menu.hpp:208
void move_selection_up(std::size_t dep)
Definition: menu.cpp:472
std::size_t get_thickness() const
Definition: menu_style.cpp:58