The Battle for Wesnoth 1.21.0-dev
 
Loading...
Searching...
No Matches
scrollbar.cpp
Go to the documentation of this file.
1/*
2 Copyright (C) 2008 - 2025
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
28namespace gui2
29{
30
31scrollbar_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
66void 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
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
127void scrollbar_base::place(const point& origin, const point& size)
128{
129 // Inherited.
130 styled_widget::place(origin, size);
131
132 recalculate();
133}
134
135void 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
152void 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
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.
214 positioner_length_ = available_length;
216 item_position_ = 0;
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_) {
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
283void scrollbar_base::move_positioner(const int distance)
284{
285 if(distance < 0 && -distance > static_cast<int>(positioner_offset_)) {
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
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.
virtual unsigned minimum_positioner_length() const =0
The minimum length of the positioner.
virtual bool get_active() const override
See styled_widget::get_active.
void set_item_position(const unsigned item_position)
Note the position isn't guaranteed to be the wanted position the step size is honored.
void signal_handler_mouse_leave(const event::ui_event event, bool &handled)
void signal_handler_left_button_up(const event::ui_event event, bool &handled)
virtual void update_canvas() override
See styled_widget::update_canvas.
unsigned positioner_offset_
The start offset of the positioner.
float pixels_per_step_
Number of pixels per step.
virtual void move_positioner(const int distance)
Moves the positioner.
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)
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.
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.
virtual unsigned maximum_positioner_length() const =0
The maximum length of the positioner.
state_t
Possible states of the widget.
virtual int on_bar(const point &coordinate) const =0
Is the coordinate on the bar?
void set_state(const state_t state)
unsigned positioner_length_
The current length of the positioner.
unsigned item_count_
The number of items the scrollbar 'holds'.
void recalculate()
Updates the scrollbar.
unsigned visible_items_
The number of items which can be shown at the same time.
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.
point mouse_
The position the mouse was at the last movement.
void scroll(const scroll_mode scroll)
Sets the item position.
Definition scrollbar.cpp:71
state_t state_
Current state of the widget.
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.
void signal_handler_mouse_enter(const event::ui_event event, bool &handled, bool &halt)
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.
void scroll_by(const int pixels)
Definition scrollbar.cpp:66
virtual void set_active(const bool active) override
See styled_widget::set_active.
void signal_handler_mouse_motion(const event::ui_event event, bool &handled, bool &halt, const point &coordinate)
unsigned step_size_
Number of items moved when scrolling.
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:59
void invalidate_layout()
Updates the size of the window.
Definition window.cpp:759
void mouse_capture(const bool capture=true)
Definition window.cpp:1191
#define LOG_HEADER
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
This file contains the window object, this object is a top level container which has the event manage...
#define PLAIN_LOG
Definition log.hpp:296
ui_event
The event sent to the dispatcher.
Definition handler.hpp:115
Generic file dialog.
point get_mouse_position()
Returns the current mouse position.
Definition helper.cpp:167
Holds a 2D point.
Definition point.hpp:25