The Battle for Wesnoth  1.17.0-dev
button.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2003 - 2021
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/button.hpp"
19 
20 #include "filesystem.hpp"
21 #include "game_config.hpp"
22 #include "game_errors.hpp"
23 #include "picture.hpp"
24 #include "log.hpp"
25 #include "font/sdl_ttf_compat.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 
41 
42 button::button(CVideo& video, const std::string& label, button::TYPE type,
43  std::string button_image_name, SPACE_CONSUMPTION spacing,
44  const bool auto_join, std::string overlay_image, int font_size)
45  : widget(video, auto_join), type_(type),
46  label_text_(label),
47  image_(nullptr), pressedImage_(nullptr), activeImage_(nullptr), pressedActiveImage_(nullptr),
48  disabledImage_(nullptr), pressedDisabledImage_(nullptr),
49  overlayImage_(nullptr), overlayPressedImage_(nullptr), overlayActiveImage_(nullptr),
50  state_(NORMAL), pressed_(false),
51  spacing_(spacing), base_height_(0), base_width_(0),
52  button_image_name_(), button_overlay_image_name_(overlay_image),
53  button_image_path_suffix_(),
54  font_size_(font_size <= 0 ? (type != TYPE_CHECK && type != TYPE_RADIO ? default_font_size : font::SIZE_SMALL) : font_size),
55  horizontal_padding_(font_size_),
56  checkbox_horizontal_padding_(font_size_ / 2),
57  vertical_padding_(font_size_ / 2)
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 = 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_ = 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)
141  pressed_image = button_image;
142 
143  if (!active_image)
144  active_image = button_image;
145 
146  if (type_ == TYPE_CHECK || type_ == TYPE_RADIO) {
147  touched_image = image::get_image(button_image_name_ + "-touched.png"+ button_image_path_suffix_);
148  if (!touched_image)
149  touched_image = pressed_image;
150 
151  pressed_active_image = image::get_image(button_image_name_ + "-active-pressed.png"+ button_image_path_suffix_);
152  if (!pressed_active_image)
153  pressed_active_image = pressed_image;
154 
155  if (filesystem::file_exists(game_config::path + "/images/" + button_image_name_ + size_postfix + "-disabled-pressed.png"))
156  pressed_disabled_image = image::get_image(button_image_name_ + "-disabled-pressed.png"+ button_image_path_suffix_);
157  if (!pressed_disabled_image)
158  pressed_disabled_image = image::get_image(button_image_name_ + "-pressed.png~GS()"+ button_image_path_suffix_);
159  }
160 
161  if (!button_image) {
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_ = scale_surface(button_image,location().w,location().h);
178  pressedImage_ = scale_surface(pressed_image,location().w,location().h);
179  activeImage_ = scale_surface(active_image,location().w,location().h);
180  disabledImage_ = scale_surface(disabled_image,location().w,location().h);
181  } else {
182  image_ = scale_surface(button_image,button_image->w,button_image->h);
183  activeImage_ = scale_surface(active_image,button_image->w,button_image->h);
184  disabledImage_ = scale_surface(disabled_image,button_image->w,button_image->h);
185  pressedImage_ = scale_surface(pressed_image,button_image->w,button_image->h);
186  if (type_ == TYPE_CHECK || type_ == TYPE_RADIO) {
187  pressedDisabledImage_ = scale_surface(pressed_disabled_image,button_image->w,button_image->h);
188  pressedActiveImage_ = scale_surface(pressed_active_image, button_image->w, button_image->h);
189  touchedImage_ = 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 
212  if (type_ != TYPE_IMAGE){
213  textRect_ = font::pango_draw_text(nullptr, video().screen_area(), font_size_, font::BUTTON_COLOR, label_text_, 0, 0);
214  }
215 
216  // TODO: There's a weird text clipping bug, allowing the code below to run fixes it.
217  // The proper fix should possibly be in the draw_contents() function.
218 #if 0
219  if (!change_size)
220  return;
221 #endif
222 
224  if(type_ == TYPE_PRESS || type_ == TYPE_TURBO) {
225  if(spacing_ == MINIMUM_SPACE) {
227  } else {
229  }
230  } else {
231  if(label_text_.empty()) {
233  } else {
235  }
236  }
237 }
238 
240 {
241  if (type_ != TYPE_CHECK && type_ != TYPE_RADIO && type_ != TYPE_IMAGE)
242  return;
243  STATE new_state;
244 
245  if (check) {
246  new_state = (state_ == ACTIVE || state_ == PRESSED_ACTIVE)? PRESSED_ACTIVE : PRESSED;
247  } else {
248  new_state = (state_ == ACTIVE || state_ == PRESSED_ACTIVE)? ACTIVE : NORMAL;
249  }
250 
251  if (state_ != new_state) {
252  state_ = new_state;
253  set_dirty();
254  }
255 }
256 
257 void button::set_active(bool active)
258 {
259  if ((state_ == NORMAL) && active) {
260  state_ = ACTIVE;
261  set_dirty();
262  } else if ((state_ == ACTIVE) && !active) {
263  state_ = NORMAL;
264  set_dirty();
265  }
266 }
267 
268 bool button::checked() const
269 {
271 }
272 
273 void button::enable(bool new_val)
274 {
275  if(new_val != enabled())
276  {
277  pressed_ = false;
278  // check buttons should keep their state
279  if(type_ != TYPE_CHECK) {
280  state_ = NORMAL;
281  }
282  widget::enable(new_val);
283  }
284 }
285 
287 {
288  surface image = image_;
289  const int image_w = image_->w;
290 
291  int offset = 0;
292  switch(state_) {
293  case ACTIVE:
294  image = activeImage_;
295  break;
296  case PRESSED:
297  image = pressedImage_;
298  if (type_ == TYPE_PRESS)
299  offset = 1;
300  break;
301  case PRESSED_ACTIVE:
302  image = pressedActiveImage_;
303  break;
304  case TOUCHED_NORMAL:
305  case TOUCHED_PRESSED:
306  image = touchedImage_;
307  break;
308  default:
309  break;
310  }
311 
312  const SDL_Rect& loc = location();
313  SDL_Rect clipArea = loc;
314  const int texty = loc.y + loc.h / 2 - textRect_.h / 2 + offset;
315  int textx;
316 
317  if (type_ != TYPE_CHECK && type_ != TYPE_RADIO && type_ != TYPE_IMAGE)
318  textx = loc.x + image->w / 2 - textRect_.w / 2 + offset;
319  else {
320  clipArea.w += image_w + checkbox_horizontal_padding_;
321  textx = loc.x + image_w + checkbox_horizontal_padding_ / 2;
322  }
323 
324  color_t button_color = font::BUTTON_COLOR;
325 
326  if (!enabled()) {
327 
328  if (state_ == PRESSED || state_ == PRESSED_ACTIVE)
329  image = pressedDisabledImage_;
330  else image = disabledImage_;
331 
332  button_color = font::GRAY_COLOR;
333  }
334 
335  if (overlayImage_) {
336 
338 
339  if (overlayPressedImage_) {
340  switch (state_) {
341  case ACTIVE:
343  noverlay = &overlayActiveImage_;
344  break;
345  case PRESSED:
346  case PRESSED_ACTIVE:
347  case TOUCHED_NORMAL:
348  case TOUCHED_PRESSED:
350  break;
351  default:
352  break;
353  }
354  }
355 
356  surface nimage = image.clone();
357  sdl_blit(*noverlay, nullptr, nimage, nullptr);
358  image = nimage;
359  }
360 
361  video().blit_surface(loc.x, loc.y, image);
362  if (type_ != TYPE_IMAGE){
363  clipArea.x += offset;
364  clipArea.y += offset;
365  clipArea.w -= 2*offset;
366  clipArea.h -= 2*offset;
367  font::pango_draw_text(&video(), clipArea, font_size_, button_color, label_text_, textx, texty);
368  }
369 }
370 
371 bool button::hit(int x, int y) const
372 {
373  return sdl::point_in_rect(x,y,location());
374 }
375 
376 static bool is_valid_image(const std::string& str) { return !str.empty() && str[0] != IMAGE_PREFIX; }
377 
378 void button::set_image(const std::string& image_file)
379 {
380  if(!is_valid_image(image_file)) {
381  return;
382  }
383 
384  button_image_name_ = "buttons/" + image_file;
385  load_images();
386  set_dirty();
387 }
388 
389 void button::set_overlay(const std::string& image_file)
390 {
391  // We allow empty paths for overlays
392  if(image_file[0] == IMAGE_PREFIX) {
393  return;
394  }
395 
396  button_overlay_image_name_ = image_file;
397  load_images();
398  set_dirty();
399 }
400 
401 void button::set_label(const std::string& val)
402 {
403  label_text_ = val;
404 
405  //if we have a list of items, use the first one that isn't an image
406  if (std::find(label_text_.begin(), label_text_.end(), COLUMN_SEPARATOR) != label_text_.end()) {
407  const std::vector<std::string>& items = utils::split(label_text_, COLUMN_SEPARATOR);
408  const std::vector<std::string>::const_iterator i = std::find_if(items.begin(),items.end(),is_valid_image);
409  if(i != items.end()) {
410  label_text_ = *i;
411  }
412  }
413 
414  calculate_size();
415 
416  set_dirty(true);
417 }
418 
419 void button::mouse_motion(const SDL_MouseMotionEvent& event)
420 {
421  if (hit(event.x, event.y)) {
422  // the cursor is over the widget
423  if (state_ == NORMAL)
424  state_ = ACTIVE;
425  else if (state_ == PRESSED && (type_ == TYPE_CHECK || type_ == TYPE_RADIO))
427  } else {
428  // the cursor is not over the widget
429 
430  if (type_ == TYPE_CHECK || type_ == TYPE_RADIO) {
431 
432  switch (state_) {
433  case TOUCHED_NORMAL:
434  state_ = NORMAL;
435  break;
436  case TOUCHED_PRESSED:
437  state_ = PRESSED;
438  break;
439  case PRESSED_ACTIVE:
440  state_ = PRESSED;
441  break;
442  case ACTIVE:
443  state_ = NORMAL;
444  break;
445  default:
446  break;
447  }
448  } else if ((type_ != TYPE_IMAGE) || state_ != PRESSED)
449  state_ = NORMAL;
450  }
451 }
452 
453 void button::mouse_down(const SDL_MouseButtonEvent& event)
454 {
455  if (hit(event.x, event.y) && event.button == SDL_BUTTON_LEFT) {
456 
457  switch (type_) {
458  case TYPE_RADIO:
459  case TYPE_CHECK:
460  if (state_ == PRESSED_ACTIVE)
462  else if (state_ == ACTIVE)
464  break;
465  case TYPE_TURBO:
467  state_ = PRESSED;
468  break;
469  default:
470  state_ = PRESSED;
471  break;
472  }
473  }
474 }
475 
477  state_ = NORMAL;
478  draw_contents();
479 }
480 
481 void button::mouse_up(const SDL_MouseButtonEvent& event)
482 {
483  if (!(hit(event.x, event.y) && event.button == SDL_BUTTON_LEFT))
484  return;
485 
486  // the user has stopped pressing the mouse left button while on the widget
487  switch (type_) {
488  case TYPE_CHECK:
489 
490  switch (state_) {
491  case TOUCHED_NORMAL:
493  pressed_ = true;
494  break;
495  case TOUCHED_PRESSED:
496  state_ = ACTIVE;
497  pressed_ = true;
498  break;
499  default:
500  break;
501  }
503  break;
504  case TYPE_RADIO:
507  pressed_ = true;
508  // exit(0);
510  }
511  //} else if (state_ == TOUCHED_PRESSED) {
512  // state_ = PRESSED_ACTIVE;
513  //}
514  break;
515  case TYPE_PRESS:
516  if (state_ == PRESSED) {
517  state_ = ACTIVE;
518  pressed_ = true;
520  }
521  break;
522  case TYPE_TURBO:
523  state_ = ACTIVE;
524  break;
525  case TYPE_IMAGE:
526  pressed_ = true;
528  break;
529  }
530 }
531 
532 void button::handle_event(const SDL_Event& event)
533 {
535 
536  if (hidden() || !enabled())
537  return;
538 
539  STATE start_state = state_;
540 
541  if (!mouse_locked())
542  {
543  switch(event.type) {
544  case SDL_MOUSEBUTTONDOWN:
545  mouse_down(event.button);
546  break;
547  case SDL_MOUSEBUTTONUP:
548  mouse_up(event.button);
549  break;
550  case SDL_MOUSEMOTION:
551  mouse_motion(event.motion);
552  break;
553  default:
554  return;
555  }
556  }
557 
558  if (start_state != state_)
559  set_dirty(true);
560 }
561 
563 {
564  if (type_ != TYPE_TURBO) {
565  const bool res = pressed_;
566  pressed_ = false;
567  return res;
568  } else
570 }
571 
572 }
const color_t BUTTON_COLOR
surface get_image(const image::locator &i_locator, TYPE type)
Caches and returns an image.
Definition: picture.cpp:816
#define ERR_DP
Definition: button.cpp:36
std::string button_image_name_
Definition: button.hpp:117
bool enabled() const
Definition: widget.cpp:202
void set_check(bool check)
Definition: button.cpp:239
virtual void mouse_up(const SDL_MouseButtonEvent &event)
Definition: button.cpp:481
std::string button_image_path_suffix_
Definition: button.hpp:119
static void check(LexState *ls, int c)
Definition: lparser.cpp:107
bool pressed_
Definition: button.hpp:111
virtual void enable(bool new_val=true)
Definition: widget.cpp:194
const color_t GRAY_COLOR
bool hidden() const
Definition: widget.cpp:188
Collection of helper functions relating to Pango formatting.
int base_height_
Definition: button.hpp:115
void set_label(const std::string &val)
Definition: button.cpp:401
surface pressedActiveImage_
Definition: button.hpp:102
bool hit(int x, int y) const
Definition: button.cpp:371
void set_image(const std::string &image_file_base)
Definition: button.cpp:378
static l_noret error(LoadState *S, const char *why)
Definition: lundump.cpp:40
surface pressedImage_
Definition: button.hpp:102
Definition: video.hpp:32
virtual void enable(bool new_val=true)
Definition: button.cpp:273
static bool file_exists(const bfs::path &fpath)
Definition: filesystem.cpp:263
bool ends_with(const std::string &str, const std::string &suffix)
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="", int font_size=-1)
Definition: button.cpp:42
surface overlayPressedImage_
Definition: button.hpp:102
surface activeImage_
Definition: button.hpp:102
General purpose widgets.
const int default_font_size
Definition: button.cpp:40
void set_width(int w)
Definition: widget.cpp:109
const std::vector< std::string > items
#define h
surface disabledImage_
Definition: button.hpp:102
int checkbox_horizontal_padding_
Definition: button.hpp:123
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:197
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:453
void load_images()
Definition: button.cpp:84
virtual void draw_contents()
Definition: button.cpp:286
surface clone() const
Makes a copy of this surface.
Definition: surface.cpp:63
char const IMAGE_PREFIX
int base_width_
Definition: button.hpp:115
void release()
Definition: button.cpp:476
void set_dirty(bool dirty=true)
Definition: widget.cpp:207
surface overlayImage_
Definition: button.hpp:102
virtual void handle_event(const SDL_Event &)
Definition: widget.cpp:330
std::string button_overlay_image_name_
Definition: button.hpp:118
std::string label
What to show in the filter&#39;s drop-down list.
Definition: manager.cpp:217
static bool is_valid_image(const std::string &str)
Definition: button.cpp:376
void calculate_size()
Definition: button.cpp:202
const SDL_Rect & location() const
Definition: widget.cpp:134
SPACE_CONSUMPTION spacing_
Definition: button.hpp:113
TYPE type_
Definition: button.hpp:92
SDL_Rect pango_draw_text(CVideo *gui, const SDL_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.
surface touchedImage_
Definition: button.hpp:102
virtual void mouse_motion(const SDL_MouseMotionEvent &event)
Definition: button.cpp:419
SPACE_CONSUMPTION
Definition: button.hpp:62
void set_overlay(const std::string &image_file_base)
Definition: button.cpp:389
std::string path
Definition: game_config.cpp:39
bool pressed()
Definition: button.cpp:562
const int SIZE_BUTTON
Definition: constants.cpp:25
virtual void set_location(const SDL_Rect &rect)
Definition: widget.cpp:75
bool checked() const
Definition: button.cpp:268
surface image_
Definition: button.hpp:102
bool point_in_rect(int x, int y, const SDL_Rect &rect)
Tests whether a point is inside a rectangle.
Definition: rect.cpp:23
std::size_t i
Definition: function.cpp:967
virtual void handle_event(const SDL_Event &event)
Definition: button.cpp:532
Declarations for File-IO.
int w
bool mouse_locked() const
Definition: widget.cpp:62
surface overlayPressedDisabledImage_
Definition: button.hpp:102
int font_size_
Definition: button.hpp:121
CVideo & video() const
Definition: widget.hpp:84
const std::string button_press
surface overlayActiveImage_
Definition: button.hpp:102
std::string label_text_
Definition: button.hpp:100
SDL_Rect textRect_
Definition: button.hpp:106
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
std::vector< std::string > split(const config_attribute_value &val)
int horizontal_padding_
Definition: button.hpp:122
Functions to load and save images from/to disk.
void play_UI_sound(const std::string &files)
Definition: sound.cpp:1052
Standard logging facilities (interface).
surface pressedDisabledImage_
Definition: button.hpp:102
STATE state_
Definition: button.hpp:109
int vertical_padding_
Definition: button.hpp:124
void sdl_blit(const surface &src, SDL_Rect *src_rect, surface &dst, SDL_Rect *dst_rect)
Definition: utils.hpp:32
static lg::log_domain log_display("display")
void set_height(int h)
Definition: widget.cpp:114
Transitional API for porting SDL_ttf-based code to Pango.
surface overlayDisabledImage_
Definition: button.hpp:102
const int SIZE_SMALL
Definition: constants.cpp:24
void set_active(bool active)
Definition: button.cpp:257
const std::string checkbox_release