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
17 #include "gui/widgets/scrollbar_container_private.hpp"
18
19 #include "gui/auxiliary/find_widget.hpp"
20 #include "gui/core/event/message.hpp"
21 #include "gui/core/layout_exception.hpp"
22 #include "gui/core/log.hpp"
23 #include "gui/widgets/clickable_item.hpp"
24 #include "gui/widgets/spacer.hpp"
25 #include "gui/widgets/window.hpp"
26 #include "sdl/rect.hpp"
27
28 #include <algorithm>
29 #include "utils/functional.hpp"
30
31 #define LOG_SCOPE_HEADER get_control_type() + " [" + id() + "] " + __func__
32 #define LOG_HEADER LOG_SCOPE_HEADER + ':'
33
34 namespace gui2
35 {
36 namespace
37 {
38
39 static const std::string button_up_names[]
40 { "_begin", "_line_up", "_half_page_up", "_page_up" };
41
42 static const std::string button_down_names[]
43 { "_end", "_line_down", "_half_page_down", "_page_down" };
44
45 /**
46 * Returns a map with the names of all buttons and the scrollbar jump they're
47 * supposed to execute.
48 */
scroll_lookup()49 const std::map<std::string, scrollbar_base::scroll_mode>& scroll_lookup()
50 {
51 static std::map<std::string, scrollbar_base::scroll_mode> lookup;
52 if(lookup.empty()) {
53 lookup["_begin"] = scrollbar_base::BEGIN;
54 lookup["_line_up"] = scrollbar_base::ITEM_BACKWARDS;
55 lookup["_half_page_up"] = scrollbar_base::HALF_JUMP_BACKWARDS;
56 lookup["_page_up"] = scrollbar_base::JUMP_BACKWARDS;
57
58 lookup["_end"] = scrollbar_base::END;
59 lookup["_line_down"] = scrollbar_base::ITEM_FORWARD;
60 lookup["_half_page_down"] = scrollbar_base::HALF_JUMP_FORWARD;
61 lookup["_page_down"] = scrollbar_base::JUMP_FORWARD;
62 }
63
64 return lookup;
65 }
66
67 } // namespace
68
scrollbar_container(const implementation::builder_styled_widget & builder,const std::string & control_type)69 scrollbar_container::scrollbar_container(
70 const implementation::builder_styled_widget& builder, const std::string& control_type)
71 : container_base(builder, control_type)
72 , state_(ENABLED)
73 , vertical_scrollbar_mode_(AUTO_VISIBLE_FIRST_RUN)
74 , horizontal_scrollbar_mode_(AUTO_VISIBLE_FIRST_RUN)
75 , vertical_scrollbar_grid_(nullptr)
76 , horizontal_scrollbar_grid_(nullptr)
77 , vertical_scrollbar_(nullptr)
78 , horizontal_scrollbar_(nullptr)
79 , content_grid_(nullptr)
80 , content_(nullptr)
81 , content_visible_area_()
82 {
83 connect_signal<event::SDL_KEY_DOWN>(
84 std::bind(&scrollbar_container::signal_handler_sdl_key_down, this, _2, _3, _5, _6));
85
86 connect_signal<event::SDL_WHEEL_UP>(
87 std::bind(&scrollbar_container::signal_handler_sdl_wheel_up, this, _2, _3),
88 event::dispatcher::back_post_child);
89
90 connect_signal<event::SDL_WHEEL_DOWN>(
91 std::bind(&scrollbar_container::signal_handler_sdl_wheel_down, this, _2, _3),
92 event::dispatcher::back_post_child);
93
94 connect_signal<event::SDL_WHEEL_LEFT>(
95 std::bind(&scrollbar_container::signal_handler_sdl_wheel_left, this, _2, _3),
96 event::dispatcher::back_post_child);
97
98 connect_signal<event::SDL_WHEEL_RIGHT>(
99 std::bind(&scrollbar_container::signal_handler_sdl_wheel_right, this, _2, _3),
100 event::dispatcher::back_post_child);
101
102 connect_signal<event::SDL_TOUCH_MOTION>(
103 std::bind(&scrollbar_container::signal_handler_sdl_touch_motion,
104 this,
105 _2,
106 _3,
107 _5,
108 _6),
109 event::dispatcher::back_post_child);
110 }
111
layout_initialize(const bool full_initialization)112 void scrollbar_container::layout_initialize(const bool full_initialization)
113 {
114 // Inherited.
115 container_base::layout_initialize(full_initialization);
116
117 if(full_initialization) {
118 assert(vertical_scrollbar_grid_);
119
120 switch(vertical_scrollbar_mode_) {
121 case ALWAYS_VISIBLE:
122 vertical_scrollbar_grid_->set_visible(widget::visibility::visible);
123 break;
124
125 case AUTO_VISIBLE:
126 vertical_scrollbar_grid_->set_visible(widget::visibility::hidden);
127 break;
128
129 default:
130 vertical_scrollbar_grid_->set_visible(widget::visibility::invisible);
131 }
132
133 assert(horizontal_scrollbar_grid_);
134
135 switch(horizontal_scrollbar_mode_) {
136 case ALWAYS_VISIBLE:
137 horizontal_scrollbar_grid_->set_visible(widget::visibility::visible);
138 break;
139
140 case AUTO_VISIBLE:
141 horizontal_scrollbar_grid_->set_visible(widget::visibility::hidden);
142 break;
143
144 default:
145 horizontal_scrollbar_grid_->set_visible(widget::visibility::invisible);
146 }
147 }
148
149 assert(content_grid_);
150 content_grid_->layout_initialize(full_initialization);
151 }
152
request_reduce_height(const unsigned maximum_height)153 void scrollbar_container::request_reduce_height(const unsigned maximum_height)
154 {
155 DBG_GUI_L << LOG_HEADER << " requested height " << maximum_height << ".\n";
156
157 /*
158 * First ask the content to reduce it's height. This seems to work for now,
159 * but maybe some sizing hints will be required later.
160 */
161 /** @todo Evaluate whether sizing hints are required. */
162 assert(content_grid_);
163 const unsigned offset =
164 horizontal_scrollbar_grid_ && horizontal_scrollbar_grid_->get_visible() != widget::visibility::invisible
165 ? horizontal_scrollbar_grid_->get_best_size().y
166 : 0;
167
168 content_grid_->request_reduce_height(maximum_height - offset);
169
170 // Did we manage to achieve the wanted size?
171 point size = get_best_size();
172 if(static_cast<unsigned>(size.y) <= maximum_height) {
173 DBG_GUI_L << LOG_HEADER << " child honored request, height " << size.y << ".\n";
174 return;
175 }
176
177 if(vertical_scrollbar_mode_ == ALWAYS_INVISIBLE) {
178 DBG_GUI_L << LOG_HEADER << " request failed due to scrollbar mode.\n";
179 return;
180 }
181
182 assert(vertical_scrollbar_grid_);
183 const bool resized = vertical_scrollbar_grid_->get_visible() == widget::visibility::invisible;
184
185 // Always set the bar visible, is a nop is already visible.
186 vertical_scrollbar_grid_->set_visible(widget::visibility::visible);
187
188 const point scrollbar_size = vertical_scrollbar_grid_->get_best_size();
189
190 // If showing the scrollbar increased the height, hide and abort.
191 if(resized && scrollbar_size.y > size.y) {
192 vertical_scrollbar_grid_->set_visible(widget::visibility::invisible);
193 DBG_GUI_L << LOG_HEADER << " request failed, showing the scrollbar"
194 << " increased the height to " << scrollbar_size.y << ".\n";
195 return;
196 }
197
198 if(maximum_height > static_cast<unsigned>(scrollbar_size.y)) {
199 size.y = maximum_height;
200 } else {
201 size.y = scrollbar_size.y;
202 }
203
204 // FIXME adjust for the step size of the scrollbar
205
206 set_layout_size(size);
207 DBG_GUI_L << LOG_HEADER << " resize resulted in " << size.y << ".\n";
208
209 if(resized) {
210 DBG_GUI_L << LOG_HEADER << " resize modified the width, throw notification.\n";
211
212 throw layout_exception_width_modified();
213 }
214 }
215
request_reduce_width(const unsigned maximum_width)216 void scrollbar_container::request_reduce_width(const unsigned maximum_width)
217 {
218 DBG_GUI_L << LOG_HEADER << " requested width " << maximum_width << ".\n";
219
220 if(static_cast<unsigned>(get_grid().get_best_size().x) > maximum_width) {
221 get_grid().request_reduce_width(maximum_width);
222 }
223
224 // First ask our content, it might be able to wrap which looks better as
225 // a scrollbar.
226 assert(content_grid_);
227 const unsigned offset =
228 vertical_scrollbar_grid_ && vertical_scrollbar_grid_->get_visible() != widget::visibility::invisible
229 ? vertical_scrollbar_grid_->get_best_size().x
230 : 0;
231
232 content_grid_->request_reduce_width(maximum_width - offset);
233
234 // Did we manage to achieve the wanted size?
235 point size = get_best_size();
236 if(static_cast<unsigned>(size.x) <= maximum_width) {
237 DBG_GUI_L << LOG_HEADER << " child honored request, width " << size.x << ".\n";
238 return;
239 }
240
241 if(horizontal_scrollbar_mode_ == ALWAYS_INVISIBLE) {
242 DBG_GUI_L << LOG_HEADER << " request failed due to scrollbar mode.\n";
243 return;
244 }
245
246 // Always set the bar visible, is a nop when it's already visible.
247 assert(horizontal_scrollbar_grid_);
248 horizontal_scrollbar_grid_->set_visible(widget::visibility::visible);
249 size = get_best_size();
250
251 point scrollbar_size = horizontal_scrollbar_grid_->get_best_size();
252
253 /*
254 * If the vertical bar is not invisible it's size needs to be added to the
255 * minimum size.
256 */
257 if(vertical_scrollbar_grid_->get_visible() != widget::visibility::invisible) {
258 scrollbar_size.x += vertical_scrollbar_grid_->get_best_size().x;
259 }
260
261 // If showing the scrollbar increased the width, hide and abort.
262 if(horizontal_scrollbar_mode_ == AUTO_VISIBLE_FIRST_RUN && scrollbar_size.x > size.x) {
263 horizontal_scrollbar_grid_->set_visible(widget::visibility::invisible);
264 DBG_GUI_L << LOG_HEADER << " request failed, showing the scrollbar"
265 << " increased the width to " << scrollbar_size.x << ".\n";
266 return;
267 }
268
269 if(maximum_width > static_cast<unsigned>(scrollbar_size.x)) {
270 size.x = maximum_width;
271 } else {
272 size.x = scrollbar_size.x;
273 }
274
275 size.x = std::max(size.x, get_grid().get_best_size().x);
276
277 // FIXME adjust for the step size of the scrollbar
278
279 set_layout_size(size);
280 DBG_GUI_L << LOG_HEADER << " resize resulted in " << size.x << ".\n";
281 }
282
can_wrap() const283 bool scrollbar_container::can_wrap() const
284 {
285 return content_grid_ ? content_grid_->can_wrap() : false;
286 }
287
calculate_best_size() const288 point scrollbar_container::calculate_best_size() const
289 {
290 log_scope2(log_gui_layout, LOG_SCOPE_HEADER);
291
292 /***** get vertical scrollbar size *****/
293 const point vertical_scrollbar = vertical_scrollbar_grid_->get_visible() == widget::visibility::invisible
294 ? point()
295 : vertical_scrollbar_grid_->get_best_size();
296
297 /***** get horizontal scrollbar size *****/
298 const point horizontal_scrollbar = horizontal_scrollbar_grid_->get_visible() == widget::visibility::invisible
299 ? point()
300 : horizontal_scrollbar_grid_->get_best_size();
301
302 /***** get content size *****/
303 assert(content_grid_);
304 const point content = content_grid_->get_best_size();
305
306 point result(
307 vertical_scrollbar.x + std::max(horizontal_scrollbar.x, content.x),
308 horizontal_scrollbar.y + std::max(vertical_scrollbar.y, content.y));
309
310 DBG_GUI_L << LOG_HEADER << " vertical_scrollbar " << vertical_scrollbar << " horizontal_scrollbar "
311 << horizontal_scrollbar << " content " << content << " result " << result << ".\n";
312
313 return result;
314 }
315
set_scrollbar_mode(grid * scrollbar_grid,scrollbar_base * scrollbar,scrollbar_container::scrollbar_mode & scrollbar_mode,const unsigned items,const unsigned visible_items,grid * content_grid)316 static void set_scrollbar_mode(grid* scrollbar_grid,
317 scrollbar_base* scrollbar,
318 scrollbar_container::scrollbar_mode& scrollbar_mode,
319 const unsigned items,
320 const unsigned visible_items,
321 grid* content_grid)
322 {
323 assert(scrollbar_grid && scrollbar);
324
325 if(scrollbar_mode == scrollbar_container::ALWAYS_INVISIBLE) {
326 scrollbar_grid->set_visible(widget::visibility::invisible);
327 return;
328 }
329
330 scrollbar->set_item_count(items);
331 scrollbar->set_item_position(0);
332 scrollbar->set_visible_items(visible_items);
333
334 if(scrollbar_mode == scrollbar_container::AUTO_VISIBLE) {
335 const bool scrollbar_needed = items > visible_items;
336 scrollbar_grid->set_visible(scrollbar_needed ? widget::visibility::visible : widget::visibility::hidden);
337 } else if(scrollbar_mode == scrollbar_container::AUTO_VISIBLE_FIRST_RUN) {
338 if(items <= visible_items && content_grid != nullptr
339 && scrollbar_grid->get_visible() == widget::visibility::visible
340 ) {
341 scrollbar_grid->set_visible(widget::visibility::invisible);
342 // Give newly freed space to the items.
343 content_grid->layout_initialize(false);
344 }
345 }
346 }
is_inserted_before(unsigned insertion_pos,unsigned old_item_count,unsigned old_position,unsigned visible_items)347 static bool is_inserted_before(
348 unsigned insertion_pos, unsigned old_item_count, unsigned old_position, unsigned visible_items)
349 {
350 if(old_position == 0) {
351 return false;
352 } else if(old_position + visible_items >= old_item_count) {
353 return true;
354 } else if(insertion_pos <= old_position) {
355 return true;
356 }
357
358 return false;
359 }
360
adjust_scrollbar_mode(grid * scrollbar_grid,scrollbar_base * scrollbar,scrollbar_container::scrollbar_mode & scrollbar_mode,const unsigned items_before,const unsigned items_after,const int insertion_pos,const unsigned visible_items)361 static void adjust_scrollbar_mode(grid* scrollbar_grid,
362 scrollbar_base* scrollbar,
363 scrollbar_container::scrollbar_mode& scrollbar_mode,
364 const unsigned items_before,
365 const unsigned items_after,
366 const int insertion_pos,
367 const unsigned visible_items)
368 {
369 assert(scrollbar_grid && scrollbar);
370 if(items_before != scrollbar->get_item_count()) {
371 return set_scrollbar_mode(scrollbar_grid, scrollbar, scrollbar_mode, items_after, visible_items, nullptr);
372 }
373
374 // TODO: does this also work well in case the items were removed?
375 const unsigned previous_item_position = scrollbar->get_item_position();
376
377 // Casts insertion_pos to an unsigned so negative values are interpreted as 'at end'
378 const bool inserted_before_visible_area = is_inserted_before(
379 static_cast<unsigned>(insertion_pos), items_before, previous_item_position, visible_items);
380
381 if(scrollbar_mode == scrollbar_container::ALWAYS_INVISIBLE) {
382 scrollbar_grid->set_visible(widget::visibility::invisible);
383 return;
384 }
385
386 scrollbar->set_item_count(items_after);
387 scrollbar->set_item_position(inserted_before_visible_area
388 ? previous_item_position + items_after - items_before
389 : previous_item_position);
390
391 // scrollbar->set_item_position(0);
392 scrollbar->set_visible_items(visible_items);
393
394 if(scrollbar_mode == scrollbar_container::AUTO_VISIBLE) {
395 const bool scrollbar_needed = items_after > visible_items;
396 scrollbar_grid->set_visible(scrollbar_needed ? widget::visibility::visible : widget::visibility::hidden);
397 }
398 }
399
place(const point & origin,const point & size)400 void scrollbar_container::place(const point& origin, const point& size)
401 {
402 // Inherited.
403 container_base::place(origin, size);
404
405 // Set content size
406 assert(content_ && content_grid_);
407
408 const point content_origin = content_->get_origin();
409
410 const point best_size = content_grid_->get_best_size();
411 const point content_size(content_->get_width(), content_->get_height());
412
413 const point content_grid_size(std::max(best_size.x, content_size.x), std::max(best_size.y, content_size.y));
414
415 set_content_size(content_origin, content_grid_size);
416
417 // Set vertical scrollbar
418 set_scrollbar_mode(
419 vertical_scrollbar_grid_,
420 vertical_scrollbar_,
421 vertical_scrollbar_mode_,
422 content_grid_->get_height(),
423 content_->get_height(),
424 content_grid_.get()
425 );
426
427 // Set horizontal scrollbar
428 set_scrollbar_mode(
429 horizontal_scrollbar_grid_,
430 horizontal_scrollbar_,
431 horizontal_scrollbar_mode_,
432 content_grid_->get_width(),
433 content_->get_width(),
434 content_grid_.get()
435 );
436
437 // Update the buttons.
438 set_scrollbar_button_status();
439
440 // Now set the visible part of the content.
441 content_visible_area_ = content_->get_rectangle();
442 content_grid_->set_visible_rectangle(content_visible_area_);
443 }
444
set_origin(const point & origin)445 void scrollbar_container::set_origin(const point& origin)
446 {
447 // Inherited.
448 container_base::set_origin(origin);
449
450 // Set content size
451 assert(content_ && content_grid_);
452
453 const point content_origin = content_->get_origin();
454
455 content_grid_->set_origin(content_origin);
456
457 // Changing the origin also invalidates the visible area.
458 content_grid_->set_visible_rectangle(content_visible_area_);
459 }
460
set_visible_rectangle(const SDL_Rect & rectangle)461 void scrollbar_container::set_visible_rectangle(const SDL_Rect& rectangle)
462 {
463 // Inherited.
464 container_base::set_visible_rectangle(rectangle);
465
466 // Now get the visible part of the content.
467 content_visible_area_ = sdl::intersect_rects(rectangle, content_->get_rectangle());
468
469 content_grid_->set_visible_rectangle(content_visible_area_);
470 }
471
get_active() const472 bool scrollbar_container::get_active() const
473 {
474 return state_ != DISABLED;
475 }
476
get_state() const477 unsigned scrollbar_container::get_state() const
478 {
479 return state_;
480 }
481
find_at(const point & coordinate,const bool must_be_active)482 widget* scrollbar_container::find_at(const point& coordinate, const bool must_be_active)
483 {
484 widget* w = scrollbar_container_implementation::find_at<widget>(*this, coordinate, must_be_active);
485 if(w == nullptr) {
486 w = widget::find_at(coordinate, must_be_active);
487 }
488
489 return w;
490 }
491
find_at(const point & coordinate,const bool must_be_active) const492 const widget* scrollbar_container::find_at(const point& coordinate, const bool must_be_active) const
493 {
494 const widget* w = scrollbar_container_implementation::find_at<const widget>(*this, coordinate, must_be_active);
495 if(w == nullptr) {
496 w = widget::find_at(coordinate, must_be_active);
497 }
498
499 return w;
500 }
501
find(const std::string & id,const bool must_be_active)502 widget* scrollbar_container::find(const std::string& id, const bool must_be_active)
503 {
504 return scrollbar_container_implementation::find<widget>(*this, id, must_be_active);
505 }
506
find(const std::string & id,const bool must_be_active) const507 const widget* scrollbar_container::find(const std::string& id, const bool must_be_active) const
508 {
509 return scrollbar_container_implementation::find<const widget>(*this, id, must_be_active);
510 }
511
disable_click_dismiss() const512 bool scrollbar_container::disable_click_dismiss() const
513 {
514 assert(content_grid_);
515 return container_base::disable_click_dismiss() || content_grid_->disable_click_dismiss();
516 }
517
content_resize_request(const bool force_sizing)518 bool scrollbar_container::content_resize_request(const bool force_sizing)
519 {
520 /**
521 * @todo Try to handle AUTO_VISIBLE_FIRST_RUN here as well.
522 *
523 * Handling it here makes the code a bit more complex but allows to not
524 * reserve space for scrollbars, which will look nicer in the MP lobby.
525 * But the extra complexity is no 1.8 material.
526 */
527
528 assert(content_ && content_grid_);
529
530 point best_size = content_grid_->recalculate_best_size();
531 point size = content_->get_size();
532
533 DBG_GUI_L << LOG_HEADER << " wanted size " << best_size << " available size " << size << ".\n";
534
535 if(size == point()) {
536 DBG_GUI_L << LOG_HEADER << " initial setup not done, bailing out.\n";
537 return false;
538 }
539
540 if(best_size.x <= size.x && best_size.y <= size.y) {
541 const point content_size = content_grid_->get_size();
542
543 if(content_size.x > size.x || content_size.y > size.y) {
544 DBG_GUI_L << LOG_HEADER << " will fit, only needs a resize.\n";
545 goto resize;
546 }
547
548 if(force_sizing) {
549 DBG_GUI_L << LOG_HEADER << " fits, but resize forced.\n";
550 goto resize;
551 }
552
553 DBG_GUI_L << LOG_HEADER << " fits, nothing to do.\n";
554 return true;
555 }
556
557 if(best_size.x > size.x) {
558 DBG_GUI_L << LOG_HEADER << " content too wide.\n";
559
560 if(horizontal_scrollbar_mode_ == ALWAYS_INVISIBLE ||
561 (
562 horizontal_scrollbar_mode_ == AUTO_VISIBLE_FIRST_RUN &&
563 horizontal_scrollbar_grid_->get_visible() == widget::visibility::invisible
564 )
565 ) {
566 DBG_GUI_L << LOG_HEADER << " can't use horizontal scrollbar, request placement.\n";
567
568 event::message message;
569 fire(event::REQUEST_PLACEMENT, *this, message);
570 return false;
571 }
572 }
573
574 if(best_size.y > size.y) {
575 DBG_GUI_L << LOG_HEADER << " content too high.\n";
576
577 if(vertical_scrollbar_mode_ == ALWAYS_INVISIBLE ||
578 (
579 vertical_scrollbar_mode_ == AUTO_VISIBLE_FIRST_RUN &&
580 vertical_scrollbar_grid_->get_visible() == widget::visibility::invisible
581 )
582 ) {
583 DBG_GUI_L << LOG_HEADER << " can't use vertical scrollbar, request placement.\n";
584
585 event::message message;
586 fire(event::REQUEST_PLACEMENT, *this, message);
587 return false;
588 }
589 }
590
591 resize:
592 DBG_GUI_L << LOG_HEADER << " handle resizing.\n";
593
594 place(get_origin(), get_size());
595 return true;
596 }
597
content_resize_request(const int width_modification,const int height_modification,const int width_modification_pos,const int height_modification_pos)598 bool scrollbar_container::content_resize_request(const int width_modification,
599 const int height_modification,
600 const int width_modification_pos,
601 const int height_modification_pos)
602 {
603 DBG_GUI_L << LOG_HEADER << " wanted width modification " << width_modification << " wanted height modification "
604 << height_modification << ".\n";
605
606 if(get_size() == point()) {
607 DBG_GUI_L << LOG_HEADER << " initial setup not done, bailing out.\n";
608 return false;
609 }
610
611 window* window = get_window();
612 assert(window);
613
614 if(window->get_need_layout()) {
615 DBG_GUI_L << LOG_HEADER << " window already needs a layout phase, bailing out.\n";
616 return false;
617 }
618
619 assert(content_ && content_grid_);
620
621 const bool result =
622 content_resize_width(width_modification, width_modification_pos) &&
623 content_resize_height(height_modification, height_modification_pos);
624
625 scrollbar_moved();
626
627 /*
628 * The subroutines set the new size of the scrollbar but don't
629 * update the button status.
630 */
631 if(result) {
632 set_scrollbar_button_status();
633 }
634
635 DBG_GUI_L << LOG_HEADER << " result " << result << ".\n";
636 return result;
637 }
638
content_resize_width(const int width_modification,const int width_modification_pos)639 bool scrollbar_container::content_resize_width(const int width_modification, const int width_modification_pos)
640 {
641 if(width_modification == 0) {
642 return true;
643 }
644
645 const int new_width = content_grid_->get_width() + width_modification;
646 DBG_GUI_L << LOG_HEADER << " current width " << content_grid_->get_width() << " wanted width " << new_width;
647
648 if(new_width < 0) {
649 return false;
650 }
651
652 if(static_cast<unsigned>(new_width) <= content_->get_width()) {
653 DBG_GUI_L << " width fits in container, test height.\n";
654
655 adjust_scrollbar_mode(horizontal_scrollbar_grid_, horizontal_scrollbar_, horizontal_scrollbar_mode_,
656 content_grid_->get_width(), content_grid_->get_width() + width_modification, width_modification_pos,
657 content_->get_width());
658 return true;
659 }
660
661 assert(horizontal_scrollbar_ && horizontal_scrollbar_grid_);
662 if(horizontal_scrollbar_mode_ == ALWAYS_INVISIBLE ||
663 (
664 horizontal_scrollbar_mode_ == AUTO_VISIBLE_FIRST_RUN &&
665 horizontal_scrollbar_grid_->get_visible() == widget::visibility::invisible
666 )
667 ) {
668 DBG_GUI_L << " can't use horizontal scrollbar, ask window.\n";
669
670 window* window = get_window();
671 assert(window);
672
673 window->invalidate_layout();
674 return false;
675 }
676
677 DBG_GUI_L << " use the horizontal scrollbar, test height.\n";
678 adjust_scrollbar_mode(horizontal_scrollbar_grid_, horizontal_scrollbar_, horizontal_scrollbar_mode_,
679 content_grid_->get_width(), content_grid_->get_width() + width_modification, width_modification_pos,
680 content_->get_width());
681
682 return true;
683 }
684
content_resize_height(const int height_modification,const int height_modification_pos)685 bool scrollbar_container::content_resize_height(const int height_modification, const int height_modification_pos)
686 {
687 if(height_modification == 0) {
688 return true;
689 }
690
691 const int new_height = content_grid_->get_height() + height_modification;
692
693 DBG_GUI_L << LOG_HEADER << " current height " << content_grid_->get_height() << " wanted height " << new_height;
694
695 if(new_height < 0) {
696 return false;
697 }
698
699 if(static_cast<unsigned>(new_height) <= content_->get_height()) {
700 DBG_GUI_L << " height in container, resize allowed.\n";
701
702 adjust_scrollbar_mode(vertical_scrollbar_grid_, vertical_scrollbar_, vertical_scrollbar_mode_,
703 content_grid_->get_height(), new_height, height_modification_pos, content_->get_height());
704 return true;
705 }
706
707 assert(vertical_scrollbar_ && vertical_scrollbar_grid_);
708 if(vertical_scrollbar_mode_ == ALWAYS_INVISIBLE ||
709 (
710 vertical_scrollbar_mode_ == AUTO_VISIBLE_FIRST_RUN &&
711 vertical_scrollbar_grid_->get_visible() == widget::visibility::invisible
712 )
713 ) {
714 DBG_GUI_L << " can't use vertical scrollbar, ask window.\n";
715
716 window* window = get_window();
717 assert(window);
718
719 window->invalidate_layout();
720 return false;
721 }
722
723 DBG_GUI_L << " use the vertical scrollbar, resize allowed.\n";
724
725 adjust_scrollbar_mode(vertical_scrollbar_grid_, vertical_scrollbar_, vertical_scrollbar_mode_,
726 content_grid_->get_height(), new_height, height_modification_pos, content_->get_height());
727
728 return true;
729 }
730
finalize_setup()731 void scrollbar_container::finalize_setup()
732 {
733 /***** Setup vertical scrollbar *****/
734 vertical_scrollbar_grid_ = find_widget<grid>(this, "_vertical_scrollbar_grid", false, true);
735
736 vertical_scrollbar_ =
737 find_widget<scrollbar_base>(vertical_scrollbar_grid_, "_vertical_scrollbar", false, true);
738
739 connect_signal_notify_modified(*vertical_scrollbar_,
740 std::bind(&scrollbar_container::vertical_scrollbar_moved, this));
741
742 /***** Setup horizontal scrollbar *****/
743 horizontal_scrollbar_grid_ = find_widget<grid>(this, "_horizontal_scrollbar_grid", false, true);
744
745 horizontal_scrollbar_ =
746 find_widget<scrollbar_base>(horizontal_scrollbar_grid_, "_horizontal_scrollbar", false, true);
747
748 connect_signal_notify_modified(*horizontal_scrollbar_,
749 std::bind(&scrollbar_container::horizontal_scrollbar_moved, this));
750
751 /***** Setup the scrollbar buttons *****/
752 for(const auto& item : scroll_lookup()) {
753 // Vertical.
754 clickable_item* button = find_widget<clickable_item>(vertical_scrollbar_grid_, item.first, false, false);
755
756 if(button) {
757 button->connect_click_handler(
758 std::bind(&scrollbar_container::scroll_vertical_scrollbar, this, item.second));
759 }
760
761 // Horizontal.
762 button = find_widget<clickable_item>(horizontal_scrollbar_grid_, item.first, false, false);
763
764 if(button) {
765 button->connect_click_handler(
766 std::bind(&scrollbar_container::scroll_horizontal_scrollbar, this, item.second));
767 }
768 }
769
770 /***** Setup the content *****/
771 content_ = build_single_widget_instance<spacer>("spacer");
772
773 // TODO: possibly move this unique_ptr casting functionality to a helper function.
774 content_grid_.reset(dynamic_cast<grid*>(get_grid().swap_child("_content_grid", content_, true).release()));
775 assert(content_grid_);
776
777 content_grid_->set_parent(this);
778
779 /***** Let our subclasses initialize themselves. *****/
780 finalize_subclass();
781 }
782
set_vertical_scrollbar_mode(const scrollbar_mode scrollbar_mode)783 void scrollbar_container::set_vertical_scrollbar_mode(const scrollbar_mode scrollbar_mode)
784 {
785 if(vertical_scrollbar_mode_ != scrollbar_mode) {
786 vertical_scrollbar_mode_ = scrollbar_mode;
787 }
788 }
789
set_horizontal_scrollbar_mode(const scrollbar_mode scrollbar_mode)790 void scrollbar_container::set_horizontal_scrollbar_mode(const scrollbar_mode scrollbar_mode)
791 {
792 if(horizontal_scrollbar_mode_ != scrollbar_mode) {
793 horizontal_scrollbar_mode_ = scrollbar_mode;
794 }
795 }
796
impl_draw_children(surface & frame_buffer,int x_offset,int y_offset)797 void scrollbar_container::impl_draw_children(surface& frame_buffer, int x_offset, int y_offset)
798 {
799 assert(get_visible() == widget::visibility::visible && content_grid_->get_visible() == widget::visibility::visible);
800
801 // Inherited.
802 container_base::impl_draw_children(frame_buffer, x_offset, y_offset);
803
804 content_grid_->draw_children(frame_buffer, x_offset, y_offset);
805 }
806
layout_children()807 void scrollbar_container::layout_children()
808 {
809 // Inherited.
810 container_base::layout_children();
811
812 assert(content_grid_);
813 content_grid_->layout_children();
814 }
815
child_populate_dirty_list(window & caller,const std::vector<widget * > & call_stack)816 void scrollbar_container::child_populate_dirty_list(window& caller, const std::vector<widget*>& call_stack)
817 {
818 // Inherited.
819 container_base::child_populate_dirty_list(caller, call_stack);
820
821 assert(content_grid_);
822 std::vector<widget*> child_call_stack(call_stack);
823 content_grid_->populate_dirty_list(caller, child_call_stack);
824 }
825
set_content_size(const point & origin,const point & size)826 void scrollbar_container::set_content_size(const point& origin, const point& size)
827 {
828 content_grid_->place(origin, size);
829 }
830
show_content_rect(const SDL_Rect & rect)831 void scrollbar_container::show_content_rect(const SDL_Rect& rect)
832 {
833 assert(content_);
834 assert(horizontal_scrollbar_ && vertical_scrollbar_);
835
836 // Set the bottom right location first if it doesn't fit the top left
837 // will look good. First calculate the left and top position depending on
838 // the current position.
839
840 const int left_position = horizontal_scrollbar_->get_item_position() + (rect.x - content_->get_x());
841 const int top_position = vertical_scrollbar_->get_item_position() + (rect.y - content_->get_y());
842
843 // bottom.
844 const int wanted_bottom = rect.y + rect.h;
845 const int current_bottom = content_->get_y() + content_->get_height();
846
847 int distance = wanted_bottom - current_bottom;
848 if(distance > 0) {
849 vertical_scrollbar_->set_item_position(vertical_scrollbar_->get_item_position() + distance);
850 }
851
852 // right.
853 const int wanted_right = rect.x + rect.w;
854 const int current_right = content_->get_x() + content_->get_width();
855
856 distance = wanted_right - current_right;
857 if(distance > 0) {
858 horizontal_scrollbar_->set_item_position(horizontal_scrollbar_->get_item_position() + distance);
859 }
860
861 // top.
862 if(top_position < static_cast<int>(vertical_scrollbar_->get_item_position())) {
863 vertical_scrollbar_->set_item_position(top_position);
864 }
865
866 // left.
867 if(left_position < static_cast<int>(horizontal_scrollbar_->get_item_position())) {
868 horizontal_scrollbar_->set_item_position(left_position);
869 }
870
871 // Update.
872 scrollbar_moved();
873 }
874
set_scrollbar_button_status()875 void scrollbar_container::set_scrollbar_button_status()
876 {
877 if(true) { /** @todo scrollbar visibility. */
878 /***** set scroll up button status *****/
879 for(const auto& name : button_up_names) {
880 styled_widget* button = find_widget<styled_widget>(vertical_scrollbar_grid_, name, false, false);
881
882 if(button) {
883 button->set_active(!vertical_scrollbar_->at_begin());
884 }
885 }
886
887 /***** set scroll down status *****/
888 for(const auto& name : button_down_names) {
889 styled_widget* button = find_widget<styled_widget>(vertical_scrollbar_grid_, name, false, false);
890
891 if(button) {
892 button->set_active(!vertical_scrollbar_->at_end());
893 }
894 }
895
896 /***** Set the status if the scrollbars *****/
897 vertical_scrollbar_->set_active(!vertical_scrollbar_->all_items_visible());
898 }
899
900 if(true) { /** @todo scrollbar visibility. */
901 /***** Set scroll left button status *****/
902 for(const auto& name : button_up_names) {
903 styled_widget* button = find_widget<styled_widget>(horizontal_scrollbar_grid_, name, false, false);
904
905 if(button) {
906 button->set_active(!horizontal_scrollbar_->at_begin());
907 }
908 }
909
910 /***** Set scroll right button status *****/
911 for(const auto& name : button_down_names) {
912 styled_widget* button = find_widget<styled_widget>(horizontal_scrollbar_grid_, name, false, false);
913
914 if(button) {
915 button->set_active(!horizontal_scrollbar_->at_end());
916 }
917 }
918
919 /***** Set the status if the scrollbars *****/
920 horizontal_scrollbar_->set_active(!horizontal_scrollbar_->all_items_visible());
921 }
922 }
923
vertical_scrollbar_at_end()924 bool scrollbar_container::vertical_scrollbar_at_end()
925 {
926 assert(vertical_scrollbar_);
927
928 return vertical_scrollbar_->at_end();
929 }
930
get_vertical_scrollbar_item_position() const931 unsigned scrollbar_container::get_vertical_scrollbar_item_position() const
932 {
933 assert(vertical_scrollbar_);
934
935 return vertical_scrollbar_->get_item_position();
936 }
937
set_vertical_scrollbar_item_position(const unsigned position)938 void scrollbar_container::set_vertical_scrollbar_item_position(const unsigned position)
939 {
940 assert(vertical_scrollbar_);
941
942 vertical_scrollbar_->set_item_position(position);
943 scrollbar_moved();
944 }
945
get_horizontal_scrollbar_item_position() const946 unsigned scrollbar_container::get_horizontal_scrollbar_item_position() const
947 {
948 assert(horizontal_scrollbar_);
949
950 return horizontal_scrollbar_->get_item_position();
951 }
952
set_horizontal_scrollbar_item_position(const unsigned position)953 void scrollbar_container::set_horizontal_scrollbar_item_position(const unsigned position)
954 {
955 assert(horizontal_scrollbar_);
956
957 horizontal_scrollbar_->set_item_position(position);
958 scrollbar_moved();
959 }
960
scroll_vertical_scrollbar(const scrollbar_base::scroll_mode scroll)961 void scrollbar_container::scroll_vertical_scrollbar(const scrollbar_base::scroll_mode scroll)
962 {
963 assert(vertical_scrollbar_);
964
965 vertical_scrollbar_->scroll(scroll);
966 scrollbar_moved();
967 }
968
scroll_horizontal_scrollbar(const scrollbar_base::scroll_mode scroll)969 void scrollbar_container::scroll_horizontal_scrollbar(const scrollbar_base::scroll_mode scroll)
970 {
971 assert(horizontal_scrollbar_);
972
973 horizontal_scrollbar_->scroll(scroll);
974 scrollbar_moved();
975 }
976
handle_key_home(SDL_Keymod,bool & handled)977 void scrollbar_container::handle_key_home(SDL_Keymod /*modifier*/, bool& handled)
978 {
979 assert(vertical_scrollbar_ && horizontal_scrollbar_);
980
981 vertical_scrollbar_->scroll(scrollbar_base::BEGIN);
982 horizontal_scrollbar_->scroll(scrollbar_base::BEGIN);
983 scrollbar_moved();
984
985 handled = true;
986 }
987
handle_key_end(SDL_Keymod,bool & handled)988 void scrollbar_container::handle_key_end(SDL_Keymod /*modifier*/, bool& handled)
989 {
990 assert(vertical_scrollbar_);
991
992 vertical_scrollbar_->scroll(scrollbar_base::END);
993 scrollbar_moved();
994
995 handled = true;
996 }
997
handle_key_page_up(SDL_Keymod,bool & handled)998 void scrollbar_container::handle_key_page_up(SDL_Keymod /*modifier*/, bool& handled)
999 {
1000 assert(vertical_scrollbar_);
1001
1002 vertical_scrollbar_->scroll(scrollbar_base::JUMP_BACKWARDS);
1003 scrollbar_moved();
1004
1005 handled = true;
1006 }
1007
handle_key_page_down(SDL_Keymod,bool & handled)1008 void scrollbar_container::handle_key_page_down(SDL_Keymod /*modifier*/, bool& handled)
1009
1010 {
1011 assert(vertical_scrollbar_);
1012
1013 vertical_scrollbar_->scroll(scrollbar_base::JUMP_FORWARD);
1014 scrollbar_moved();
1015
1016 handled = true;
1017 }
1018
handle_key_up_arrow(SDL_Keymod,bool & handled)1019 void scrollbar_container::handle_key_up_arrow(SDL_Keymod /*modifier*/, bool& handled)
1020 {
1021 assert(vertical_scrollbar_);
1022
1023 vertical_scrollbar_->scroll(scrollbar_base::ITEM_BACKWARDS);
1024 scrollbar_moved();
1025
1026 handled = true;
1027 }
1028
handle_key_down_arrow(SDL_Keymod,bool & handled)1029 void scrollbar_container::handle_key_down_arrow(SDL_Keymod /*modifier*/, bool& handled)
1030 {
1031 assert(vertical_scrollbar_);
1032
1033 vertical_scrollbar_->scroll(scrollbar_base::ITEM_FORWARD);
1034 scrollbar_moved();
1035
1036 handled = true;
1037 }
1038
handle_key_left_arrow(SDL_Keymod,bool & handled)1039 void scrollbar_container::handle_key_left_arrow(SDL_Keymod /*modifier*/, bool& handled)
1040 {
1041 assert(horizontal_scrollbar_);
1042
1043 horizontal_scrollbar_->scroll(scrollbar_base::ITEM_BACKWARDS);
1044 scrollbar_moved();
1045
1046 handled = true;
1047 }
1048
handle_key_right_arrow(SDL_Keymod,bool & handled)1049 void scrollbar_container::handle_key_right_arrow(SDL_Keymod /*modifier*/, bool& handled)
1050 {
1051 assert(horizontal_scrollbar_);
1052
1053 horizontal_scrollbar_->scroll(scrollbar_base::ITEM_FORWARD);
1054 scrollbar_moved();
1055
1056 handled = true;
1057 }
1058
scrollbar_moved()1059 void scrollbar_container::scrollbar_moved()
1060 {
1061 // Init.
1062 assert(content_ && content_grid_);
1063 assert(vertical_scrollbar_ && horizontal_scrollbar_);
1064
1065 /*** Update the content location. ***/
1066 const int x_offset = horizontal_scrollbar_mode_ == ALWAYS_INVISIBLE
1067 ? 0
1068 : horizontal_scrollbar_->get_item_position() * horizontal_scrollbar_->get_step_size();
1069
1070 const int y_offset = vertical_scrollbar_mode_ == ALWAYS_INVISIBLE
1071 ? 0
1072 : vertical_scrollbar_->get_item_position() * vertical_scrollbar_->get_step_size();
1073
1074 const point content_origin {content_->get_x() - x_offset, content_->get_y() - y_offset};
1075
1076 content_grid_->set_origin(content_origin);
1077 content_grid_->set_visible_rectangle(content_visible_area_);
1078 content_grid_->set_is_dirty(true);
1079
1080 // Update scrollbar.
1081 set_scrollbar_button_status();
1082 }
1083
type()1084 const std::string& scrollbar_container::type()
1085 {
1086 static const std::string type = "scrollbar_container";
1087 return type;
1088 }
1089
get_control_type() const1090 const std::string& scrollbar_container::get_control_type() const
1091 {
1092 return type();
1093 }
1094
signal_handler_sdl_key_down(const event::ui_event event,bool & handled,const SDL_Keycode key,SDL_Keymod modifier)1095 void scrollbar_container::signal_handler_sdl_key_down(
1096 const event::ui_event event, bool& handled, const SDL_Keycode key, SDL_Keymod modifier)
1097 {
1098 DBG_GUI_E << LOG_HEADER << event << ".\n";
1099
1100 switch(key) {
1101 case SDLK_HOME:
1102 handle_key_home(modifier, handled);
1103 break;
1104
1105 case SDLK_END:
1106 handle_key_end(modifier, handled);
1107 break;
1108
1109 case SDLK_PAGEUP:
1110 handle_key_page_up(modifier, handled);
1111 break;
1112
1113 case SDLK_PAGEDOWN:
1114 handle_key_page_down(modifier, handled);
1115 break;
1116
1117 case SDLK_UP:
1118 handle_key_up_arrow(modifier, handled);
1119 break;
1120
1121 case SDLK_DOWN:
1122 handle_key_down_arrow(modifier, handled);
1123 break;
1124
1125 case SDLK_LEFT:
1126 handle_key_left_arrow(modifier, handled);
1127 break;
1128
1129 case SDLK_RIGHT:
1130 handle_key_right_arrow(modifier, handled);
1131 break;
1132 default:
1133 /* ignore */
1134 break;
1135 }
1136 }
1137
signal_handler_sdl_wheel_up(const event::ui_event event,bool & handled)1138 void scrollbar_container::signal_handler_sdl_wheel_up(const event::ui_event event, bool& handled)
1139 {
1140 DBG_GUI_E << LOG_HEADER << event << ".\n";
1141
1142 assert(vertical_scrollbar_grid_ && vertical_scrollbar_);
1143
1144 if(vertical_scrollbar_grid_->get_visible() == widget::visibility::visible) {
1145 vertical_scrollbar_->scroll(scrollbar_base::HALF_JUMP_BACKWARDS);
1146 scrollbar_moved();
1147 handled = true;
1148 }
1149 }
1150
signal_handler_sdl_wheel_down(const event::ui_event event,bool & handled)1151 void scrollbar_container::signal_handler_sdl_wheel_down(const event::ui_event event, bool& handled)
1152 {
1153 DBG_GUI_E << LOG_HEADER << event << ".\n";
1154
1155 assert(vertical_scrollbar_grid_ && vertical_scrollbar_);
1156
1157 if(vertical_scrollbar_grid_->get_visible() == widget::visibility::visible) {
1158 vertical_scrollbar_->scroll(scrollbar_base::HALF_JUMP_FORWARD);
1159 scrollbar_moved();
1160 handled = true;
1161 }
1162 }
1163
signal_handler_sdl_wheel_left(const event::ui_event event,bool & handled)1164 void scrollbar_container::signal_handler_sdl_wheel_left(const event::ui_event event, bool& handled)
1165 {
1166 DBG_GUI_E << LOG_HEADER << event << ".\n";
1167
1168 assert(horizontal_scrollbar_grid_ && horizontal_scrollbar_);
1169
1170 if(horizontal_scrollbar_grid_->get_visible() == widget::visibility::visible) {
1171 horizontal_scrollbar_->scroll(scrollbar_base::HALF_JUMP_BACKWARDS);
1172 scrollbar_moved();
1173 handled = true;
1174 }
1175 }
1176
signal_handler_sdl_wheel_right(const event::ui_event event,bool & handled)1177 void scrollbar_container::signal_handler_sdl_wheel_right(const event::ui_event event, bool& handled)
1178 {
1179 DBG_GUI_E << LOG_HEADER << event << ".\n";
1180
1181 assert(horizontal_scrollbar_grid_ && horizontal_scrollbar_);
1182
1183 if(horizontal_scrollbar_grid_->get_visible() == widget::visibility::visible) {
1184 horizontal_scrollbar_->scroll(scrollbar_base::HALF_JUMP_FORWARD);
1185 scrollbar_moved();
1186 handled = true;
1187 }
1188 }
1189
1190 void
signal_handler_sdl_touch_motion(const event::ui_event event,bool & handled,const point & position,const point & distance)1191 scrollbar_container::signal_handler_sdl_touch_motion(const event::ui_event event,
1192 bool& handled,
1193 const point& position,
1194 const point& distance)
1195 {
1196 (void) position;
1197 DBG_GUI_E << LOG_HEADER << event << ".\n";
1198
1199 bool is_scrollbar_moved = false;
1200
1201 if (horizontal_scrollbar_grid_ && horizontal_scrollbar_) {
1202
1203 if(horizontal_scrollbar_grid_->get_visible() == widget::visibility::visible) {
1204 horizontal_scrollbar_->scroll_by(-distance.x);
1205 is_scrollbar_moved = true;
1206 }
1207 }
1208
1209 if (vertical_scrollbar_grid_ && vertical_scrollbar_) {
1210
1211 if(vertical_scrollbar_grid_->get_visible() == widget::visibility::visible) {
1212 vertical_scrollbar_->scroll_by(-distance.y);
1213 is_scrollbar_moved = true;
1214 }
1215 }
1216
1217 if (is_scrollbar_moved) {
1218 scrollbar_moved();
1219 handled = true;
1220 }
1221 }
1222
1223
1224
1225 } // namespace gui2
1226