The Battle for Wesnoth  1.13.11+dev
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Modules Pages
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 "font/text.hpp"
22 #include "game_config.hpp"
23 #include "game_errors.hpp"
24 #include "image.hpp"
25 #include "log.hpp"
26 #include "font/marked-up_text.hpp"
27 #include "font/standard_colors.hpp"
28 #include "sdl/rect.hpp"
30 #include "sound.hpp"
31 #include "video.hpp"
32 #include "wml_separators.hpp"
33 
34 #include <boost/algorithm/string/predicate.hpp>
35 
36 static lg::log_domain log_display("display");
37 #define ERR_DP LOG_STREAM(err, log_display)
38 
39 namespace gui {
40 
41 const int font_size = font::SIZE_NORMAL;
45 
47  std::string button_image_name, SPACE_CONSUMPTION spacing,
48  const bool auto_join, std::string overlay_image)
49  : widget(video, auto_join), type_(type),
50  label_text_(label),
51  image_(nullptr), pressedImage_(nullptr), activeImage_(nullptr), pressedActiveImage_(nullptr),
52  disabledImage_(nullptr), pressedDisabledImage_(nullptr),
53  overlayImage_(nullptr), overlayPressedImage_(nullptr), overlayActiveImage_(nullptr),
54  state_(NORMAL), pressed_(false),
55  spacing_(spacing), base_height_(0), base_width_(0),
56  button_image_name_(), button_overlay_image_name_(overlay_image),
57  button_image_path_suffix_()
58 {
59  if (button_image_name.empty()) {
60 
61  switch (type_) {
62  case TYPE_PRESS:
63  button_image_name_ = "buttons/button_normal/button_H22";
64  break;
65  case TYPE_TURBO:
66  button_image_name_ = "buttons/button_menu/menu_button_copper_H20";
67  break;
68  case TYPE_CHECK:
69  button_image_name_ = "buttons/checkbox";
70  break;
71  case TYPE_RADIO:
72  button_image_name_ = "buttons/radiobox";
73  break;
74  default:
75  break;
76  }
77  } else {
78  button_image_name_ = "buttons/" + button_image_name;
79  }
80 
81  load_images();
82 }
83 
85 
86  std::string size_postfix;
87 
88  switch (location().h) {
89  case 25:
90  size_postfix = "_25";
91  break;
92  case 30:
93  size_postfix = "_30";
94  break;
95  case 60:
96  size_postfix = "_60";
97  break;
98  default:
99  break;
100  }
101 
103  surface pressed_image(image::get_image(button_image_name_ + "-pressed.png"+ button_image_path_suffix_));
105  surface disabled_image;
106  if (filesystem::file_exists(game_config::path + "/images/" + button_image_name_ + "-disabled.png"))
107  disabled_image.assign((image::get_image(button_image_name_ + "-disabled.png"+ button_image_path_suffix_)));
108  surface pressed_disabled_image, pressed_active_image, touched_image;
109 
110  if (!button_overlay_image_name_.empty()) {
111 
112  if (button_overlay_image_name_.length() > size_postfix.length() &&
114  button_overlay_image_name_.resize(button_overlay_image_name_.length() - size_postfix.length());
115  }
116 
119 
120  if (filesystem::file_exists(game_config::path + "/images/" + button_overlay_image_name_ + size_postfix + "-active.png"))
122 
123  if (filesystem::file_exists(game_config::path + "/images/" + button_overlay_image_name_ + size_postfix + "-disabled.png"))
127 
128  if (filesystem::file_exists(game_config::path + "/images/" + button_overlay_image_name_ + size_postfix + "-disabled-pressed.png"))
132  } else {
133  overlayImage_.assign(nullptr);
134  }
135 
136  if (disabled_image == nullptr) {
137  disabled_image = image::get_image(button_image_name_ + ".png~GS()" + button_image_path_suffix_);
138  }
139 
140  if (pressed_image.null())
141  pressed_image.assign(button_image);
142 
143  if (active_image.null())
144  active_image.assign(button_image);
145 
146  if (type_ == TYPE_CHECK || type_ == TYPE_RADIO) {
147  touched_image.assign(image::get_image(button_image_name_ + "-touched.png"+ button_image_path_suffix_));
148  if (touched_image.null())
149  touched_image.assign(pressed_image);
150 
151  pressed_active_image.assign(image::get_image(button_image_name_ + "-active-pressed.png"+ button_image_path_suffix_));
152  if (pressed_active_image.null())
153  pressed_active_image.assign(pressed_image);
154 
155  if (filesystem::file_exists(game_config::path + "/images/" + button_image_name_ + size_postfix + "-disabled-pressed.png"))
156  pressed_disabled_image.assign(image::get_image(button_image_name_ + "-disabled-pressed.png"+ button_image_path_suffix_));
157  if (pressed_disabled_image.null())
158  pressed_disabled_image = image::get_image(button_image_name_ + "-pressed.png~GS()"+ button_image_path_suffix_);
159  }
160 
161  if (button_image.null()) {
162  std::string err_msg = "error initializing button images! file name: ";
163  err_msg += button_image_name_;
164  err_msg += ".png";
165  ERR_DP << err_msg << std::endl;
166  throw game::error(err_msg);
167  }
168 
169  base_height_ = button_image->h;
170  base_width_ = button_image->w;
171 
172  if (type_ != TYPE_IMAGE) {
174  }
175 
176  if(type_ == TYPE_PRESS || type_ == TYPE_TURBO) {
177  image_.assign(scale_surface(button_image,location().w,location().h));
178  pressedImage_.assign(scale_surface(pressed_image,location().w,location().h));
179  activeImage_.assign(scale_surface(active_image,location().w,location().h));
180  disabledImage_.assign(scale_surface(disabled_image,location().w,location().h));
181  } else {
182  image_.assign(scale_surface(button_image,button_image->w,button_image->h));
183  activeImage_.assign(scale_surface(active_image,button_image->w,button_image->h));
184  disabledImage_.assign(scale_surface(disabled_image,button_image->w,button_image->h));
185  pressedImage_.assign(scale_surface(pressed_image,button_image->w,button_image->h));
186  if (type_ == TYPE_CHECK || type_ == TYPE_RADIO) {
187  pressedDisabledImage_.assign(scale_surface(pressed_disabled_image,button_image->w,button_image->h));
188  pressedActiveImage_.assign(scale_surface(pressed_active_image, button_image->w, button_image->h));
189  touchedImage_.assign(scale_surface(touched_image, button_image->w, button_image->h));
190  }
191  }
192 
193  if (type_ == TYPE_IMAGE){
194  calculate_size();
195  }
196 }
197 
199 {
200 }
201 
203 {
204  if (type_ == TYPE_IMAGE){
205  SDL_Rect loc_image = location();
206  loc_image.h = image_->h;
207  loc_image.w = image_->w;
208  set_location(loc_image);
209  return;
210  }
211  const SDL_Rect& loc = location();
212  bool change_size = loc.h == 0 || loc.w == 0;
213 
214  if (!change_size) {
215  unsigned w = loc.w - (type_ == TYPE_PRESS || type_ == TYPE_TURBO ? horizontal_padding : checkbox_horizontal_padding + base_width_);
216  if (type_ != TYPE_IMAGE)
217  {
218  int fs = font_size;
219  int style = TTF_STYLE_NORMAL;
220  std::string::const_iterator i_beg = label_text_.begin(), i_end = label_text_.end(),
221  i = font::parse_markup(i_beg, i_end, &fs, nullptr, &style);
222  if (i != i_end) {
223  std::string tmp(i, i_end);
224  label_text_.erase(i - i_beg, i_end - i_beg);
225  label_text_ += font::make_text_ellipsis(tmp, fs, w, style);
226  }
227  }
228  }
229 
230  if (type_ != TYPE_IMAGE){
231  textRect_ = font::draw_text(nullptr, video().screen_area(), font_size,
233  }
234 
235  // TODO: There's a weird text clipping bug, allowing the code below to run fixes it.
236  // The proper fix should possibly be in the draw_contents() function.
237 #if 0
238  if (!change_size)
239  return;
240 #endif
241 
242  set_height(std::max(textRect_.h+vertical_padding,base_height_));
243  if(type_ == TYPE_PRESS || type_ == TYPE_TURBO) {
244  if(spacing_ == MINIMUM_SPACE) {
245  set_width(textRect_.w + horizontal_padding);
246  } else {
247  set_width(std::max(textRect_.w+horizontal_padding,base_width_));
248  }
249  } else {
250  if(label_text_.empty()) {
252  } else {
253  set_width(checkbox_horizontal_padding + textRect_.w + base_width_);
254  }
255  }
256 }
257 
259 {
260  if (type_ != TYPE_CHECK && type_ != TYPE_RADIO && type_ != TYPE_IMAGE)
261  return;
262  STATE new_state;
263 
264  if (check) {
265  new_state = (state_ == ACTIVE || state_ == PRESSED_ACTIVE)? PRESSED_ACTIVE : PRESSED;
266  } else {
267  new_state = (state_ == ACTIVE || state_ == PRESSED_ACTIVE)? ACTIVE : NORMAL;
268  }
269 
270  if (state_ != new_state) {
271  state_ = new_state;
272  set_dirty();
273  }
274 }
275 
276 void button::set_active(bool active)
277 {
278  if ((state_ == NORMAL) && active) {
279  state_ = ACTIVE;
280  set_dirty();
281  } else if ((state_ == ACTIVE) && !active) {
282  state_ = NORMAL;
283  set_dirty();
284  }
285 }
286 
287 bool button::checked() const
288 {
290 }
291 
292 void button::enable(bool new_val)
293 {
294  if(new_val != enabled())
295  {
296  pressed_ = false;
297  // check buttons should keep their state
298  if(type_ != TYPE_CHECK) {
299  state_ = NORMAL;
300  }
301  widget::enable(new_val);
302  }
303 }
304 
306 {
307  surface image = image_;
308  const int image_w = image_->w;
309 
310  int offset = 0;
311  switch(state_) {
312  case ACTIVE:
313  image = activeImage_;
314  break;
315  case PRESSED:
316  image = pressedImage_;
317  if (type_ == TYPE_PRESS)
318  offset = 1;
319  break;
320  case PRESSED_ACTIVE:
321  image = pressedActiveImage_;
322  break;
323  case TOUCHED_NORMAL:
324  case TOUCHED_PRESSED:
325  image = touchedImage_;
326  break;
327  default:
328  break;
329  }
330 
331  const SDL_Rect& loc = location();
332  SDL_Rect clipArea = loc;
333  const int texty = loc.y + loc.h / 2 - textRect_.h / 2 + offset;
334  int textx;
335 
336  if (type_ != TYPE_CHECK && type_ != TYPE_RADIO && type_ != TYPE_IMAGE)
337  textx = loc.x + image->w / 2 - textRect_.w / 2 + offset;
338  else {
339  clipArea.w += image_w + checkbox_horizontal_padding;
340  textx = loc.x + image_w + checkbox_horizontal_padding / 2;
341  }
342 
343  color_t button_color = font::BUTTON_COLOR;
344 
345  if (!enabled()) {
346 
347  if (state_ == PRESSED || state_ == PRESSED_ACTIVE)
348  image = pressedDisabledImage_;
349  else image = disabledImage_;
350 
351  button_color = font::GRAY_COLOR;
352  }
353 
354  if (!overlayImage_.null()) {
355 
356  surface noverlay = make_neutral_surface(
358 
359  if (!overlayPressedImage_.null()) {
360  switch (state_) {
361  case ACTIVE:
362  if (!overlayActiveImage_.null())
364  break;
365  case PRESSED:
366  case PRESSED_ACTIVE:
367  case TOUCHED_NORMAL:
368  case TOUCHED_PRESSED:
369  noverlay = make_neutral_surface( enabled() ?
371  break;
372  default:
373  break;
374  }
375  }
376 
377  surface nimage = make_neutral_surface(image);
378  sdl_blit(noverlay, nullptr, nimage, nullptr);
379  image = nimage;
380  }
381 
382  video().blit_surface(loc.x, loc.y, image);
383  if (type_ != TYPE_IMAGE){
384  clipArea.x += offset;
385  clipArea.y += offset;
386  clipArea.w -= 2*offset;
387  clipArea.h -= 2*offset;
388  font::draw_text(&video(), clipArea, font_size, button_color, label_text_, textx, texty);
389  }
390 }
391 
392 bool button::hit(int x, int y) const
393 {
394  return sdl::point_in_rect(x,y,location());
395 }
396 
397 static bool is_valid_image(const std::string& str) { return !str.empty() && str[0] != IMAGE_PREFIX; }
398 
399 void button::set_image(const std::string& image_file)
400 {
401  if(!is_valid_image(image_file)) {
402  return;
403  }
404 
405  button_image_name_ = "buttons/" + image_file;
406  load_images();
407  set_dirty();
408 }
409 
410 void button::set_overlay(const std::string& image_file)
411 {
412  // We allow empty paths for overlays
413  if(image_file[0] == IMAGE_PREFIX) {
414  return;
415  }
416 
417  button_overlay_image_name_ = image_file;
418  load_images();
419  set_dirty();
420 }
421 
423 {
424  label_text_ = val;
425 
426  //if we have a list of items, use the first one that isn't an image
427  if (std::find(label_text_.begin(), label_text_.end(), COLUMN_SEPARATOR) != label_text_.end()) {
428  const std::vector<std::string>& items = utils::split(label_text_, COLUMN_SEPARATOR);
429  const std::vector<std::string>::const_iterator i = std::find_if(items.begin(),items.end(),is_valid_image);
430  if(i != items.end()) {
431  label_text_ = *i;
432  }
433  }
434 
435  calculate_size();
436 
437  set_dirty(true);
438 }
439 
440 void button::mouse_motion(const SDL_MouseMotionEvent& event)
441 {
442  if (hit(event.x, event.y)) {
443  // the cursor is over the widget
444  if (state_ == NORMAL)
445  state_ = ACTIVE;
446  else if (state_ == PRESSED && (type_ == TYPE_CHECK || type_ == TYPE_RADIO))
448  } else {
449  // the cursor is not over the widget
450 
451  if (type_ == TYPE_CHECK || type_ == TYPE_RADIO) {
452 
453  switch (state_) {
454  case TOUCHED_NORMAL:
455  state_ = NORMAL;
456  break;
457  case TOUCHED_PRESSED:
458  state_ = PRESSED;
459  break;
460  case PRESSED_ACTIVE:
461  state_ = PRESSED;
462  break;
463  case ACTIVE:
464  state_ = NORMAL;
465  break;
466  default:
467  break;
468  }
469  } else if ((type_ != TYPE_IMAGE) || state_ != PRESSED)
470  state_ = NORMAL;
471  }
472 }
473 
474 void button::mouse_down(const SDL_MouseButtonEvent& event)
475 {
476  if (hit(event.x, event.y) && event.button == SDL_BUTTON_LEFT) {
477 
478  switch (type_) {
479  case TYPE_RADIO:
480  case TYPE_CHECK:
481  if (state_ == PRESSED_ACTIVE)
483  else if (state_ == ACTIVE)
485  break;
486  case TYPE_TURBO:
488  state_ = PRESSED;
489  break;
490  default:
491  state_ = PRESSED;
492  break;
493  }
494  }
495 }
496 
498  state_ = NORMAL;
499  draw_contents();
500 }
501 
502 void button::mouse_up(const SDL_MouseButtonEvent& event)
503 {
504  if (!(hit(event.x, event.y) && event.button == SDL_BUTTON_LEFT))
505  return;
506 
507  // the user has stopped pressing the mouse left button while on the widget
508  switch (type_) {
509  case TYPE_CHECK:
510 
511  switch (state_) {
512  case TOUCHED_NORMAL:
514  pressed_ = true;
515  break;
516  case TOUCHED_PRESSED:
517  state_ = ACTIVE;
518  pressed_ = true;
519  break;
520  default:
521  break;
522  }
524  break;
525  case TYPE_RADIO:
528  pressed_ = true;
529  // exit(0);
531  }
532  //} else if (state_ == TOUCHED_PRESSED) {
533  // state_ = PRESSED_ACTIVE;
534  //}
535  break;
536  case TYPE_PRESS:
537  if (state_ == PRESSED) {
538  state_ = ACTIVE;
539  pressed_ = true;
541  }
542  break;
543  case TYPE_TURBO:
544  state_ = ACTIVE;
545  break;
546  case TYPE_IMAGE:
547  pressed_ = true;
549  break;
550  }
551 }
552 
553 void button::handle_event(const SDL_Event& event)
554 {
556 
557  if (hidden() || !enabled())
558  return;
559 
560  STATE start_state = state_;
561 
562  if (!mouse_locked())
563  {
564  switch(event.type) {
565  case SDL_MOUSEBUTTONDOWN:
566  mouse_down(event.button);
567  break;
568  case SDL_MOUSEBUTTONUP:
569  mouse_up(event.button);
570  break;
571  case SDL_MOUSEMOTION:
572  mouse_motion(event.motion);
573  break;
574  default:
575  return;
576  }
577  }
578 
579  if (start_state != state_)
580  set_dirty(true);
581 }
582 
584 {
585  if (type_ != TYPE_TURBO) {
586  const bool res = pressed_;
587  pressed_ = false;
588  return res;
589  } else
591 }
592 
593 }
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:920
#define ERR_DP
Definition: button.cpp:37
std::string button_image_name_
Definition: button.hpp:91
void set_check(bool check)
Definition: button.cpp:258
virtual void mouse_up(const SDL_MouseButtonEvent &event)
Definition: button.cpp:502
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:46
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 enabled() const
Definition: widget.cpp:210
bool pressed_
Definition: button.hpp:85
virtual void enable(bool new_val=true)
Definition: widget.cpp:202
const color_t GRAY_COLOR
int base_height_
Definition: button.hpp:89
void set_label(const std::string &val)
Definition: button.cpp:422
surface pressedActiveImage_
Definition: button.hpp:76
bool null() const
Definition: surface.hpp:79
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.
void set_image(const std::string &image_file_base)
Definition: button.cpp:399
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:292
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:42
CVideo & video() const
Definition: widget.hpp:84
General purpose widgets.
void set_width(int w)
Definition: widget.cpp:117
#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:272
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:162
virtual void mouse_down(const SDL_MouseButtonEvent &event)
Definition: button.cpp:474
void load_images()
Definition: button.cpp:84
-file util.hpp
virtual void draw_contents()
Definition: button.cpp:305
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
void release()
Definition: button.cpp:497
const int SIZE_NORMAL
Definition: constants.cpp:19
bool hidden() const
Definition: widget.cpp:196
void set_dirty(bool dirty=true)
Definition: widget.cpp:215
surface overlayImage_
Definition: button.hpp:76
virtual void handle_event(const SDL_Event &)
Definition: widget.cpp:338
std::string button_overlay_image_name_
Definition: button.hpp:92
static bool is_valid_image(const std::string &str)
Definition: button.cpp:397
void calculate_size()
Definition: button.cpp:202
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:440
SPACE_CONSUMPTION
Definition: button.hpp:36
void set_overlay(const std::string &image_file_base)
Definition: button.cpp:410
std::string path
Definition: game_config.cpp:56
bool pressed()
Definition: button.cpp:583
virtual void set_location(const SDL_Rect &rect)
Definition: widget.cpp:83
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
virtual void handle_event(const SDL_Event &event)
Definition: button.cpp:553
size_t i
Definition: function.cpp:933
bool hit(int x, int y) const
Definition: button.cpp:392
Declarations for File-IO.
int w
surface overlayPressedDisabledImage_
Definition: button.hpp:76
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:44
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:198
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.
surface make_neutral_surface(const surface &surf)
Definition: utils.cpp:70
bool checked() const
Definition: button.cpp:287
this module manages the cache of images.
Definition: image.cpp:103
void play_UI_sound(const std::string &files)
Definition: sound.cpp:1020
Standard logging facilities (interface).
bool mouse_locked() const
Definition: widget.cpp:70
surface pressedDisabledImage_
Definition: button.hpp:76
STATE state_
Definition: button.hpp:83
const SDL_Rect & location() const
Definition: widget.cpp:142
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:122
const int font_size
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:276
const int checkbox_horizontal_padding
const std::string checkbox_release