The Battle for Wesnoth  1.17.10+dev
button.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2003 - 2022
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 "draw.hpp"
21 #include "filesystem.hpp"
22 #include "game_config.hpp"
23 #include "game_errors.hpp"
24 #include "picture.hpp"
25 #include "log.hpp"
26 #include "font/sdl_ttf_compat.hpp"
27 #include "font/standard_colors.hpp"
28 #include "sdl/rect.hpp"
30 #include "sound.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(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(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 
84 // This function is a mess and i can only hope it someday dies a horrible death
86 
87  std::string size_postfix;
88 
89  switch (location().h) {
90  case 25:
91  size_postfix = "_25";
92  break;
93  case 30:
94  size_postfix = "_30";
95  break;
96  case 60:
97  size_postfix = "_60";
98  break;
99  default:
100  break;
101  }
102 
106  button_image_name_ + "-pressed.png" + button_image_path_suffix_);
109  if (filesystem::file_exists(game_config::path + "/images/" + button_image_name_ + "-disabled.png")) {
111  button_image_name_ + "-disabled.png" + button_image_path_suffix_);
112  } else {
114  }
115 
116  if (!button_overlay_image_name_.empty()) {
117 
118  if (button_overlay_image_name_.length() > size_postfix.length() &&
120  button_overlay_image_name_.resize(button_overlay_image_name_.length() - size_postfix.length());
121  }
122 
125 
126  if (filesystem::file_exists(game_config::path + "/images/" + button_overlay_image_name_ + size_postfix + "-active.png"))
128 
129  if (filesystem::file_exists(game_config::path + "/images/" + button_overlay_image_name_ + size_postfix + "-disabled.png"))
133 
134  if (filesystem::file_exists(game_config::path + "/images/" + button_overlay_image_name_ + size_postfix + "-disabled-pressed.png"))
138  } else {
140  }
141 
142  if (!disabledImage_) {
145  }
146 
147  if (!pressedImage_) {
149  }
150 
151  if (!activeImage_) {
153  }
154 
155  if (type_ == TYPE_CHECK || type_ == TYPE_RADIO) {
158  if (!touchedImage_) {
160  }
161 
163  button_image_name_ + "-active-pressed.png"+ button_image_path_suffix_);
164  if (!pressedActiveImage_) {
166  }
167 
168  if (filesystem::file_exists(game_config::path + "/images/" + button_image_name_ + size_postfix + "-disabled-pressed.png")) {
170  button_image_name_ + "-disabled-pressed.png"+ button_image_path_suffix_);
171  }
172  if (!pressedDisabledImage_) {
174  button_image_name_ + "-pressed.png~GS()"+ button_image_path_suffix_);
175  }
176  }
177 
178  if (!image_) {
179  std::string err_msg = "error initializing button images! file name: ";
180  err_msg += button_image_name_;
181  err_msg += ".png";
182  ERR_DP << err_msg;
183  throw game::error(err_msg);
184  }
185 
186  base_height_ = image_.h();
187  base_width_ = image_.w();
188 
189  if (type_ != TYPE_IMAGE) {
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){
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 
239 void button::set_check(bool check)
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  queue_redraw();
254  }
255 }
256 
257 void button::set_active(bool active)
258 {
259  if ((state_ == NORMAL) && active) {
260  state_ = ACTIVE;
261  queue_redraw();
262  } else if ((state_ == ACTIVE) && !active) {
263  state_ = NORMAL;
264  queue_redraw();
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 
286 // I can only assume this is working because nobody has complained it isn't.
288 {
289  texture image = image_;
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  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  if (state_ == PRESSED || state_ == PRESSED_ACTIVE)
328  image = pressedDisabledImage_;
329  else image = disabledImage_;
330 
331  button_color = font::GRAY_COLOR;
332  }
333 
334  SDL_Rect dest = loc;
335  if(type_ != TYPE_PRESS && type_ != TYPE_TURBO) {
336  // Scale other button types to match the base image?
337  dest.w = image_.w();
338  dest.h = image_.h();
339  }
340 
341  draw::blit(image, dest);
342 
343  if (overlayImage_) {
345 
346  if (overlayPressedImage_) {
347  switch (state_) {
348  case ACTIVE:
350  overlay = overlayActiveImage_;
351  break;
352  case PRESSED:
353  case PRESSED_ACTIVE:
354  case TOUCHED_NORMAL:
355  case TOUCHED_PRESSED:
357  break;
358  default:
359  break;
360  }
361  }
362 
363  dest.w = overlay.w();
364  dest.h = overlay.h();
365  draw::blit(overlay, dest);
366  }
367 
368  if (type_ != TYPE_IMAGE){
369  clipArea.x += offset;
370  clipArea.y += offset;
371  clipArea.w -= 2*offset;
372  clipArea.h -= 2*offset;
373  font::pango_draw_text(true, clipArea, font_size_, button_color, label_text_, textx, texty);
374  }
375 }
376 
377 bool button::hit(int x, int y) const
378 {
379  return location().contains(x, y);
380 }
381 
382 static bool is_valid_image(const std::string& str) { return !str.empty() && str[0] != IMAGE_PREFIX; }
383 
384 void button::set_image(const std::string& image_file)
385 {
386  if(!is_valid_image(image_file)) {
387  return;
388  }
389 
390  button_image_name_ = "buttons/" + image_file;
391  load_images();
392  queue_redraw();
393 }
394 
395 void button::set_overlay(const std::string& image_file)
396 {
397  // We allow empty paths for overlays
398  if(image_file[0] == IMAGE_PREFIX) {
399  return;
400  }
401 
402  button_overlay_image_name_ = image_file;
403  load_images();
404  queue_redraw();
405 }
406 
407 void button::set_label(const std::string& val)
408 {
409  label_text_ = val;
410 
411  //if we have a list of items, use the first one that isn't an image
412  if (std::find(label_text_.begin(), label_text_.end(), COLUMN_SEPARATOR) != label_text_.end()) {
413  const std::vector<std::string>& items = utils::split(label_text_, COLUMN_SEPARATOR);
414  const std::vector<std::string>::const_iterator i = std::find_if(items.begin(),items.end(),is_valid_image);
415  if(i != items.end()) {
416  label_text_ = *i;
417  }
418  }
419 
420  calculate_size();
421  queue_redraw();
422 }
423 
424 void button::mouse_motion(const SDL_MouseMotionEvent& event)
425 {
426  if (hit(event.x, event.y)) {
427  // the cursor is over the widget
428  if (state_ == NORMAL)
429  state_ = ACTIVE;
430  else if (state_ == PRESSED && (type_ == TYPE_CHECK || type_ == TYPE_RADIO))
432  } else {
433  // the cursor is not over the widget
434 
435  if (type_ == TYPE_CHECK || type_ == TYPE_RADIO) {
436 
437  switch (state_) {
438  case TOUCHED_NORMAL:
439  state_ = NORMAL;
440  break;
441  case TOUCHED_PRESSED:
442  state_ = PRESSED;
443  break;
444  case PRESSED_ACTIVE:
445  state_ = PRESSED;
446  break;
447  case ACTIVE:
448  state_ = NORMAL;
449  break;
450  default:
451  break;
452  }
453  } else if ((type_ != TYPE_IMAGE) || state_ != PRESSED)
454  state_ = NORMAL;
455  }
456 }
457 
458 void button::mouse_down(const SDL_MouseButtonEvent& event)
459 {
460  if (hit(event.x, event.y) && event.button == SDL_BUTTON_LEFT) {
461 
462  switch (type_) {
463  case TYPE_RADIO:
464  case TYPE_CHECK:
465  if (state_ == PRESSED_ACTIVE)
467  else if (state_ == ACTIVE)
469  break;
470  case TYPE_TURBO:
472  state_ = PRESSED;
473  break;
474  default:
475  state_ = PRESSED;
476  break;
477  }
478  }
479 }
480 
482  state_ = NORMAL;
483  draw_contents();
484 }
485 
486 void button::mouse_up(const SDL_MouseButtonEvent& event)
487 {
488  if (!(hit(event.x, event.y) && event.button == SDL_BUTTON_LEFT))
489  return;
490 
491  // the user has stopped pressing the mouse left button while on the widget
492  switch (type_) {
493  case TYPE_CHECK:
494 
495  switch (state_) {
496  case TOUCHED_NORMAL:
498  pressed_ = true;
499  break;
500  case TOUCHED_PRESSED:
501  state_ = ACTIVE;
502  pressed_ = true;
503  break;
504  default:
505  break;
506  }
508  break;
509  case TYPE_RADIO:
512  pressed_ = true;
513  // exit(0);
515  }
516  //} else if (state_ == TOUCHED_PRESSED) {
517  // state_ = PRESSED_ACTIVE;
518  //}
519  break;
520  case TYPE_PRESS:
521  if (state_ == PRESSED) {
522  state_ = ACTIVE;
523  pressed_ = true;
525  }
526  break;
527  case TYPE_TURBO:
528  state_ = ACTIVE;
529  break;
530  case TYPE_IMAGE:
531  pressed_ = true;
533  break;
534  }
535 }
536 
537 void button::handle_event(const SDL_Event& event)
538 {
540 
541  if (hidden() || !enabled())
542  return;
543 
544  STATE start_state = state_;
545 
546  if (!mouse_locked())
547  {
548  switch(event.type) {
549  case SDL_MOUSEBUTTONDOWN:
550  mouse_down(event.button);
551  break;
552  case SDL_MOUSEBUTTONUP:
553  mouse_up(event.button);
554  break;
555  case SDL_MOUSEMOTION:
556  mouse_motion(event.motion);
557  break;
558  default:
559  return;
560  }
561  }
562 
563  if (start_state != state_) {
564  queue_redraw();
565  }
566 }
567 
569 {
570  if (type_ != TYPE_TURBO) {
571  const bool res = pressed_;
572  pressed_ = false;
573  return res;
574  } else
576 }
577 
578 }
Drawing functions, for drawing things on the screen.
const color_t BUTTON_COLOR
#define ERR_DP
Definition: button.cpp:36
std::string button_image_name_
Definition: button.hpp:118
bool enabled() const
Definition: widget.cpp:176
void set_check(bool check)
Definition: button.cpp:239
virtual void mouse_up(const SDL_MouseButtonEvent &event)
Definition: button.cpp:486
std::string button_image_path_suffix_
Definition: button.hpp:120
bool pressed_
Definition: button.hpp:112
int w() const
The draw-space width of the texture, in pixels.
Definition: texture.hpp:105
virtual void enable(bool new_val=true)
Definition: widget.cpp:168
const color_t GRAY_COLOR
bool hidden() const
Definition: widget.cpp:162
Collection of helper functions relating to Pango formatting.
int base_height_
Definition: button.hpp:116
void set_label(const std::string &val)
Definition: button.cpp:407
void queue_redraw()
Indicate that the widget should be redrawn.
Definition: widget.cpp:216
bool hit(int x, int y) const
Definition: button.cpp:377
void set_image(const std::string &image_file_base)
Definition: button.cpp:384
texture pressedActiveImage_
Definition: button.hpp:103
int h() const
The draw-space height of the texture, in pixels.
Definition: texture.hpp:114
virtual void enable(bool new_val=true)
Definition: button.cpp:273
static bool file_exists(const bfs::path &fpath)
Definition: filesystem.cpp:264
bool ends_with(const std::string &str, const std::string &suffix)
texture overlayDisabledImage_
Definition: button.hpp:103
General purpose widgets.
texture overlayImage_
Definition: button.hpp:103
const int default_font_size
Definition: button.cpp:40
void set_width(int w)
Definition: widget.cpp:99
const std::vector< std::string > items
#define h
int checkbox_horizontal_padding_
Definition: button.hpp:124
texture image_
Definition: button.hpp:103
virtual void mouse_down(const SDL_MouseButtonEvent &event)
Definition: button.cpp:458
void load_images()
Definition: button.cpp:85
virtual void draw_contents()
Definition: button.cpp:287
bool contains(int x, int y) const
Whether the given point lies within the rectangle.
Definition: rect.cpp:54
Wrapper class to encapsulate creation and management of an SDL_Texture.
Definition: texture.hpp:32
char const IMAGE_PREFIX
int base_width_
Definition: button.hpp:116
void release()
Definition: button.cpp:481
button(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
std::string button_overlay_image_name_
Definition: button.hpp:119
std::string label
What to show in the filter&#39;s drop-down list.
Definition: manager.cpp:217
texture overlayPressedDisabledImage_
Definition: button.hpp:103
static bool is_valid_image(const std::string &str)
Definition: button.cpp:382
void calculate_size()
Definition: button.cpp:202
SPACE_CONSUMPTION spacing_
Definition: button.hpp:114
TYPE type_
Definition: button.hpp:93
The basic class for representing 8-bit RGB or RGBA colour values.
Definition: color.hpp:58
virtual void mouse_motion(const SDL_MouseMotionEvent &event)
Definition: button.cpp:424
SPACE_CONSUMPTION
Definition: button.hpp:63
void set_overlay(const std::string &image_file_base)
Definition: button.cpp:395
std::string path
Definition: game_config.cpp:39
const rect & location() const
Definition: widget.cpp:124
bool pressed()
Definition: button.cpp:568
texture overlayPressedImage_
Definition: button.hpp:103
const int SIZE_BUTTON
Definition: constants.cpp:25
virtual void set_location(const SDL_Rect &rect)
Definition: widget.cpp:70
bool checked() const
Definition: button.cpp:268
texture pressedImage_
Definition: button.hpp:103
texture touchedImage_
Definition: button.hpp:103
std::size_t i
Definition: function.cpp:967
virtual void handle_event(const SDL_Event &event)
Definition: button.cpp:537
virtual void handle_event(const SDL_Event &) override
Definition: widget.hpp:93
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.
Declarations for File-IO.
bool mouse_locked() const
Definition: widget.cpp:65
int font_size_
Definition: button.hpp:122
void blit(const texture &tex, const SDL_Rect &dst)
Draws a texture, or part of a texture, at the given location.
Definition: draw.cpp:301
void reset()
Releases ownership of the managed texture and resets the ptr to null.
Definition: texture.cpp:208
const std::string button_press
std::string label_text_
Definition: button.hpp:101
SDL_Rect textRect_
Definition: button.hpp:107
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
constexpr const SDL_Rect empty_rect
Definition: rect.hpp:30
std::vector< std::string > split(const config_attribute_value &val)
Base class for all the errors encountered by the engine.
Definition: exceptions.hpp:28
int horizontal_padding_
Definition: button.hpp:123
Functions to load and save images from/to disk.
void play_UI_sound(const std::string &files)
Definition: sound.cpp:1066
Standard logging facilities (interface).
STATE state_
Definition: button.hpp:110
int vertical_padding_
Definition: button.hpp:125
texture overlayActiveImage_
Definition: button.hpp:103
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:1128
static lg::log_domain log_display("display")
void set_height(int h)
Definition: widget.cpp:104
Transitional API for porting SDL_ttf-based code to Pango.
texture activeImage_
Definition: button.hpp:103
texture pressedDisabledImage_
Definition: button.hpp:103
const int SIZE_SMALL
Definition: constants.cpp:24
void set_active(bool active)
Definition: button.cpp:257
const std::string checkbox_release
texture disabledImage_
Definition: button.hpp:103