The Battle for Wesnoth 1.21.0-dev
 
Loading...
Searching...
No Matches
button.cpp
Go to the documentation of this file.
1/*
2 Copyright (C) 2003 - 2025
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"
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
35static lg::log_domain log_display("display");
36#define ERR_DP LOG_STREAM(err, log_display)
37
38namespace gui {
39
41
42button::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
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
109 if (filesystem::file_exists(game_config::path + "/images/" + button_image_name_ + "-disabled.png")) {
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 }
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
187 base_width_ = image_.w();
188
189 if (type_ != TYPE_IMAGE) {
191 }
192
193 if (type_ == TYPE_IMAGE){
195 }
196}
197
199{
200}
201
203{
204 if (type_ == TYPE_IMAGE){
205 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
239void 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
257void 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
268bool button::checked() const
269{
271}
272
273void 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{
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 rect loc = location();
313 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::TITLE_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 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
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
377bool button::hit(int x, int y) const
378{
379 return location().contains(x, y);
380}
381
382static bool is_valid_image(const std::string& str) { return !str.empty() && str[0] != IMAGE_PREFIX; }
383
384void 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
395void 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
407void 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
421 queue_redraw();
422}
423
424void 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
458void 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;
484}
485
486void 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
537void 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_EVENT_MOUSE_BUTTON_DOWN:
550 mouse_down(event.button);
551 break;
552 case SDL_EVENT_MOUSE_BUTTON_UP:
553 mouse_up(event.button);
554 break;
555 case SDL_EVENT_MOUSE_MOTION:
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
@ 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
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
rect textRect_
Definition button.hpp:76
texture pressedImage_
Definition button.hpp:72
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
virtual void set_location(const rect &rect)
Definition widget.cpp:69
void set_height(int h)
Definition widget.cpp:103
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:103
void reset()
Releases ownership of the managed texture and resets the ptr to null.
Definition texture.cpp:202
int h() const
The draw-space height of the texture, in pixels.
Definition texture.hpp:112
#define ERR_DP
Drawing functions, for drawing things on the screen.
Declarations for File-IO.
std::string label
What to show in the filter's drop-down list.
Definition manager.cpp:201
Standard logging facilities (interface).
void blit(const texture &tex, const ::rect &dst)
Draws a texture, or part of a texture, at the given location.
Definition draw.cpp:409
bool file_exists(const std::string &name)
Returns true if a file or directory with such name already exists.
Graphical text output.
const color_t TITLE_COLOR
const color_t GRAY_COLOR
const int SIZE_BUTTON
Definition constants.cpp:25
rect pango_draw_text(bool actually_draw, const rect &area, int size, const color_t &color, const std::string &text, int x, int y)
Draws text on the screen.
const std::string checkbox_release
const std::string button_press
std::string path
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:955
void play_UI_sound(const std::string &files)
Definition sound.cpp:1009
STL namespace.
std::vector< std::string > split(const config_attribute_value &val)
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:51
Base class for all the errors encountered by the engine.
An abstract description of a rectangle with integer coordinates.
Definition rect.hpp:49
bool contains(int x, int y) const
Whether the given point lies within the rectangle.
Definition rect.cpp:54
static lg::log_domain log_display("display")
char const IMAGE_PREFIX
char const COLUMN_SEPARATOR
#define i
#define h