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