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