The Battle for Wesnoth  1.15.2+dev
button.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2003 - 2018 by David White <dave@whitevine.net>
3  Part of the Battle for Wesnoth Project https://www.wesnoth.org/
4 
5  This program is free software; you can redistribute it and/or modify
6  it under the terms of the GNU General Public License as published by
7  the Free Software Foundation; either version 2 of the License, or
8  (at your option) any later version.
9  This program is distributed in the hope that it will be useful,
10  but WITHOUT ANY WARRANTY.
11 
12  See the COPYING file for more details.
13 */
14 
15 #define GETTEXT_DOMAIN "wesnoth-lib"
16 
17 #include "widgets/button.hpp"
18 
19 #include "filesystem.hpp"
20 #include "font/sdl_ttf.hpp"
21 #include "game_config.hpp"
22 #include "game_errors.hpp"
23 #include "picture.hpp"
24 #include "log.hpp"
25 #include "font/marked-up_text.hpp"
26 #include "font/standard_colors.hpp"
27 #include "sdl/rect.hpp"
29 #include "sound.hpp"
30 #include "video.hpp"
31 #include "wml_separators.hpp"
32 
33 #include <boost/algorithm/string/predicate.hpp>
34 
35 static lg::log_domain log_display("display");
36 #define ERR_DP LOG_STREAM(err, log_display)
37 
38 namespace gui {
39 
44 
45 button::button(CVideo& video, const std::string& label, button::TYPE type,
46  std::string button_image_name, SPACE_CONSUMPTION spacing,
47  const bool auto_join, std::string overlay_image)
48  : widget(video, auto_join), type_(type),
49  label_text_(label),
50  image_(nullptr), pressedImage_(nullptr), activeImage_(nullptr), pressedActiveImage_(nullptr),
51  disabledImage_(nullptr), pressedDisabledImage_(nullptr),
52  overlayImage_(nullptr), overlayPressedImage_(nullptr), overlayActiveImage_(nullptr),
53  state_(NORMAL), pressed_(false),
54  spacing_(spacing), base_height_(0), base_width_(0),
55  button_image_name_(), button_overlay_image_name_(overlay_image),
56  button_image_path_suffix_()
57 {
58  if (button_image_name.empty()) {
59 
60  switch (type_) {
61  case TYPE_PRESS:
62  button_image_name_ = "buttons/button_normal/button_H22";
63  break;
64  case TYPE_TURBO:
65  button_image_name_ = "buttons/button_menu/menu_button_copper_H20";
66  break;
67  case TYPE_CHECK:
68  button_image_name_ = "buttons/checkbox";
69  break;
70  case TYPE_RADIO:
71  button_image_name_ = "buttons/radiobox";
72  break;
73  default:
74  break;
75  }
76  } else {
77  button_image_name_ = "buttons/" + button_image_name;
78  }
79 
80  load_images();
81 }
82 
84 
85  std::string size_postfix;
86 
87  switch (location().h) {
88  case 25:
89  size_postfix = "_25";
90  break;
91  case 30:
92  size_postfix = "_30";
93  break;
94  case 60:
95  size_postfix = "_60";
96  break;
97  default:
98  break;
99  }
100 
102  surface pressed_image(image::get_image(button_image_name_ + "-pressed.png"+ button_image_path_suffix_));
104  surface disabled_image;
105  if (filesystem::file_exists(game_config::path + "/images/" + button_image_name_ + "-disabled.png"))
106  disabled_image = image::get_image(button_image_name_ + "-disabled.png"+ button_image_path_suffix_);
107  surface pressed_disabled_image, pressed_active_image, touched_image;
108 
109  if (!button_overlay_image_name_.empty()) {
110 
111  if (button_overlay_image_name_.length() > size_postfix.length() &&
113  button_overlay_image_name_.resize(button_overlay_image_name_.length() - size_postfix.length());
114  }
115 
118 
119  if (filesystem::file_exists(game_config::path + "/images/" + button_overlay_image_name_ + size_postfix + "-active.png"))
121 
122  if (filesystem::file_exists(game_config::path + "/images/" + button_overlay_image_name_ + size_postfix + "-disabled.png"))
126 
127  if (filesystem::file_exists(game_config::path + "/images/" + button_overlay_image_name_ + size_postfix + "-disabled-pressed.png"))
131  } else {
132  overlayImage_ = nullptr;
133  }
134 
135  if (disabled_image == nullptr) {
136  disabled_image = image::get_image(button_image_name_ + ".png~GS()" + button_image_path_suffix_);
137  }
138 
139  if (!pressed_image)
140  pressed_image = button_image;
141 
142  if (!active_image)
143  active_image = button_image;
144 
145  if (type_ == TYPE_CHECK || type_ == TYPE_RADIO) {
146  touched_image = image::get_image(button_image_name_ + "-touched.png"+ button_image_path_suffix_);
147  if (!touched_image)
148  touched_image = pressed_image;
149 
150  pressed_active_image = image::get_image(button_image_name_ + "-active-pressed.png"+ button_image_path_suffix_);
151  if (!pressed_active_image)
152  pressed_active_image = pressed_image;
153 
154  if (filesystem::file_exists(game_config::path + "/images/" + button_image_name_ + size_postfix + "-disabled-pressed.png"))
155  pressed_disabled_image = image::get_image(button_image_name_ + "-disabled-pressed.png"+ button_image_path_suffix_);
156  if (!pressed_disabled_image)
157  pressed_disabled_image = image::get_image(button_image_name_ + "-pressed.png~GS()"+ button_image_path_suffix_);
158  }
159 
160  if (!button_image) {
161  std::string err_msg = "error initializing button images! file name: ";
162  err_msg += button_image_name_;
163  err_msg += ".png";
164  ERR_DP << err_msg << std::endl;
165  throw game::error(err_msg);
166  }
167 
168  base_height_ = button_image->h;
169  base_width_ = button_image->w;
170 
171  if (type_ != TYPE_IMAGE) {
173  }
174 
175  if(type_ == TYPE_PRESS || type_ == TYPE_TURBO) {
176  image_ = scale_surface(button_image,location().w,location().h);
177  pressedImage_ = scale_surface(pressed_image,location().w,location().h);
178  activeImage_ = scale_surface(active_image,location().w,location().h);
179  disabledImage_ = scale_surface(disabled_image,location().w,location().h);
180  } else {
181  image_ = scale_surface(button_image,button_image->w,button_image->h);
182  activeImage_ = scale_surface(active_image,button_image->w,button_image->h);
183  disabledImage_ = scale_surface(disabled_image,button_image->w,button_image->h);
184  pressedImage_ = scale_surface(pressed_image,button_image->w,button_image->h);
185  if (type_ == TYPE_CHECK || type_ == TYPE_RADIO) {
186  pressedDisabledImage_ = scale_surface(pressed_disabled_image,button_image->w,button_image->h);
187  pressedActiveImage_ = scale_surface(pressed_active_image, button_image->w, button_image->h);
188  touchedImage_ = scale_surface(touched_image, button_image->w, button_image->h);
189  }
190  }
191 
192  if (type_ == TYPE_IMAGE){
193  calculate_size();
194  }
195 }
196 
198 {
199 }
200 
202 {
203  if (type_ == TYPE_IMAGE){
204  SDL_Rect loc_image = location();
205  loc_image.h = image_->h;
206  loc_image.w = image_->w;
207  set_location(loc_image);
208  return;
209  }
210  const SDL_Rect& loc = location();
211  bool change_size = loc.h == 0 || loc.w == 0;
212 
213  if (!change_size) {
214  unsigned w = loc.w - (type_ == TYPE_PRESS || type_ == TYPE_TURBO ? horizontal_padding : checkbox_horizontal_padding + base_width_);
215  if (type_ != TYPE_IMAGE)
216  {
217  int fs = font_size;
218  int style = TTF_STYLE_NORMAL;
219  std::string::const_iterator i_beg = label_text_.begin(), i_end = label_text_.end(),
220  i = font::parse_markup(i_beg, i_end, &fs, nullptr, &style);
221  if (i != i_end) {
222  std::string tmp(i, i_end);
223  label_text_.erase(i - i_beg, i_end - i_beg);
224  label_text_ += font::make_text_ellipsis(tmp, fs, w, style);
225  }
226  }
227  }
228 
229  if (type_ != TYPE_IMAGE){
230  textRect_ = font::draw_text(nullptr, video().screen_area(), font_size,
232  }
233 
234  // TODO: There's a weird text clipping bug, allowing the code below to run fixes it.
235  // The proper fix should possibly be in the draw_contents() function.
236 #if 0
237  if (!change_size)
238  return;
239 #endif
240 
241  set_height(std::max(textRect_.h+vertical_padding,base_height_));
242  if(type_ == TYPE_PRESS || type_ == TYPE_TURBO) {
243  if(spacing_ == MINIMUM_SPACE) {
244  set_width(textRect_.w + horizontal_padding);
245  } else {
246  set_width(std::max(textRect_.w+horizontal_padding,base_width_));
247  }
248  } else {
249  if(label_text_.empty()) {
251  } else {
252  set_width(checkbox_horizontal_padding + textRect_.w + base_width_);
253  }
254  }
255 }
256 
258 {
259  if (type_ != TYPE_CHECK && type_ != TYPE_RADIO && type_ != TYPE_IMAGE)
260  return;
261  STATE new_state;
262 
263  if (check) {
264  new_state = (state_ == ACTIVE || state_ == PRESSED_ACTIVE)? PRESSED_ACTIVE : PRESSED;
265  } else {
266  new_state = (state_ == ACTIVE || state_ == PRESSED_ACTIVE)? ACTIVE : NORMAL;
267  }
268 
269  if (state_ != new_state) {
270  state_ = new_state;
271  set_dirty();
272  }
273 }
274 
275 void button::set_active(bool active)
276 {
277  if ((state_ == NORMAL) && active) {
278  state_ = ACTIVE;
279  set_dirty();
280  } else if ((state_ == ACTIVE) && !active) {
281  state_ = NORMAL;
282  set_dirty();
283  }
284 }
285 
286 bool button::checked() const
287 {
289 }
290 
291 void button::enable(bool new_val)
292 {
293  if(new_val != enabled())
294  {
295  pressed_ = false;
296  // check buttons should keep their state
297  if(type_ != TYPE_CHECK) {
298  state_ = NORMAL;
299  }
300  widget::enable(new_val);
301  }
302 }
303 
305 {
306  surface image = image_;
307  const int image_w = image_->w;
308 
309  int offset = 0;
310  switch(state_) {
311  case ACTIVE:
312  image = activeImage_;
313  break;
314  case PRESSED:
315  image = pressedImage_;
316  if (type_ == TYPE_PRESS)
317  offset = 1;
318  break;
319  case PRESSED_ACTIVE:
320  image = pressedActiveImage_;
321  break;
322  case TOUCHED_NORMAL:
323  case TOUCHED_PRESSED:
324  image = touchedImage_;
325  break;
326  default:
327  break;
328  }
329 
330  const SDL_Rect& loc = location();
331  SDL_Rect clipArea = loc;
332  const int texty = loc.y + loc.h / 2 - textRect_.h / 2 + offset;
333  int textx;
334 
335  if (type_ != TYPE_CHECK && type_ != TYPE_RADIO && type_ != TYPE_IMAGE)
336  textx = loc.x + image->w / 2 - textRect_.w / 2 + offset;
337  else {
338  clipArea.w += image_w + checkbox_horizontal_padding;
339  textx = loc.x + image_w + checkbox_horizontal_padding / 2;
340  }
341 
342  color_t button_color = font::BUTTON_COLOR;
343 
344  if (!enabled()) {
345 
346  if (state_ == PRESSED || state_ == PRESSED_ACTIVE)
347  image = pressedDisabledImage_;
348  else image = disabledImage_;
349 
350  button_color = font::GRAY_COLOR;
351  }
352 
353  if (overlayImage_) {
354 
356 
357  if (overlayPressedImage_) {
358  switch (state_) {
359  case ACTIVE:
361  noverlay = &overlayActiveImage_;
362  break;
363  case PRESSED:
364  case PRESSED_ACTIVE:
365  case TOUCHED_NORMAL:
366  case TOUCHED_PRESSED:
368  break;
369  default:
370  break;
371  }
372  }
373 
374  surface nimage = image.clone();
375  sdl_blit(*noverlay, nullptr, nimage, nullptr);
376  image = nimage;
377  }
378 
379  video().blit_surface(loc.x, loc.y, image);
380  if (type_ != TYPE_IMAGE){
381  clipArea.x += offset;
382  clipArea.y += offset;
383  clipArea.w -= 2*offset;
384  clipArea.h -= 2*offset;
385  font::draw_text(&video(), clipArea, font_size, button_color, label_text_, textx, texty);
386  }
387 }
388 
389 bool button::hit(int x, int y) const
390 {
391  return sdl::point_in_rect(x,y,location());
392 }
393 
394 static bool is_valid_image(const std::string& str) { return !str.empty() && str[0] != IMAGE_PREFIX; }
395 
396 void button::set_image(const std::string& image_file)
397 {
398  if(!is_valid_image(image_file)) {
399  return;
400  }
401 
402  button_image_name_ = "buttons/" + image_file;
403  load_images();
404  set_dirty();
405 }
406 
407 void button::set_overlay(const std::string& image_file)
408 {
409  // We allow empty paths for overlays
410  if(image_file[0] == IMAGE_PREFIX) {
411  return;
412  }
413 
414  button_overlay_image_name_ = image_file;
415  load_images();
416  set_dirty();
417 }
418 
419 void button::set_label(const std::string& val)
420 {
421  label_text_ = val;
422 
423  //if we have a list of items, use the first one that isn't an image
424  if (std::find(label_text_.begin(), label_text_.end(), COLUMN_SEPARATOR) != label_text_.end()) {
425  const std::vector<std::string>& items = utils::split(label_text_, COLUMN_SEPARATOR);
426  const std::vector<std::string>::const_iterator i = std::find_if(items.begin(),items.end(),is_valid_image);
427  if(i != items.end()) {
428  label_text_ = *i;
429  }
430  }
431 
432  calculate_size();
433 
434  set_dirty(true);
435 }
436 
437 void button::mouse_motion(const SDL_MouseMotionEvent& event)
438 {
439  if (hit(event.x, event.y)) {
440  // the cursor is over the widget
441  if (state_ == NORMAL)
442  state_ = ACTIVE;
443  else if (state_ == PRESSED && (type_ == TYPE_CHECK || type_ == TYPE_RADIO))
445  } else {
446  // the cursor is not over the widget
447 
448  if (type_ == TYPE_CHECK || type_ == TYPE_RADIO) {
449 
450  switch (state_) {
451  case TOUCHED_NORMAL:
452  state_ = NORMAL;
453  break;
454  case TOUCHED_PRESSED:
455  state_ = PRESSED;
456  break;
457  case PRESSED_ACTIVE:
458  state_ = PRESSED;
459  break;
460  case ACTIVE:
461  state_ = NORMAL;
462  break;
463  default:
464  break;
465  }
466  } else if ((type_ != TYPE_IMAGE) || state_ != PRESSED)
467  state_ = NORMAL;
468  }
469 }
470 
471 void button::mouse_down(const SDL_MouseButtonEvent& event)
472 {
473  if (hit(event.x, event.y) && event.button == SDL_BUTTON_LEFT) {
474 
475  switch (type_) {
476  case TYPE_RADIO:
477  case TYPE_CHECK:
478  if (state_ == PRESSED_ACTIVE)
480  else if (state_ == ACTIVE)
482  break;
483  case TYPE_TURBO:
485  state_ = PRESSED;
486  break;
487  default:
488  state_ = PRESSED;
489  break;
490  }
491  }
492 }
493 
495  state_ = NORMAL;
496  draw_contents();
497 }
498 
499 void button::mouse_up(const SDL_MouseButtonEvent& event)
500 {
501  if (!(hit(event.x, event.y) && event.button == SDL_BUTTON_LEFT))
502  return;
503 
504  // the user has stopped pressing the mouse left button while on the widget
505  switch (type_) {
506  case TYPE_CHECK:
507 
508  switch (state_) {
509  case TOUCHED_NORMAL:
511  pressed_ = true;
512  break;
513  case TOUCHED_PRESSED:
514  state_ = ACTIVE;
515  pressed_ = true;
516  break;
517  default:
518  break;
519  }
521  break;
522  case TYPE_RADIO:
525  pressed_ = true;
526  // exit(0);
528  }
529  //} else if (state_ == TOUCHED_PRESSED) {
530  // state_ = PRESSED_ACTIVE;
531  //}
532  break;
533  case TYPE_PRESS:
534  if (state_ == PRESSED) {
535  state_ = ACTIVE;
536  pressed_ = true;
538  }
539  break;
540  case TYPE_TURBO:
541  state_ = ACTIVE;
542  break;
543  case TYPE_IMAGE:
544  pressed_ = true;
546  break;
547  }
548 }
549 
550 void button::handle_event(const SDL_Event& event)
551 {
553 
554  if (hidden() || !enabled())
555  return;
556 
557  STATE start_state = state_;
558 
559  if (!mouse_locked())
560  {
561  switch(event.type) {
562  case SDL_MOUSEBUTTONDOWN:
563  mouse_down(event.button);
564  break;
565  case SDL_MOUSEBUTTONUP:
566  mouse_up(event.button);
567  break;
568  case SDL_MOUSEMOTION:
569  mouse_motion(event.motion);
570  break;
571  default:
572  return;
573  }
574  }
575 
576  if (start_state != state_)
577  set_dirty(true);
578 }
579 
581 {
582  if (type_ != TYPE_TURBO) {
583  const bool res = pressed_;
584  pressed_ = false;
585  return res;
586  } else
588 }
589 
590 }
const color_t BUTTON_COLOR
surface get_image(const image::locator &i_locator, TYPE type)
function to get the surface corresponding to an image.
Definition: picture.cpp:833
#define ERR_DP
Definition: button.cpp:36
std::string button_image_name_
Definition: button.hpp:91
bool enabled() const
Definition: widget.cpp:202
void set_check(bool check)
Definition: button.cpp:257
virtual void mouse_up(const SDL_MouseButtonEvent &event)
Definition: button.cpp:499
button(CVideo &video, const std::string &label, TYPE type=TYPE_PRESS, std::string button_image="", SPACE_CONSUMPTION spacing=DEFAULT_SPACE, const bool auto_join=true, std::string overlay_image="")
Definition: button.cpp:45
std::string button_image_path_suffix_
Definition: button.hpp:93
static void check(LexState *ls, int c)
Definition: lparser.cpp:106
bool pressed_
Definition: button.hpp:85
virtual void enable(bool new_val=true)
Definition: widget.cpp:194
const color_t GRAY_COLOR
bool hidden() const
Definition: widget.cpp:188
int base_height_
Definition: button.hpp:89
void set_label(const std::string &val)
Definition: button.cpp:419
surface pressedActiveImage_
Definition: button.hpp:76
std::string::const_iterator parse_markup(std::string::const_iterator i1, std::string::const_iterator i2, int *font_size, color_t *color, int *style)
Parses the markup-tags at the front of a string.
bool hit(int x, int y) const
Definition: button.cpp:389
void set_image(const std::string &image_file_base)
Definition: button.cpp:396
static l_noret error(LoadState *S, const char *why)
Definition: lundump.cpp:39
surface pressedImage_
Definition: button.hpp:76
Definition: video.hpp:31
virtual void enable(bool new_val=true)
Definition: button.cpp:291
static bool file_exists(const bfs::path &fpath)
Definition: filesystem.cpp:266
bool ends_with(const std::string &str, const std::string &suffix)
surface overlayPressedImage_
Definition: button.hpp:76
surface activeImage_
Definition: button.hpp:76
const int horizontal_padding
Definition: button.cpp:41
General purpose widgets.
void set_width(int w)
Definition: widget.cpp:109
#define h
surface disabledImage_
Definition: button.hpp:76
const std::vector< std::string > items
surface scale_surface(const surface &surf, int w, int h)
Scale a surface using alpha-weighted modified bilinear filtering Note: causes artifacts with alpha gr...
Definition: utils.cpp:196
void blit_surface(int x, int y, surface surf, SDL_Rect *srcrect=nullptr, SDL_Rect *clip_rect=nullptr)
Draws a surface directly onto the screen framebuffer.
Definition: video.cpp:163
virtual void mouse_down(const SDL_MouseButtonEvent &event)
Definition: button.cpp:471
void load_images()
Definition: button.cpp:83
-file util.hpp
virtual void draw_contents()
Definition: button.cpp:304
std::vector< std::string > split(const std::string &val, const char c, const int flags)
Splits a (comma-)separated string into a vector of pieces.
surface clone() const
Makes a copy of this surface.
Definition: surface.cpp:75
char const IMAGE_PREFIX
int base_width_
Definition: button.hpp:89
void release()
Definition: button.cpp:494
const int SIZE_NORMAL
Definition: constants.cpp:19
void set_dirty(bool dirty=true)
Definition: widget.cpp:207
surface overlayImage_
Definition: button.hpp:76
virtual void handle_event(const SDL_Event &)
Definition: widget.cpp:330
std::string button_overlay_image_name_
Definition: button.hpp:92
static bool is_valid_image(const std::string &str)
Definition: button.cpp:394
void calculate_size()
Definition: button.cpp:201
const SDL_Rect & location() const
Definition: widget.cpp:134
SPACE_CONSUMPTION spacing_
Definition: button.hpp:87
TYPE type_
Definition: button.hpp:66
surface touchedImage_
Definition: button.hpp:76
virtual void mouse_motion(const SDL_MouseMotionEvent &event)
Definition: button.cpp:437
SPACE_CONSUMPTION
Definition: button.hpp:36
void set_overlay(const std::string &image_file_base)
Definition: button.cpp:407
std::string path
Definition: game_config.cpp:39
bool pressed()
Definition: button.cpp:580
virtual void set_location(const SDL_Rect &rect)
Definition: widget.cpp:75
bool checked() const
Definition: button.cpp:286
surface image_
Definition: button.hpp:76
bool point_in_rect(int x, int y, const SDL_Rect &rect)
Tests whether a point is inside a rectangle.
Definition: rect.cpp:22
std::size_t i
Definition: function.cpp:933
virtual void handle_event(const SDL_Event &event)
Definition: button.cpp:550
Declarations for File-IO.
int w
bool mouse_locked() const
Definition: widget.cpp:62
surface overlayPressedDisabledImage_
Definition: button.hpp:76
CVideo & video() const
Definition: widget.hpp:83
std::string make_text_ellipsis(const std::string &text, int font_size, int max_width, int style)
If the text exceeds the specified max width, end it with an ellipsis (...)
Definition: sdl_ttf.cpp:443
const std::string button_press
surface overlayActiveImage_
Definition: button.hpp:76
const int vertical_padding
Definition: button.cpp:43
std::string label_text_
Definition: button.hpp:74
SDL_Rect textRect_
Definition: button.hpp:80
Contains the SDL_Rect helper code.
char const COLUMN_SEPARATOR
virtual ~button()
Default implementation, but defined out-of-line for efficiency reasons.
Definition: button.cpp:197
bool find(E event, F functor)
Tests whether an event handler is available.
SDL_Rect draw_text(surface &dst, const SDL_Rect &area, int size, const color_t &color, const std::string &txt, int x, int y, bool use_tooltips, int style)
Function to draw text on a surface.
this module manages the cache of images.
void play_UI_sound(const std::string &files)
Definition: sound.cpp:1019
Standard logging facilities (interface).
surface pressedDisabledImage_
Definition: button.hpp:76
STATE state_
Definition: button.hpp:83
void sdl_blit(const surface &src, SDL_Rect *src_rect, surface &dst, SDL_Rect *dst_rect)
Definition: utils.hpp:33
static lg::log_domain log_display("display")
void set_height(int h)
Definition: widget.cpp:114
const int font_size
Definition: button.cpp:40
surface overlayDisabledImage_
Definition: button.hpp:76
const int SIZE_SMALL
Definition: constants.cpp:23
void set_active(bool active)
Definition: button.cpp:275
const int checkbox_horizontal_padding
Definition: button.cpp:42
const std::string checkbox_release