The Battle for Wesnoth  1.19.5+dev
scrollbar.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 
19 
20 #include "gui/core/log.hpp"
21 #include "gui/widgets/window.hpp" // Needed for invalidate_layout()
22 
23 #include <functional>
24 
25 #define LOG_SCOPE_HEADER get_control_type() + " [" + id() + "] " + __func__
26 #define LOG_HEADER LOG_SCOPE_HEADER + ':'
27 
28 namespace gui2
29 {
30 
31 scrollbar_base::scrollbar_base(const implementation::builder_styled_widget& builder, const std::string& control_type)
32  : styled_widget(builder, control_type)
33  , state_(ENABLED)
34  , item_count_(0)
35  , item_position_(0)
36  , visible_items_(1)
37  , step_size_(1)
38  , pixels_per_step_(0.0)
39  , mouse_(0, 0)
40  , positioner_offset_(0)
41  , positioner_length_(0)
42 {
43  connect_signal<event::MOUSE_ENTER>(std::bind(
44  &scrollbar_base::signal_handler_mouse_enter, this, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4));
45  connect_signal<event::MOUSE_MOTION>(std::bind(
46  &scrollbar_base::signal_handler_mouse_motion, this, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, std::placeholders::_5));
47  connect_signal<event::SDL_TOUCH_MOTION>(std::bind(
48  &scrollbar_base::signal_handler_mouse_motion, this, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, std::placeholders::_5));
49  connect_signal<event::MOUSE_LEAVE>(std::bind(
50  &scrollbar_base::signal_handler_mouse_leave, this, std::placeholders::_2, std::placeholders::_3));
51  connect_signal<event::LEFT_BUTTON_DOWN>(std::bind(
52  &scrollbar_base::signal_handler_left_button_down, this, std::placeholders::_2, std::placeholders::_3));
53  connect_signal<event::LEFT_BUTTON_UP>(std::bind(
54  &scrollbar_base::signal_handler_left_button_up, this, std::placeholders::_2, std::placeholders::_3));
55 }
56 
58 {
59  // These values won't change so set them once.
60  for(auto& tmp : get_canvases()) {
61  tmp.set_variable("offset_before", wfl::variant(offset_before()));
62  tmp.set_variable("offset_after", wfl::variant(offset_after()));
63  }
64 }
65 
66 void scrollbar_base::scroll_by(const int pixels)
67 {
68  move_positioner(static_cast<int>(pixels_per_step_ * pixels));
69 }
70 
72 {
73  switch(scroll) {
74  case BEGIN:
76  break;
77 
78  case ITEM_BACKWARDS:
79  if(item_position_) {
81  }
82  break;
83 
87  : 0);
88  break;
89 
90  case JUMP_BACKWARDS:
93  : 0);
94  break;
95 
96  case END:
98  break;
99 
100  case ITEM_FORWARD:
102  break;
103 
104  case HALF_JUMP_FORWARD:
106  break;
107 
108  case JUMP_FORWARD:
110  break;
111 
112  default:
113  assert(false);
114  }
115 
116  /*
117  * @note I'm not sure if this should be in @ref set_item_position, but right now it
118  * causes weird viewport issues with listboxes. Selecting certain items makes the
119  * viewport to jump to the top, likely having to do with @ref recalculate dispatching
120  * an event (since it calls set_item_position). This seems like a safe spot for now.
121  *
122  * -- vultraz, 2017-09-13
123  */
124  fire(event::NOTIFY_MODIFIED, *this, nullptr);
125 }
126 
127 void scrollbar_base::place(const point& origin, const point& size)
128 {
129  // Inherited.
130  styled_widget::place(origin, size);
131 
132  recalculate();
133 }
134 
135 void scrollbar_base::set_active(const bool active)
136 {
137  if(get_active() != active) {
138  set_state(active ? ENABLED : DISABLED);
139  }
140 }
141 
143 {
144  return state_ != DISABLED;
145 }
146 
148 {
149  return state_;
150 }
151 
152 void scrollbar_base::set_item_position(const unsigned item_position)
153 {
154  // Set the value always execute since we update a part of the state.
155  item_position_ = item_position > item_count_ - visible_items_
157  : item_position;
158 
160 
161  if(all_items_visible()) {
162  item_position_ = 0;
163  }
164 
165  // Determine the pixel offset of the item position.
167  = static_cast<unsigned>(item_position_ * pixels_per_step_);
168 
169  update_canvas();
170 
172 
173 #if 0
174  /** See comment in @ref scroll for more info. */
175  fire(event::NOTIFY_MODIFIED, *this, nullptr);
176 #endif
177 }
178 
180 {
181  for(auto & tmp : get_canvases())
182  {
183  tmp.set_variable("positioner_offset", wfl::variant(positioner_offset_));
184  tmp.set_variable("positioner_length", wfl::variant(positioner_length_));
185  }
186  queue_redraw();
187 }
188 
190 {
191  if(state != state_) {
192  state_ = state;
193  queue_redraw();
194  }
195 }
196 
198 {
199  // We can be called before the size has been set up in that case we can't do
200  // the proper recalculation so stop before we die with an assert.
201  if(!get_length()) {
202  return;
203  }
204 
205  // Get the available size for the slider to move.
206  const int available_length = get_length() - offset_before()
207  - offset_after();
208 
209  assert(available_length > 0);
210 
211  // All visible.
212  if(item_count_ <= visible_items_) {
214  positioner_length_ = available_length;
216  item_position_ = 0;
217  update_canvas();
218  return;
219  }
220 
221  /**
222  * @todo In the MP lobby it can happen that a listbox has first zero items,
223  * then gets filled and since there are no visible items the second assert
224  * after this block will be triggered. Use this ugly hack to avoid that
225  * case. (This hack also added the gui/widgets/window.hpp include.)
226  */
227  if(!visible_items_) {
228  window* window = get_window();
229  assert(window);
232  << " Can't recalculate size, force a window layout phase.";
233  return;
234  }
235 
236  assert(step_size_);
237  assert(visible_items_);
238 
239  const unsigned steps = (item_count_ - visible_items_ - step_size_)
240  / step_size_;
241 
242  positioner_length_ = available_length * visible_items_ / item_count_;
244 
245  // Make sure we can also show the last step, so add one more step.
246  pixels_per_step_ = (available_length - positioner_length_)
247  / static_cast<float>(steps + 1);
248 
250 #if 0
251  PLAIN_LOG << "Scrollbar recalculate overview:\n"
252  << "item_count_ " << item_count_
253  << " visible_items_ " << visible_items_
254  << " step_size_ " << step_size_
255  << " steps " << steps
256  << "\n"
257  << "minimum_positioner_length() " << minimum_positioner_length()
258  << " maximum_positioner_length() " << maximum_positioner_length()
259  << "\n"
260  << " positioner_length_ " << positioner_length_
261  << " positioner_offset_ " << positioner_offset_
262  << "\n"
263  << "available_length " << available_length
264  << " pixels_per_step_ " << pixels_per_step_
265  << ".\n\n";
266 #endif
267 }
268 
270 {
271  const unsigned minimum = minimum_positioner_length();
272  const unsigned maximum = maximum_positioner_length();
273 
274  if(minimum == maximum) {
275  positioner_length_ = maximum;
276  } else if(maximum != 0 && positioner_length_ > maximum) {
277  positioner_length_ = maximum;
278  } else if(positioner_length_ < minimum) {
279  positioner_length_ = minimum;
280  }
281 }
282 
283 void scrollbar_base::move_positioner(const int distance)
284 {
285  if(distance < 0 && -distance > static_cast<int>(positioner_offset_)) {
286  positioner_offset_ = 0;
287  } else {
288  positioner_offset_ += distance;
289  }
290 
291  const unsigned length = get_length() - offset_before() - offset_after()
293 
294  if(positioner_offset_ > length) {
295  positioner_offset_ = length;
296  }
297 
298  unsigned position
299  = static_cast<unsigned>(positioner_offset_ / pixels_per_step_);
300 
301  // Note due to floating point rounding the position might be outside the
302  // available positions so set it back.
303  if(position > item_count_ - visible_items_) {
304  position = item_count_ - visible_items_;
305  }
306 
307  if(position != item_position_) {
308  item_position_ = position;
309 
311 
312  fire(event::NOTIFY_MODIFIED, *this, nullptr);
313 
314  // positioner_moved_notifier_.notify();
315  }
316 #if 0
317  PLAIN_LOG << "Scrollbar move overview:\n"
318  << "item_count_ " << item_count_
319  << " visible_items_ " << visible_items_
320  << " step_size_ " << step_size_
321  << "\n"
322  << "minimum_positioner_length() " << minimum_positioner_length()
323  << " maximum_positioner_length() " << maximum_positioner_length()
324  << "\n"
325  << " positioner_length_ " << positioner_length_
326  << " positioner_offset_ " << positioner_offset_
327  << "\n"
328  << " pixels_per_step_ " << pixels_per_step_
329  << " item_position_ " << item_position_
330  << ".\n\n";
331 #endif
332  update_canvas();
333 }
334 
336  bool& handled,
337  bool& halt)
338 {
339  DBG_GUI_E << LOG_HEADER << ' ' << event << ".";
340 
341  // Send the motion under our event id to make debugging easier.
342  signal_handler_mouse_motion(event, handled, halt, get_mouse_position());
343 }
344 
346  bool& handled,
347  bool& halt,
348  const point& coordinate)
349 {
350  DBG_GUI_E << LOG_HEADER << ' ' << event << " at " << coordinate << ".";
351 
352  point mouse = coordinate;
353  mouse.x -= get_x();
354  mouse.y -= get_y();
355 
356  switch(state_) {
357  case ENABLED:
358  if(on_positioner(mouse)) {
360  }
361 
362  break;
363 
364  case PRESSED: {
365  if(in_orthogonal_range(mouse)) {
366  const int distance = get_length_difference(mouse_, mouse);
367  mouse_ = mouse;
368  move_positioner(distance);
369  }
370 
371  } break;
372 
373  case FOCUSED:
374  if(!on_positioner(mouse)) {
376  }
377  break;
378 
379  case DISABLED:
380  // Shouldn't be possible, but seems to happen in the lobby
381  // if a resize layout happens during dragging.
382  halt = true;
383  break;
384 
385  default:
386  assert(false);
387  }
388  handled = true;
389 }
390 
392  bool& handled)
393 {
394  DBG_GUI_E << LOG_HEADER << ' ' << event << ".";
395 
396  if(state_ == FOCUSED) {
398  }
399  handled = true;
400 }
401 
402 
404  bool& handled)
405 {
406  DBG_GUI_E << LOG_HEADER << ' ' << event << ".";
407 
408  point mouse = get_mouse_position();
409  mouse.x -= get_x();
410  mouse.y -= get_y();
411 
412  if(on_positioner(mouse)) {
413  assert(get_window());
414  mouse_ = mouse;
417  }
418 
419  const int bar = on_bar(mouse);
420 
421  if(bar == -1) {
423  // positioner_moved_notifier_.notify();
424  } else if(bar == 1) {
426  // positioner_moved_notifier_.notify();
427  } else {
428  assert(bar == 0);
429  }
430 
431  handled = true;
432 }
433 
435  bool& handled)
436 {
437  DBG_GUI_E << LOG_HEADER << ' ' << event << ".";
438 
439  point mouse = get_mouse_position();
440  mouse.x -= get_x();
441  mouse.y -= get_y();
442 
443  if(state_ != PRESSED) {
444  return;
445  }
446 
447  assert(get_window());
448  get_window()->mouse_capture(false);
449 
450  if(on_positioner(mouse)) {
452  } else {
454  }
455 
456  handled = true;
457 }
458 
459 } // namespace gui2
bool fire(const ui_event event, widget &target)
Fires an event which has no extra parameters.
Definition: dispatcher.cpp:74
virtual unsigned minimum_positioner_length() const =0
The minimum length of the positioner.
virtual bool get_active() const override
See styled_widget::get_active.
Definition: scrollbar.cpp:142
void set_item_position(const unsigned item_position)
Note the position isn't guaranteed to be the wanted position the step size is honored.
Definition: scrollbar.cpp:152
void signal_handler_mouse_leave(const event::ui_event event, bool &handled)
Definition: scrollbar.cpp:391
void signal_handler_left_button_up(const event::ui_event event, bool &handled)
Definition: scrollbar.cpp:434
virtual void update_canvas() override
See styled_widget::update_canvas.
Definition: scrollbar.cpp:179
unsigned positioner_offset_
The start offset of the positioner.
Definition: scrollbar.hpp:268
float pixels_per_step_
Number of pixels per step.
Definition: scrollbar.hpp:254
virtual void move_positioner(const int distance)
Moves the positioner.
Definition: scrollbar.cpp:283
virtual bool in_orthogonal_range(const point &coordinate) const =0
Is the coordinate in the bar's orthogonal range?
void signal_handler_left_button_down(const event::ui_event event, bool &handled)
Definition: scrollbar.cpp:403
bool all_items_visible() const
Are all items visible?
Definition: scrollbar.hpp:92
virtual void place(const point &origin, const point &size) override
See widget::place.
Definition: scrollbar.cpp:127
scrollbar_base(const implementation::builder_styled_widget &builder, const std::string &control_type)
Definition: scrollbar.cpp:31
virtual void child_callback_positioner_moved()
Callback for subclasses to get notified about positioner movement.
Definition: scrollbar.hpp:211
virtual unsigned maximum_positioner_length() const =0
The maximum length of the positioner.
state_t
Possible states of the widget.
Definition: scrollbar.hpp:118
virtual int on_bar(const point &coordinate) const =0
Is the coordinate on the bar?
void set_state(const state_t state)
Definition: scrollbar.cpp:189
unsigned positioner_length_
The current length of the positioner.
Definition: scrollbar.hpp:271
unsigned item_count_
The number of items the scrollbar 'holds'.
Definition: scrollbar.hpp:226
void recalculate()
Updates the scrollbar.
Definition: scrollbar.cpp:197
unsigned visible_items_
The number of items which can be shown at the same time.
Definition: scrollbar.hpp:236
virtual unsigned offset_after() const =0
The number of pixels we can't use since they're used for borders.
virtual int get_length_difference(const point &original, const point &current) const =0
Gets the relevant difference in between the two positions.
virtual unsigned get_state() const override
See styled_widget::get_state.
Definition: scrollbar.cpp:147
point mouse_
The position the mouse was at the last movement.
Definition: scrollbar.hpp:261
void scroll(const scroll_mode scroll)
Sets the item position.
Definition: scrollbar.cpp:71
state_t state_
Current state of the widget.
Definition: scrollbar.hpp:223
virtual unsigned get_length() const =0
Get the length of the scrollbar.
scroll_mode
scroll 'step size'.
Definition: scrollbar.hpp:52
@ ITEM_FORWARD
Go one item towards the end.
Definition: scrollbar.hpp:59
@ ITEM_BACKWARDS
Go one item towards the begin.
Definition: scrollbar.hpp:54
@ END
Go to the end position.
Definition: scrollbar.hpp:58
@ HALF_JUMP_FORWARD
Go half the visible items towards the end.
Definition: scrollbar.hpp:60
@ BEGIN
Go to begin position.
Definition: scrollbar.hpp:53
@ JUMP_BACKWARDS
Go the visible items towards the begin.
Definition: scrollbar.hpp:57
@ HALF_JUMP_BACKWARDS
Go half the visible items towards the begin.
Definition: scrollbar.hpp:55
void recalculate_positioner()
Updates the positioner.
Definition: scrollbar.cpp:269
void signal_handler_mouse_enter(const event::ui_event event, bool &handled, bool &halt)
Definition: scrollbar.cpp:335
virtual unsigned offset_before() const =0
The number of pixels we can't use since they're used for borders.
unsigned item_position_
The item the positioner is at, starts at 0.
Definition: scrollbar.hpp:229
void scroll_by(const int pixels)
Definition: scrollbar.cpp:66
virtual void set_active(const bool active) override
See styled_widget::set_active.
Definition: scrollbar.cpp:135
void signal_handler_mouse_motion(const event::ui_event event, bool &handled, bool &halt, const point &coordinate)
Definition: scrollbar.cpp:345
unsigned step_size_
Number of items moved when scrolling.
Definition: scrollbar.hpp:245
virtual bool on_positioner(const point &coordinate) const =0
Is the coordinate on the positioner?
std::vector< canvas > & get_canvases()
virtual void place(const point &origin, const point &size) override
See widget::place.
void queue_redraw()
Indicates that this widget should be redrawn.
Definition: widget.cpp:464
int get_x() const
Definition: widget.cpp:326
int get_y() const
Definition: widget.cpp:331
window * get_window()
Get the parent window.
Definition: widget.cpp:117
base class of top level items, the only item which needs to store the final canvases to draw on.
Definition: window.hpp:61
void invalidate_layout()
Updates the size of the window.
Definition: window.cpp:761
void mouse_capture(const bool capture=true)
Definition: window.cpp:1201
Define the common log macros for the gui toolkit.
#define ERR_GUI_G
Definition: log.hpp:44
#define DBG_GUI_E
Definition: log.hpp:35
#define LOG_HEADER
Definition: scrollbar.cpp:26
This file contains the window object, this object is a top level container which has the event manage...
#define PLAIN_LOG
Definition: log.hpp:299
ui_event
The event sent to the dispatcher.
Definition: handler.hpp:115
@ NOTIFY_MODIFIED
Definition: handler.hpp:158
Generic file dialog.
point get_mouse_position()
Returns the current mouse position.
Definition: helper.cpp:143
map_location coordinate
Contains an x and y coordinate used for starting positions in maps.
std::size_t size(const std::string &str)
Length in characters of a UTF-8 string.
Definition: unicode.cpp:85
Holds a 2D point.
Definition: point.hpp:25