The Battle for Wesnoth  1.19.7+dev
button.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2003 - 2024
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 "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 "wml_separators.hpp"
31 
32 #include <boost/algorithm/string/predicate.hpp>
33 #include <utility>
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  const 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_(std::move(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() &&
119  boost::algorithm::ends_with(button_overlay_image_name_, size_postfix)) {
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:
295  break;
296  case PRESSED:
298  if (type_ == TYPE_PRESS)
299  offset = 1;
300  break;
301  case PRESSED_ACTIVE:
303  break;
304  case TOUCHED_NORMAL:
305  case TOUCHED_PRESSED:
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)
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:
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 }
map_location loc
Definition: move.cpp:172
int checkbox_horizontal_padding_
Definition: button.hpp:93
void set_overlay(const std::string &image_file_base)
Definition: button.cpp:395
texture image_
Definition: button.hpp:72
int base_width_
Definition: button.hpp:85
virtual void handle_event(const SDL_Event &event)
Definition: button.cpp:537
void load_images()
Definition: button.cpp:85
texture disabledImage_
Definition: button.hpp:73
bool hit(int x, int y) const
Definition: button.cpp:377
texture touchedImage_
Definition: button.hpp:73
bool pressed()
Definition: button.cpp:568
virtual void enable(bool new_val=true)
Definition: button.cpp:273
int font_size_
Definition: button.hpp:91
TYPE type_
Definition: button.hpp:62
int base_height_
Definition: button.hpp:85
std::string button_image_path_suffix_
Definition: button.hpp:89
void calculate_size()
Definition: button.cpp:202
texture overlayPressedImage_
Definition: button.hpp:74
virtual void draw_contents()
Definition: button.cpp:287
bool pressed_
Definition: button.hpp:81
void release()
Definition: button.cpp:481
STATE state_
Definition: button.hpp:79
SPACE_CONSUMPTION spacing_
Definition: button.hpp:83
std::string button_image_name_
Definition: button.hpp:87
button(const std::string &label, TYPE type=TYPE_PRESS, const 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
void set_active(bool active)
Definition: button.cpp:257
SPACE_CONSUMPTION
Definition: button.hpp:32
@ MINIMUM_SPACE
Definition: button.hpp:32
texture overlayActiveImage_
Definition: button.hpp:75
virtual void mouse_up(const SDL_MouseButtonEvent &event)
Definition: button.cpp:486
texture activeImage_
Definition: button.hpp:72
std::string label_text_
Definition: button.hpp:70
texture pressedActiveImage_
Definition: button.hpp:72
@ TYPE_TURBO
Definition: button.hpp:29
@ TYPE_PRESS
Definition: button.hpp:29
@ TYPE_IMAGE
Definition: button.hpp:29
@ TYPE_CHECK
Definition: button.hpp:29
@ TYPE_RADIO
Definition: button.hpp:29
texture overlayDisabledImage_
Definition: button.hpp:74
int horizontal_padding_
Definition: button.hpp:92
texture overlayImage_
Definition: button.hpp:74
texture pressedDisabledImage_
Definition: button.hpp:73
void set_image(const std::string &image_file_base)
Definition: button.cpp:384
int vertical_padding_
Definition: button.hpp:94
void set_label(const std::string &val)
Definition: button.cpp:407
virtual void mouse_down(const SDL_MouseButtonEvent &event)
Definition: button.cpp:458
bool checked() const
Definition: button.cpp:268
virtual void mouse_motion(const SDL_MouseMotionEvent &event)
Definition: button.cpp:424
@ TOUCHED_NORMAL
Definition: button.hpp:78
@ TOUCHED_PRESSED
Definition: button.hpp:78
@ PRESSED_ACTIVE
Definition: button.hpp:78
texture pressedImage_
Definition: button.hpp:72
SDL_Rect textRect_
Definition: button.hpp:76
texture overlayPressedDisabledImage_
Definition: button.hpp:74
std::string button_overlay_image_name_
Definition: button.hpp:88
void set_check(bool check)
Definition: button.cpp:239
virtual ~button()
Default implementation, but defined out-of-line for efficiency reasons.
Definition: button.cpp:198
void set_width(int w)
Definition: widget.cpp:98
virtual void enable(bool new_val=true)
Definition: widget.cpp:167
void set_height(int h)
Definition: widget.cpp:103
virtual void set_location(const SDL_Rect &rect)
Definition: widget.cpp:69
virtual void handle_event(const SDL_Event &) override
Definition: widget.hpp:90
const rect & location() const
Definition: widget.cpp:123
void queue_redraw()
Indicate that the widget should be redrawn.
Definition: widget.cpp:215
bool enabled() const
Definition: widget.cpp:175
bool hidden() const
Definition: widget.cpp:161
bool mouse_locked() const
Definition: widget.cpp:64
Wrapper class to encapsulate creation and management of an SDL_Texture.
Definition: texture.hpp:33
int w() const
The draw-space width of the texture, in pixels.
Definition: texture.hpp:105
void reset()
Releases ownership of the managed texture and resets the ptr to null.
Definition: texture.cpp:208
int h() const
The draw-space height of the texture, in pixels.
Definition: texture.hpp:114
Drawing functions, for drawing things on the screen.
Declarations for File-IO.
std::size_t i
Definition: function.cpp:1029
std::string label
What to show in the filter's drop-down list.
Definition: manager.cpp:200
Standard logging facilities (interface).
@ NORMAL
Definition: cursor.hpp:28
void blit(const texture &tex, const SDL_Rect &dst)
Draws a texture, or part of a texture, at the given location.
Definition: draw.cpp:310
static bool file_exists(const bfs::path &fpath)
Definition: filesystem.cpp:326
Graphical text output.
const color_t BUTTON_COLOR
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.
const int SIZE_SMALL
Definition: constants.cpp:24
const color_t GRAY_COLOR
const int SIZE_BUTTON
Definition: constants.cpp:25
const std::string checkbox_release
const std::string button_press
std::string path
Definition: filesystem.cpp:91
General purpose widgets.
const int default_font_size
Definition: button.cpp:40
static bool is_valid_image(const std::string &str)
Definition: button.cpp:382
Functions to load and save images from/to disk.
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:920
constexpr const SDL_Rect empty_rect
Definition: rect.hpp:30
void play_UI_sound(const std::string &files)
Definition: sound.cpp:1078
std::vector< std::string > split(const config_attribute_value &val)
auto * find(Container &container, const Value &value)
Convenience wrapper for using find on a container without needing to comare to end()
Definition: general.hpp:140
Contains the SDL_Rect helper code.
Transitional API for porting SDL_ttf-based code to Pango.
The basic class for representing 8-bit RGB or RGBA colour values.
Definition: color.hpp:59
Base class for all the errors encountered by the engine.
Definition: exceptions.hpp:29
bool contains(int x, int y) const
Whether the given point lies within the rectangle.
Definition: rect.cpp:53
static lg::log_domain log_display("display")
#define ERR_DP
Definition: button.cpp:36
char const IMAGE_PREFIX
char const COLUMN_SEPARATOR
#define h