The Battle for Wesnoth  1.19.0-dev
slider.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2008 - 2024
3  by Mark de Wever <koraq@xs4all.nl>
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 "gui/widgets/slider.hpp"
19 
20 #include "formatter.hpp"
21 #include "gettext.hpp"
22 #include "gui/core/log.hpp"
24 #include "gui/widgets/settings.hpp"
25 #include "gui/widgets/window.hpp"
26 #include "sdl/rect.hpp"
27 #include "sound.hpp"
28 #include "gettext.hpp"
29 #include "utils/math.hpp"
30 #include "wml_exception.hpp"
31 
32 #include <functional>
33 #include <numeric>
34 
35 #define LOG_SCOPE_HEADER get_control_type() + " [" + id() + "] " + __func__
36 #define LOG_HEADER LOG_SCOPE_HEADER + ':'
37 
38 namespace gui2
39 {
40 // ------------ WIDGET -----------{
41 
42 REGISTER_WIDGET(slider)
43 
44 slider::slider(const implementation::builder_slider& builder)
45  : slider_base(builder, type())
46  , best_slider_length_(0)
47  , minimum_value_(0)
48  , step_size_(1)
49  , minimum_value_label_()
50  , maximum_value_label_()
51  , value_label_generator_()
52  , current_item_mouse_position_(0, 0)
53 {
54  connect_signal<event::SDL_KEY_DOWN>(std::bind(&slider::signal_handler_sdl_key_down, this, std::placeholders::_2, std::placeholders::_3, std::placeholders::_5));
55 
56  // connect_signal<event::LEFT_BUTTON_DOWN>(
57  // std::bind(&slider::signal_handler_left_button_down, this, std::placeholders::_2, std::placeholders::_3));
58 
59  connect_signal<event::LEFT_BUTTON_UP>(std::bind(&slider::signal_handler_left_button_up, this, std::placeholders::_2, std::placeholders::_3));
60 }
61 
63 {
65 
66  // Inherited.
68 
69  if(best_slider_length_ != 0) {
70  // Override length.
71  const auto conf = cast_config_to<slider_definition>();
72  assert(conf);
73 
74  result.x = conf->left_offset + best_slider_length_ + conf->right_offset;
75  }
76 
77  DBG_GUI_L << LOG_HEADER << " best_slider_length " << best_slider_length_ << " result " << result << ".";
78  return result;
79 }
80 
81 void slider::set_value(int value)
82 {
83  value = std::clamp(value, minimum_value_, get_maximum_value());
84  int old_value = get_value();
85 
86  if(value == old_value) {
87  return;
88  }
89 
91 
92  if(std::abs(get_value() - value) > (step_size_ / 2)) {
93  ERR_GUI_G << "slider::set_value error:"
94  << " old_value=" << old_value
95  << " new_value=" << get_value()
96  << " desired_value=" << value
97  << " minimum_value=" << minimum_value_
98  << " maximum_value=" << get_maximum_value()
99  << " step_size=" << step_size_;
100  assert(false);
101  }
102 
103  fire(event::NOTIFY_MODIFIED, *this, nullptr);
104 }
105 
107 {
110  } else if(!minimum_value_label_.empty() && get_value() == get_minimum_value()) {
111  return minimum_value_label_;
112  } else if(!maximum_value_label_.empty() && get_value() == get_maximum_value()) {
113  return maximum_value_label_;
114  }
115 
116  return t_string(formatter() << get_value());
117 }
118 
120 {
122 }
123 
125 {
126  const auto conf = cast_config_to<slider_definition>();
127  assert(conf);
128  return conf->positioner_length;
129 }
130 
131 unsigned slider::offset_before() const
132 {
133  const auto conf = cast_config_to<slider_definition>();
134  assert(conf);
135  return conf->left_offset;
136 }
137 
138 unsigned slider::offset_after() const
139 {
140  const auto conf = cast_config_to<slider_definition>();
141  assert(conf);
142  return conf->right_offset;
143 }
144 
146 {
147  rect positioner_rect(
149  );
150 
151  // Note we assume the positioner is over the entire height of the widget.
152  return positioner_rect.contains(coordinate);
153 }
154 
155 int slider::on_bar(const point& coordinate) const
156 {
157  const unsigned x = static_cast<std::size_t>(coordinate.x);
158  const unsigned y = static_cast<std::size_t>(coordinate.y);
159 
160  // Not on the widget, leave.
161  if(x > get_width() || y > get_height()) {
162  return 0;
163  }
164 
165  // we also assume the bar is over the entire height of the widget.
166  if(x < get_positioner_offset()) {
167  return -1;
168  } else if(x > get_positioner_offset() + get_positioner_length()) {
169  return 1;
170  }
171 
172  return 0;
173 }
174 
176 {
177  // Inherited.
179 
180  for(auto& tmp : get_canvases()) {
181  tmp.set_variable("text", wfl::variant(get_value_label()));
182  }
183 }
184 
185 void slider::handle_key_decrease(bool& handled)
186 {
188 
189  handled = true;
190 
192 }
193 
194 void slider::handle_key_increase(bool& handled)
195 {
197 
198  handled = true;
199 
201 }
202 
203 void slider::signal_handler_sdl_key_down(const event::ui_event event, bool& handled, const SDL_Keycode key)
204 {
205  DBG_GUI_E << LOG_HEADER << ' ' << event << ".";
206 
207  if(key == SDLK_DOWN || key == SDLK_LEFT) {
208  handle_key_decrease(handled);
209  } else if(key == SDLK_UP || key == SDLK_RIGHT) {
210  handle_key_increase(handled);
211  } else {
212  // Do nothing. Ignore other keys.
213  }
214 }
215 
216 #if 0
217 void slider::signal_handler_left_button_down(const event::ui_event event, bool& handled)
218 {
219  DBG_GUI_E << LOG_HEADER << ' ' << event << ".";
220 
221  update_current_item_mouse_position();
222 
223  handled = true;
224 }
225 #endif
226 
228 {
229  DBG_GUI_E << LOG_HEADER << ' ' << event << ".";
230 
231  get_window()->keyboard_capture(this);
232 
233  handled = true;
234 }
235 
236 static t_string default_value_label_generator(const std::vector<t_string>& value_labels, int item_position, int max)
237 {
238  assert(static_cast<int>(value_labels.size()) == max);
239  assert(item_position < max && item_position >= 0);
240  return value_labels[item_position];
241 }
242 
243 void slider::set_value_labels(const std::vector<t_string>& value_labels)
244 {
245  // Don't use std::ref because we want to store value_labels in the closure.
246  set_value_labels(std::bind(&default_value_label_generator, value_labels, std::placeholders::_1, std::placeholders::_2));
247 }
248 
249 
250 void slider::set_value_range(int min_value, int max_value)
251 {
252  // Settng both at once instead of having multiple functions set_min(),
253  // set_max() ... fixes an old problem where in cases like
254  // set_min(-10);set_min(-1);
255  // min and max would tmporarily have invalid values where since the starting max value is 0;
256 
257  VALIDATE(min_value <= max_value, "invalid slider data");
258  if (min_value == minimum_value_ && max_value == get_maximum_value()) {
259  return;
260  }
261 
262  int diff = max_value - min_value;
263  int old_value = get_value();
264 
265  step_size_ = std::gcd(diff, step_size_);
266  minimum_value_ = min_value;
267 
269  set_value(old_value);
270 
271  assert(min_value == get_minimum_value());
272  assert(max_value == get_maximum_value());
273 
274 }
275 
276 void slider::set_step_size(int step_size)
277 {
278  const int old_min_value = get_minimum_value();
279  const int old_max_value = get_maximum_value();
280 
281  const int range_diff = get_item_count() - 1;
282  const int old_value = get_value();
283 
284  step_size_ = std::gcd(range_diff, step_size);
285 
286  slider_set_item_last(range_diff / step_size_);
287  set_value(old_value);
288 
289  assert(old_min_value == get_minimum_value());
290  assert(old_max_value == get_maximum_value());
291 }
292 
293 // }---------- DEFINITION ---------{
294 
297 {
298  DBG_GUI_P << "Parsing slider " << id;
299 
300  load_resolutions<resolution>(cfg);
301 }
302 
304  : resolution_definition(cfg)
305  , positioner_length(cfg["minimum_positioner_length"])
306  , left_offset(cfg["left_offset"])
307  , right_offset(cfg["right_offset"])
308 {
309  VALIDATE(positioner_length, missing_mandatory_wml_key("resolution", "minimum_positioner_length"));
310 
311  // Note the order should be the same as the enum state_t is slider.hpp.
312  state.emplace_back(VALIDATE_WML_CHILD(cfg, "state_enabled", missing_mandatory_wml_tag("slider_definition][resolution", "state_enabled")));
313  state.emplace_back(VALIDATE_WML_CHILD(cfg, "state_disabled", missing_mandatory_wml_tag("slider_definition][resolution", "state_disabled")));
314  state.emplace_back(VALIDATE_WML_CHILD(cfg, "state_pressed", missing_mandatory_wml_tag("slider_definition][resolution", "state_pressed")));
315  state.emplace_back(VALIDATE_WML_CHILD(cfg, "state_focused", missing_mandatory_wml_tag("slider_definition][resolution", "state_focused")));
316 }
317 
318 // }---------- BUILDER -----------{
319 
320 namespace implementation
321 {
322 builder_slider::builder_slider(const config& cfg)
324  , best_slider_length_(cfg["best_slider_length"])
325  , minimum_value_(cfg["minimum_value"])
326  , maximum_value_(cfg["maximum_value"])
327  , step_size_(cfg["step_size"].to_int(1))
328  , value_(cfg["value"])
329  , minimum_value_label_(cfg["minimum_value_label"].t_str())
330  , maximum_value_label_(cfg["maximum_value_label"].t_str())
331  , value_labels_()
332 {
333  auto labels = cfg.optional_child("value_labels");
334  if(!labels) {
335  return;
336  }
337 
338  for(const auto& label : labels->child_range("value")) {
339  value_labels_.push_back(label["label"]);
340  }
341 }
342 
343 std::unique_ptr<widget> builder_slider::build() const
344 {
345  auto widget = std::make_unique<slider>(*this);
346 
347  widget->set_best_slider_length(best_slider_length_);
348  widget->set_value_range(minimum_value_, maximum_value_);
349  widget->set_step_size(step_size_);
350  widget->set_value(value_);
351 
352  widget->finalize_setup();
353 
354  if(!value_labels_.empty()) {
355  VALIDATE(value_labels_.size() == static_cast<std::size_t>(widget->get_item_count()),
356  _("The number of value_labels and values don't match."));
357 
358  widget->set_value_labels(value_labels_);
359 
360  } else {
361  widget->set_minimum_value_label(minimum_value_label_);
362  widget->set_maximum_value_label(maximum_value_label_);
363  }
364 
365  DBG_GUI_G << "Window builder: placed slider '" << id << "' with definition '" << definition << "'.";
366 
367  return widget;
368 }
369 
370 } // namespace implementation
371 
372 // }------------ END --------------
373 
374 } // namespace gui2
A config object defines a single node in a WML file, with access to child nodes.
Definition: config.hpp:159
optional_config_impl< config > optional_child(config_key_type key, int n=0)
Euivalent to mandatory_child, but returns an empty optional if the nth child was not found.
Definition: config.cpp:385
std::ostringstream wrapper.
Definition: formatter.hpp:40
bool fire(const ui_event event, widget &target)
Fires an event which has no extra parameters.
Definition: dispatcher.cpp:74
A label displays text that can be wrapped but no scrollbars are provided.
Definition: label.hpp:56
Base class for a scroll bar.
Definition: slider_base.hpp:39
virtual void update_canvas() override
See styled_widget::update_canvas.
@ ITEM_FORWARD
Go one item towards the end.
Definition: slider_base.hpp:55
@ ITEM_BACKWARDS
Go one item towards the begin.
Definition: slider_base.hpp:51
unsigned get_positioner_offset() const
unsigned get_slider_position() const
void signal_handler_left_button_down(const event::ui_event event, bool &handled)
void slider_set_item_last(const unsigned item_last)
void set_slider_position(int item_position)
Note the position isn't guaranteed to be the wanted position the step size is honored.
void scroll(const scroll_mode scroll)
Sets the item position.
Definition: slider_base.cpp:66
unsigned get_positioner_length() const
A slider is a control that can select a value by moving a grip on a groove.
Definition: slider.hpp:59
void set_step_size(int step_size)
Definition: slider.cpp:276
virtual void child_callback_positioner_moved() override
Inherited from scrollbar_base.
Definition: slider.cpp:119
t_string maximum_value_label_
When the slider shows the maximum value can show a special text.
Definition: slider.hpp:206
void handle_key_decrease(bool &handled)
Handlers for keyboard input.
Definition: slider.cpp:185
unsigned best_slider_length_
The best size for the slider part itself, if 0 ignored.
Definition: slider.hpp:152
unsigned offset_before() const override
Inherited from scrollbar_base.
Definition: slider.cpp:131
void signal_handler_sdl_key_down(const event::ui_event event, bool &handled, const SDL_Keycode key)
Signal handlers:
Definition: slider.cpp:203
int minimum_value_
The minimum value the slider holds.
Definition: slider.hpp:160
virtual int get_minimum_value() const override
Inherited from integer_selector.
Definition: slider.hpp:84
int on_bar(const point &coordinate) const override
Inherited from scrollbar_base.
Definition: slider.cpp:155
void handle_key_increase(bool &handled)
Definition: slider.cpp:194
virtual void set_value(int value) override
Inherited from integer_selector.
Definition: slider.cpp:81
virtual point calculate_best_size() const override
See widget::calculate_best_size.
Definition: slider.cpp:62
int step_size_
Definition: slider.hpp:161
virtual int get_maximum_value() const override
Inherited from integer_selector.
Definition: slider.hpp:90
void set_value_range(int min_value, int max_value)
Definition: slider.cpp:250
virtual int get_value() const override
Inherited from integer_selector.
Definition: slider.hpp:78
label_generator value_label_generator_
Function to output custom value labels for the slider.
Definition: slider.hpp:213
int get_item_count() const
Definition: slider.hpp:96
t_string get_value_label() const
Returns the label shown for the current value.
Definition: slider.cpp:106
int positioner_length() const override
Inherited from scrollbar_base.
Definition: slider.cpp:124
t_string minimum_value_label_
When the slider shows the minimum value can show a special text.
Definition: slider.hpp:200
bool on_positioner(const point &coordinate) const override
Inherited from scrollbar_base.
Definition: slider.cpp:145
void signal_handler_left_button_up(const event::ui_event event, bool &handled)
Definition: slider.cpp:227
virtual void update_canvas() override
Inherited from scrollbar_base.
Definition: slider.cpp:175
void set_value_labels(const std::vector< t_string > &value_labels)
Definition: slider.cpp:243
unsigned offset_after() const override
Inherited from scrollbar_base.
Definition: slider.cpp:138
std::vector< canvas > & get_canvases()
virtual point calculate_best_size() const override
See widget::calculate_best_size.
Base class for all widgets.
Definition: widget.hpp:53
unsigned get_width() const
Definition: widget.cpp:327
unsigned get_height() const
Definition: widget.cpp:332
window * get_window()
Get the parent window.
Definition: widget.cpp:117
void keyboard_capture(widget *widget)
Definition: window.cpp:1215
bool empty() const
Definition: tstring.hpp:186
static std::string _(const char *str)
Definition: gettext.hpp:93
Define the common log macros for the gui toolkit.
#define DBG_GUI_L
Definition: log.hpp:55
#define ERR_GUI_G
Definition: log.hpp:44
#define DBG_GUI_G
Definition: log.hpp:41
#define DBG_GUI_P
Definition: log.hpp:66
#define DBG_GUI_E
Definition: log.hpp:35
This file contains the window object, this object is a top level container which has the event manage...
#define log_scope2(domain, description)
Definition: log.hpp:275
General math utility functions.
int rounded_division(int a, int b)
Definition: math.hpp:186
ui_event
The event sent to the dispatcher.
Definition: handler.hpp:115
@ NOTIFY_MODIFIED
Definition: handler.hpp:158
std::string sound_slider_adjust
Definition: settings.cpp:51
Generic file dialog.
static t_string default_value_label_generator(const std::vector< t_string > &value_labels, int item_position, int max)
Definition: slider.cpp:236
lg::log_domain log_gui_layout("gui/layout")
Definition: log.hpp:54
Contains the implementation details for lexical_cast and shouldn't be used directly.
void play_UI_sound(const std::string &files)
Definition: sound.cpp:1064
map_location coordinate
Contains an x and y coordinate used for starting positions in maps.
Contains the SDL_Rect helper code.
#define REGISTER_WIDGET(id)
Wrapper for REGISTER_WIDGET3.
This file contains the settings handling of the widget library.
#define LOG_HEADER
Definition: slider.cpp:36
#define LOG_SCOPE_HEADER
Definition: slider.cpp:35
virtual std::unique_ptr< widget > build() const override
Definition: slider.cpp:343
std::vector< t_string > value_labels_
Definition: slider.hpp:290
std::string definition
Parameters for the styled_widget.
Base class of a resolution, contains the common keys for a resolution.
std::vector< state_definition > state
resolution(const config &cfg)
Definition: slider.cpp:303
slider_definition(const config &cfg)
Definition: slider.cpp:295
Holds a 2D point.
Definition: point.hpp:25
An abstract description of a rectangle with integer coordinates.
Definition: rect.hpp:47
bool contains(int x, int y) const
Whether the given point lies within the rectangle.
Definition: rect.cpp:52
std::string missing_mandatory_wml_tag(const std::string &section, const std::string &tag)
Returns a standard message for a missing wml child (tag).
std::string missing_mandatory_wml_key(const std::string &section, const std::string &key, const std::string &primary_key, const std::string &primary_value)
Returns a standard message for a missing wml key (attribute).
Add a special kind of assert to validate whether the input from WML doesn't contain any problems that...
#define VALIDATE_WML_CHILD(cfg, key, message)
#define VALIDATE(cond, message)
The macro to use for the validation of WML.