The Battle for Wesnoth  1.15.0-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 http://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 "image.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 
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.assign((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_.assign(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.null())
140  pressed_image.assign(button_image);
141 
142  if (active_image.null())
143  active_image.assign(button_image);
144 
145  if (type_ == TYPE_CHECK || type_ == TYPE_RADIO) {
146  touched_image.assign(image::get_image(button_image_name_ + "-touched.png"+ button_image_path_suffix_));
147  if (touched_image.null())
148  touched_image.assign(pressed_image);
149 
150  pressed_active_image.assign(image::get_image(button_image_name_ + "-active-pressed.png"+ button_image_path_suffix_));
151  if (pressed_active_image.null())
152  pressed_active_image.assign(pressed_image);
153 
154  if (filesystem::file_exists(game_config::path + "/images/" + button_image_name_ + size_postfix + "-disabled-pressed.png"))
155  pressed_disabled_image.assign(image::get_image(button_image_name_ + "-disabled-pressed.png"+ button_image_path_suffix_));
156  if (pressed_disabled_image.null())
157  pressed_disabled_image = image::get_image(button_image_name_ + "-pressed.png~GS()"+ button_image_path_suffix_);
158  }
159 
160  if (button_image.null()) {
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_.assign(scale_surface(button_image,location().w,location().h));
177  pressedImage_.assign(scale_surface(pressed_image,location().w,location().h));
178  activeImage_.assign(scale_surface(active_image,location().w,location().h));
179  disabledImage_.assign(scale_surface(disabled_image,location().w,location().h));
180  } else {
181  image_.assign(scale_surface(button_image,button_image->w,button_image->h));
182  activeImage_.assign(scale_surface(active_image,button_image->w,button_image->h));
183  disabledImage_.assign(scale_surface(disabled_image,button_image->w,button_image->h));
184  pressedImage_.assign(scale_surface(pressed_image,button_image->w,button_image->h));
185  if (type_ == TYPE_CHECK || type_ == TYPE_RADIO) {
186  pressedDisabledImage_.assign(scale_surface(pressed_disabled_image,button_image->w,button_image->h));
187  pressedActiveImage_.assign(scale_surface(pressed_active_image, button_image->w, button_image->h));
188  touchedImage_.assign(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_.null()) {
354 
355  surface noverlay = make_neutral_surface(
357 
358  if (!overlayPressedImage_.null()) {
359  switch (state_) {
360  case ACTIVE:
361  if (!overlayActiveImage_.null())
363  break;
364  case PRESSED:
365  case PRESSED_ACTIVE:
366  case TOUCHED_NORMAL:
367  case TOUCHED_PRESSED:
368  noverlay = make_neutral_surface( enabled() ?
370  break;
371  default:
372  break;
373  }
374  }
375 
376  surface nimage = make_neutral_surface(image);
377  sdl_blit(noverlay, nullptr, nimage, nullptr);
378  image = nimage;
379  }
380 
381  video().blit_surface(loc.x, loc.y, image);
382  if (type_ != TYPE_IMAGE){
383  clipArea.x += offset;
384  clipArea.y += offset;
385  clipArea.w -= 2*offset;
386  clipArea.h -= 2*offset;
387  font::draw_text(&video(), clipArea, font_size, button_color, label_text_, textx, texty);
388  }
389 }
390 
391 bool button::hit(int x, int y) const
392 {
393  return sdl::point_in_rect(x,y,location());
394 }
395 
396 static bool is_valid_image(const std::string& str) { return !str.empty() && str[0] != IMAGE_PREFIX; }
397 
398 void button::set_image(const std::string& image_file)
399 {
400  if(!is_valid_image(image_file)) {
401  return;
402  }
403 
404  button_image_name_ = "buttons/" + image_file;
405  load_images();
406  set_dirty();
407 }
408 
409 void button::set_overlay(const std::string& image_file)
410 {
411  // We allow empty paths for overlays
412  if(image_file[0] == IMAGE_PREFIX) {
413  return;
414  }
415 
416  button_overlay_image_name_ = image_file;
417  load_images();
418  set_dirty();
419 }
420 
422 {
423  label_text_ = val;
424 
425  //if we have a list of items, use the first one that isn't an image
426  if (std::find(label_text_.begin(), label_text_.end(), COLUMN_SEPARATOR) != label_text_.end()) {
427  const std::vector<std::string>& items = utils::split(label_text_, COLUMN_SEPARATOR);
428  const std::vector<std::string>::const_iterator i = std::find_if(items.begin(),items.end(),is_valid_image);
429  if(i != items.end()) {
430  label_text_ = *i;
431  }
432  }
433 
434  calculate_size();
435 
436  set_dirty(true);
437 }
438 
439 void button::mouse_motion(const SDL_MouseMotionEvent& event)
440 {
441  if (hit(event.x, event.y)) {
442  // the cursor is over the widget
443  if (state_ == NORMAL)
444  state_ = ACTIVE;
445  else if (state_ == PRESSED && (type_ == TYPE_CHECK || type_ == TYPE_RADIO))
447  } else {
448  // the cursor is not over the widget
449 
450  if (type_ == TYPE_CHECK || type_ == TYPE_RADIO) {
451 
452  switch (state_) {
453  case TOUCHED_NORMAL:
454  state_ = NORMAL;
455  break;
456  case TOUCHED_PRESSED:
457  state_ = PRESSED;
458  break;
459  case PRESSED_ACTIVE:
460  state_ = PRESSED;
461  break;
462  case ACTIVE:
463  state_ = NORMAL;
464  break;
465  default:
466  break;
467  }
468  } else if ((type_ != TYPE_IMAGE) || state_ != PRESSED)
469  state_ = NORMAL;
470  }
471 }
472 
473 void button::mouse_down(const SDL_MouseButtonEvent& event)
474 {
475  if (hit(event.x, event.y) && event.button == SDL_BUTTON_LEFT) {
476 
477  switch (type_) {
478  case TYPE_RADIO:
479  case TYPE_CHECK:
480  if (state_ == PRESSED_ACTIVE)
482  else if (state_ == ACTIVE)
484  break;
485  case TYPE_TURBO:
487  state_ = PRESSED;
488  break;
489  default:
490  state_ = PRESSED;
491  break;
492  }
493  }
494 }
495 
497  state_ = NORMAL;
498  draw_contents();
499 }
500 
501 void button::mouse_up(const SDL_MouseButtonEvent& event)
502 {
503  if (!(hit(event.x, event.y) && event.button == SDL_BUTTON_LEFT))
504  return;
505 
506  // the user has stopped pressing the mouse left button while on the widget
507  switch (type_) {
508  case TYPE_CHECK:
509 
510  switch (state_) {
511  case TOUCHED_NORMAL:
513  pressed_ = true;
514  break;
515  case TOUCHED_PRESSED:
516  state_ = ACTIVE;
517  pressed_ = true;
518  break;
519  default:
520  break;
521  }
523  break;
524  case TYPE_RADIO:
527  pressed_ = true;
528  // exit(0);
530  }
531  //} else if (state_ == TOUCHED_PRESSED) {
532  // state_ = PRESSED_ACTIVE;
533  //}
534  break;
535  case TYPE_PRESS:
536  if (state_ == PRESSED) {
537  state_ = ACTIVE;
538  pressed_ = true;
540  }
541  break;
542  case TYPE_TURBO:
543  state_ = ACTIVE;
544  break;
545  case TYPE_IMAGE:
546  pressed_ = true;
548  break;
549  }
550 }
551 
552 void button::handle_event(const SDL_Event& event)
553 {
555 
556  if (hidden() || !enabled())
557  return;
558 
559  STATE start_state = state_;
560 
561  if (!mouse_locked())
562  {
563  switch(event.type) {
564  case SDL_MOUSEBUTTONDOWN:
565  mouse_down(event.button);
566  break;
567  case SDL_MOUSEBUTTONUP:
568  mouse_up(event.button);
569  break;
570  case SDL_MOUSEMOTION:
571  mouse_motion(event.motion);
572  break;
573  default:
574  return;
575  }
576  }
577 
578  if (start_state != state_)
579  set_dirty(true);
580 }
581 
583 {
584  if (type_ != TYPE_TURBO) {
585  const bool res = pressed_;
586  pressed_ = false;
587  return res;
588  } else
590 }
591 
592 }
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: image.cpp:1038
#define ERR_DP
Definition: button.cpp:36
std::string button_image_name_
Definition: button.hpp:91
bool enabled() const
Definition: widget.cpp:205
void set_check(bool check)
Definition: button.cpp:257
virtual void mouse_up(const SDL_MouseButtonEvent &event)
Definition: button.cpp:501
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::vector< char_t > string
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:197
const color_t GRAY_COLOR
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:273
bool hidden() const
Definition: widget.cpp:191
int base_height_
Definition: button.hpp:89
void set_label(const std::string &val)
Definition: button.cpp:421
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:391
void set_image(const std::string &image_file_base)
Definition: button.cpp:398
static l_noret error(LoadState *S, const char *why)
Definition: lundump.cpp:39
surface pressedImage_
Definition: button.hpp:76
Definition: video.hpp:36
virtual void enable(bool new_val=true)
Definition: button.cpp:291
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:112
#define h
surface disabledImage_
Definition: button.hpp:76
const std::vector< std::string > items
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:112
virtual void mouse_down(const SDL_MouseButtonEvent &event)
Definition: button.cpp:473
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.
char const IMAGE_PREFIX
int base_width_
Definition: button.hpp:89
bool null() const
Definition: surface.hpp:79
void release()
Definition: button.cpp:496
const int SIZE_NORMAL
Definition: constants.cpp:19
void set_dirty(bool dirty=true)
Definition: widget.cpp:210
surface overlayImage_
Definition: button.hpp:76
virtual void handle_event(const SDL_Event &)
Definition: widget.cpp:317
std::string button_overlay_image_name_
Definition: button.hpp:92
static bool is_valid_image(const std::string &str)
Definition: button.cpp:396
void calculate_size()
Definition: button.cpp:201
const SDL_Rect & location() const
Definition: widget.cpp:137
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:439
SPACE_CONSUMPTION
Definition: button.hpp:36
void set_overlay(const std::string &image_file_base)
Definition: button.cpp:409
std::string path
Definition: game_config.cpp:57
bool pressed()
Definition: button.cpp:582
virtual void set_location(const SDL_Rect &rect)
Definition: widget.cpp:79
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
surface make_neutral_surface(const surface &surf)
Definition: utils.cpp:71
std::size_t i
Definition: function.cpp:933
virtual void handle_event(const SDL_Event &event)
Definition: button.cpp:552
void sdl_blit(const surface &src, SDL_Rect *src_rect, surface &dst, SDL_Rect *dst_rect)
Definition: utils.hpp:33
Declarations for File-IO.
int w
bool mouse_locked() const
Definition: widget.cpp:70
surface overlayPressedDisabledImage_
Definition: button.hpp:76
CVideo & video() const
Definition: widget.hpp:84
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
void assign(SDL_Surface *surf)
Definition: surface.hpp:46
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.
Definition: image.cpp:102
void play_UI_sound(const std::string &files)
Definition: sound.cpp:1020
Standard logging facilities (interface).
surface pressedDisabledImage_
Definition: button.hpp:76
STATE state_
Definition: button.hpp:83
static lg::log_domain log_display("display")
void set_height(int h)
Definition: widget.cpp:117
const int font_size
Definition: button.cpp:40
bool file_exists(const std::string &name)
Returns true if a file or directory with such name already exists.
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