The Battle for Wesnoth  1.17.23+dev
menu.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2003 - 2023
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);
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) {
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;
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;
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(
917  image::locator{sortreversed_ ? "buttons/sliders/slider_arrow_blue.png"
918  : "buttons/sliders/slider_arrow_blue.png~ROTATE(180)"}));
919  if(sort_tex && sort_tex.w() <= widths[i] && sort_tex.h() <= loc.h) {
920  const int sort_x = xpos + widths[i] - sort_tex.w() - padding;
921  const int sort_y = loc.y + loc.h/2 - sort_tex.h()/2;
922  SDL_Rect dest = {sort_x, sort_y, sort_tex.w(), sort_tex.h()};
923  draw::blit(sort_tex, dest);
924  }
925  }
926 
927  xpos += dir * (text_size.first + 5);
928  }
929  }
930  if(lang_rtl)
931  xpos = last_x;
932  else
933  xpos = last_x + widths[i];
934  }
935 }
936 
938 {
939  SDL_Rect heading_rect = inner_location();
940  heading_rect.h = heading_height();
941  style_->draw_row(*this,0,heading_rect,HEADING_ROW);
942 
943  for(std::size_t i = 0; i != item_pos_.size(); ++i) {
946  }
947 }
948 
949 int menu::hit(int x, int y) const
950 {
951  const SDL_Rect& loc = inner_location();
952  if (x >= loc.x && x < loc.x + loc.w && y >= loc.y && y < loc.y + loc.h) {
953  for(std::size_t i = 0; i != items_.size(); ++i) {
954  const SDL_Rect& rect = get_item_rect(i);
955  if (y >= rect.y && y < rect.y + rect.h)
956  return i;
957  }
958  }
959 
960  return -1;
961 }
962 
963 int menu::hit_column(int x) const
964 {
965  const std::vector<int>& widths = column_widths();
966  int j = -1, j_end = widths.size();
967  for(x -= location().x; x >= 0; x -= widths[j]) {
968  if(++j == j_end) {
969  return -1;
970  }
971  }
972  return j;
973 }
974 
975 int menu::hit_heading(int x, int y) const
976 {
977  const std::size_t height = heading_height();
978  const SDL_Rect& loc = inner_location();
979  if(y >= loc.y && static_cast<std::size_t>(y) < loc.y + height) {
980  return hit_column(x);
981  } else {
982  return -1;
983  }
984 }
985 
986 SDL_Rect menu::get_item_rect(int item) const
987 {
989 }
990 
991 SDL_Rect menu::get_item_rect_internal(std::size_t item) const
992 {
993  unsigned int first_item_on_screen = get_position();
994  if (item < first_item_on_screen ||
995  item >= first_item_on_screen + max_items_onscreen()) {
996  return sdl::empty_rect;
997  }
998 
999  const std::map<int,SDL_Rect>::const_iterator i = itemRects_.find(item);
1000  if(i != itemRects_.end())
1001  return i->second;
1002 
1003  const SDL_Rect& loc = inner_location();
1004 
1005  int y = loc.y + heading_height();
1006  if (item != first_item_on_screen) {
1007  const SDL_Rect& prev = get_item_rect_internal(item-1);
1008  y = prev.y + prev.h;
1009  }
1010 
1011  rect res(loc.x, y, loc.w, get_item_height(item));
1012 
1013  const point canvas_size = video::game_canvas_size();
1014 
1015  if(res.x > canvas_size.x) {
1016  return sdl::empty_rect;
1017  } else if(res.x + res.w > canvas_size.x) {
1018  res.w = canvas_size.x - res.x;
1019  }
1020 
1021  if(res.y > canvas_size.y) {
1022  return sdl::empty_rect;
1023  } else if(res.y + res.h > canvas_size.y) {
1024  res.h = canvas_size.y - res.y;
1025  }
1026 
1027  //only insert into the cache if the menu's co-ordinates have
1028  //been initialized
1029  if (loc.x > 0 && loc.y > 0)
1030  itemRects_.emplace(item, res);
1031 
1032  return res;
1033 }
1034 
1035 std::size_t menu::get_item_height_internal(const std::vector<std::string>& item) const
1036 {
1037  std::size_t res = 0;
1038  for(std::vector<std::string>::const_iterator i = item.begin(); i != item.end(); ++i) {
1039  SDL_Rect rect = style_->item_size(*i);
1040  res = std::max<int>(rect.h,res);
1041  }
1042 
1043  return res;
1044 }
1045 
1046 std::size_t menu::heading_height() const
1047 {
1048  if(heading_height_ == -1) {
1050  }
1051 
1052  return std::min<unsigned int>(heading_height_,max_height_);
1053 }
1054 
1055 std::size_t menu::get_item_height(int) const
1056 {
1057  if(item_height_ != -1)
1058  return std::size_t(item_height_);
1059 
1060  std::size_t max_height = 0;
1061  for(std::size_t n = 0; n != items_.size(); ++n) {
1062  max_height = std::max<int>(max_height,get_item_height_internal(items_[n].fields));
1063  }
1064 
1065  return item_height_ = max_height;
1066 }
1067 
1068 void menu::invalidate_row(std::size_t id)
1069 {
1070  if(id >= items_.size()) {
1071  return;
1072  }
1073 
1075 }
1076 
1077 void menu::invalidate_row_pos(std::size_t pos)
1078 {
1079  if(pos >= items_.size()) {
1080  return;
1081  }
1082 
1083  invalidate_row(items_[pos].id);
1084 }
1085 
1087 {
1088  rect heading_rect = inner_location();
1089  heading_rect.h = heading_height();
1090  queue_redraw(heading_rect);
1091 }
1092 
1093 }
map_location prev
Definition: astarsearch.cpp:66
basic_sorter & set_id_sort(int column)
Definition: menu.cpp:60
basic_sorter & set_numeric_sort(int column)
Definition: menu.cpp:54
basic_sorter & set_position_sort(int column, const std::vector< int > &pos)
Definition: menu.cpp:75
virtual bool less(int column, const item &row1, const item &row2) const
Definition: menu.cpp:92
basic_sorter & set_redirect_sort(int column, int to)
Definition: menu.cpp:66
virtual bool column_sortable(int column) const
Definition: menu.cpp:81
basic_sorter & set_alpha_sort(int column)
Definition: menu.cpp:48
virtual bool column_sortable(int column) const =0
std::size_t get_thickness() const
Definition: menu_style.cpp:58
void adjust_image_bounds(int &w, int &h) const
Definition: menu_style.cpp:66
std::size_t get_font_size() const
Definition: menu_style.cpp:56
virtual SDL_Rect item_size(const std::string &item) const
Definition: menu.cpp:728
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
std::size_t get_cell_padding() const
Definition: menu_style.cpp:57
virtual void init()
Definition: menu.hpp:42
virtual void draw_row(menu &menu_ref, const std::size_t row_index, const SDL_Rect &rect, ROW_TYPE type)
Definition: menu.cpp:788
void reset_selection()
Definition: menu.cpp:507
bool requires_event_focus(const SDL_Event *event=nullptr) const override
Definition: menu.cpp:546
void do_sort()
Definition: menu.cpp:229
int max_items_
Definition: menu.hpp:227
int process()
Definition: menu.cpp:663
void draw_contents() override
Definition: menu.cpp:937
bool show_result_
Definition: menu.hpp:246
void move_selection_up(std::size_t dep)
Definition: menu.cpp:472
bool click_selects_
Definition: menu.hpp:241
int heading_height_
Definition: menu.hpp:236
SDL_Rect get_item_rect_internal(std::size_t pos) const
Definition: menu.cpp:991
int highlight_heading_
Definition: menu.hpp:285
void set_sorter(sorter *s)
Definition: menu.cpp:696
void sort_by(int column)
Definition: menu.cpp:707
const sorter * sorter_
Definition: menu.hpp:282
void set_max_width(const int new_max_width)
Definition: menu.cpp:406
bool out_
Definition: menu.hpp:242
std::size_t selected_
Definition: menu.hpp:240
void recalculate_pos()
Definition: menu.cpp:250
int item_height_
Definition: menu.hpp:227
void set_selection_pos(std::size_t pos, bool silent=false, SELECTION_MOVE_VIEWPORT move_viewport=MOVE_VIEWPORT)
Definition: menu.cpp:451
void set_max_height(const int new_max_height)
Set a new max height for this menu.
Definition: menu.cpp:398
std::size_t get_item_height_internal(const std::vector< std::string > &item) const
Definition: menu.cpp:1035
std::vector< std::size_t > item_pos_
Definition: menu.hpp:233
void clear_item(int item)
Definition: menu.cpp:840
bool last_was_doubleclick_
Definition: menu.hpp:277
void fill_items(const std::vector< std::string > &items, bool strip_spaces)
Set new items to show.
Definition: menu.cpp:174
std::map< int, SDL_Rect > itemRects_
Definition: menu.hpp:255
void adjust_viewport_to_selection()
Definition: menu.cpp:444
std::vector< std::string > heading_
Definition: menu.hpp:235
bool item_ends_with_image(const std::string &item) const
Definition: menu.cpp:821
bool use_ellipsis_
Definition: menu.hpp:280
void invalidate_row_pos(std::size_t pos)
Definition: menu.cpp:1077
int sortby_
Definition: menu.hpp:283
static style & default_style
Definition: menu.hpp:95
void column_widths_item(const std::vector< std::string > &row, std::vector< int > &widths) const
Definition: menu.cpp:807
SELECTION_MOVE_VIEWPORT
Definition: menu.hpp:298
@ NO_MOVE_VIEWPORT
Definition: menu.hpp:298
@ MOVE_VIEWPORT
Definition: menu.hpp:298
int selection() const
Definition: menu.cpp:301
void set_inner_location(const SDL_Rect &rect) override
Definition: menu.cpp:310
style * style_
Definition: menu.hpp:208
void set_heading(const std::vector< std::string > &heading)
Definition: menu.cpp:356
~menu()
Default implementation, but defined out-of-line for efficiency reasons.
Definition: menu.cpp:170
bool previous_button_
Definition: menu.hpp:243
int hit_column(int x) const
Definition: menu.cpp:963
virtual void handle_event(const SDL_Event &event) override
Definition: menu.cpp:579
void update_size()
Definition: menu.cpp:274
void move_selection_down(std::size_t dep)
Definition: menu.cpp:477
bool double_clicked_
Definition: menu.hpp:248
int hit(int x, int y) const
Definition: menu.cpp:949
const std::vector< int > & column_widths() const
Definition: menu.cpp:828
void move_selection_keeping_viewport(std::size_t id)
Definition: menu.cpp:500
std::size_t max_items_onscreen() const
Definition: menu.cpp:414
void key_press(SDL_Keycode key)
Definition: menu.cpp:512
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
std::size_t get_item_height(int item) const
Definition: menu.cpp:1055
void set_click_selects(bool value)
Definition: menu.cpp:680
virtual void erase_item(std::size_t index)
Definition: menu.cpp:327
virtual void draw_row(const std::size_t row_index, const SDL_Rect &rect, ROW_TYPE type)
Definition: menu.cpp:848
bool double_clicked()
Definition: menu.cpp:673
bool silent_
Definition: menu.hpp:209
void move_selection_to(std::size_t id, bool silent=false, SELECTION_MOVE_VIEWPORT move_viewport=MOVE_VIEWPORT)
Definition: menu.cpp:484
void assert_pos()
Definition: menu.cpp:259
void update_scrollbar_grip_height()
Definition: menu.cpp:268
void scroll(unsigned int pos) override
Definition: menu.cpp:690
int hit_heading(int x, int y) const
Definition: menu.cpp:975
void set_numeric_keypress_selection(bool value)
Definition: menu.cpp:685
int max_height_
Definition: menu.hpp:226
bool sortreversed_
Definition: menu.hpp:284
void move_selection(std::size_t id)
Definition: menu.cpp:492
SDL_Rect get_item_rect(int item) const
Definition: menu.cpp:986
std::vector< item > items_
Definition: menu.hpp:232
std::size_t heading_height() const
Definition: menu.cpp:1046
int max_width_
Definition: menu.hpp:226
bool num_selects_
variable which determines whether a numeric keypress should select an item on the dialog
Definition: menu.hpp:272
ROW_TYPE
Definition: menu.hpp:35
@ SELECTED_ROW
Definition: menu.hpp:35
@ NORMAL_ROW
Definition: menu.hpp:35
@ HEADING_ROW
Definition: menu.hpp:35
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 invalidate_heading()
Definition: menu.cpp:1086
void invalidate_row(std::size_t id)
Definition: menu.cpp:1068
bool ignore_next_doubleclick_
Definition: menu.hpp:276
std::vector< int > column_widths_
Definition: menu.hpp:238
void change_item(int pos1, int pos2, const std::string &str)
Definition: menu.cpp:316
unsigned get_max_position() const
Definition: scrollarea.cpp:86
bool has_scrollbar() const
Definition: scrollarea.cpp:33
void set_shown_size(unsigned h)
Definition: scrollarea.cpp:106
void adjust_position(unsigned pos)
Definition: scrollarea.cpp:96
unsigned get_position() const
Definition: scrollarea.cpp:81
void set_full_size(unsigned h)
Definition: scrollarea.cpp:113
void set_position(unsigned pos)
Definition: scrollarea.cpp:91
rect inner_location() const
Definition: scrollarea.cpp:134
virtual void handle_event(const SDL_Event &event)
Definition: scrollarea.cpp:147
unsigned scrollbar_width() const
Definition: scrollarea.cpp:142
bool focus(const SDL_Event *event)
Definition: widget.cpp:136
const std::string & id() const
Definition: widget.cpp:198
void set_measurements(int w, int h)
Definition: widget.cpp:108
const rect & location() const
Definition: widget.cpp:123
int width() const
Definition: widget.cpp:113
void queue_redraw()
Indicate that the widget should be redrawn.
Definition: widget.cpp:215
int height() const
Definition: widget.cpp:118
void set_focus(bool focus)
Definition: widget.cpp:128
bool hidden() const
Definition: widget.cpp:161
bool mouse_locked() const
Definition: widget.cpp:64
bool focus_
Definition: widget.hpp:91
Generic locator abstracting the location of an image.
Definition: picture.hpp:64
Wrapper class to encapsulate creation and management of an SDL_Texture.
Definition: texture.hpp:33
int w() const
The draw-space width of the texture, in pixels.
Definition: texture.hpp:105
int h() const
The draw-space height of the texture, in pixels.
Definition: texture.hpp:114
Drawing functions, for drawing things on the screen.
#define DOUBLE_CLICK_EVENT
Definition: events.hpp:24
std::size_t i
Definition: function.cpp:968
int w
bool current_language_rtl()
Definition: language.cpp:55
New lexcical_cast header.
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:51
void blit(const texture &tex, const SDL_Rect &dst)
Draws a texture, or part of a texture, at the given location.
Definition: draw.cpp:311
void rect(const SDL_Rect &rect)
Draw a rectangle.
Definition: draw.cpp:151
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.
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.
const color_t NORMAL_COLOR
const std::string menu_select
const std::string button_press
General purpose widgets.
std::pair< std::string, unsigned > item
Definition: help_impl.hpp:414
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:985
point get_size(const locator &i_locator, bool skip_cache)
Returns the width and height of an image.
Definition: picture.cpp:827
const std::vector< std::string > items
constexpr const SDL_Rect empty_rect
Definition: rect.hpp:30
void play_UI_sound(const std::string &files)
Definition: sound.cpp:1066
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
@ REMOVE_EMPTY
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...
bool chars_less_insensitive(char a, char b)
Definition: general.hpp:25
std::vector< std::string > split(const config_attribute_value &val)
point game_canvas_size()
The size of the game canvas, in drawing coordinates / game pixels.
Definition: video.cpp:438
rect game_canvas()
The game canvas area, in drawing coordinates.
Definition: video.cpp:433
Contains the SDL_Rect helper code.
Transitional API for porting SDL_ttf-based code to Pango.
The basic class for representing 8-bit RGB or RGBA colour values.
Definition: color.hpp:59
std::vector< std::string > fields
Definition: menu.hpp:107
std::size_t id
Definition: menu.hpp:108
Holds a 2D point.
Definition: point.hpp:25
An abstract description of a rectangle with integer coordinates.
Definition: rect.hpp:47
mock_char c
static map_location::DIRECTION n
static map_location::DIRECTION s
char const IMG_TEXT_SEPARATOR
char const HEADING_PREFIX
char const IMAGE_PREFIX
char const COLUMN_SEPARATOR
char const DEFAULT_ITEM
bool is_wml_separator(char c)
#define h
#define a
#define b