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 #pragma once
16 
17 #include "gui/core/notifiee.hpp"
18 #include "gui/widgets/container_base.hpp"
19 #include "gui/widgets/scrollbar.hpp"
20 
21 namespace gui2
22 {
23 class spacer;
24 
25 namespace implementation
26 {
27 struct builder_scroll_label;
28 struct builder_scrollbar_panel;
29 struct builder_styled_widget;
30 }
31 
32 /**
33  * Base class for creating containers with one or two scrollbar(s).
34  *
35  * For now users can't instantiate this class directly and needs to use small
36  * wrapper classes. Maybe in the future users can use the class directly.
37  *
38  * @todo events are not yet send to the content grid.
39  */
40 class scrollbar_container : public container_base
41 {
42 	friend class debug_layout_graph;
43 
44 	friend struct implementation::builder_scroll_label;
45 	friend struct implementation::builder_scrollbar_panel;
46 	friend class listbox;
47 	friend class tree_view;
48 	friend struct scrollbar_container_implementation;
49 
50 public:
51 	explicit scrollbar_container(const implementation::builder_styled_widget& builder, const std::string& control_type);
52 
~scrollbar_container()53 	virtual ~scrollbar_container()
54 	{
55 	}
56 
57 	/** The way to handle the showing or hiding of the scrollbar. */
58 	enum scrollbar_mode {
59 		/**
60 		 * The scrollbar is always shown, whether needed or not.
61 		 */
62 		ALWAYS_VISIBLE,
63 
64 		/**
65 		 * The scrollbar is never shown even notwhen needed. There's also no space
66 		 * reserved for the scrollbar.
67 		 */
68 		ALWAYS_INVISIBLE,
69 
70 		/**
71 		 * The scrollbar is shown when the number of items is larger as the visible items.
72 		 * The space for the scrollbar is always reserved, just in case it's needed after
73 		 * the initial sizing (due to adding items).
74 		 */
75 		AUTO_VISIBLE,
76 
77 		/**
78 		 * Like AUTO_VISIBLE, but when not needed upon the initial layout phase, the bars
79 		 * are not shown and no space is reserved for them. (The algorithm hides them by
80 		 * default.
81 		 */
82 		AUTO_VISIBLE_FIRST_RUN,
83 	};
84 
85 	/***** ***** ***** ***** layout functions ***** ***** ***** *****/
86 
87 	/** See @ref widget::layout_initialize. */
88 	virtual void layout_initialize(const bool full_initialization) override;
89 
90 	/** See @ref widget::request_reduce_height. */
91 	virtual void request_reduce_height(const unsigned maximum_height) override;
92 
93 	/** See @ref widget::request_reduce_width. */
94 	virtual void request_reduce_width(const unsigned maximum_width) override;
95 
96 	/**
97 	 * See @ref widget::can_wrap.
98 	 *
99 	 * @note This function is called before the object is finalized.
100 	 */
101 	virtual bool can_wrap() const override;
102 
103 private:
104 	/** See @ref widget::calculate_best_size. */
105 	virtual point calculate_best_size() const override;
106 
107 public:
108 	/** See @ref widget::place. */
109 	virtual void place(const point& origin, const point& size) override;
110 
111 	/** See @ref widget::set_origin. */
112 	virtual void set_origin(const point& origin) override;
113 
114 	/** See @ref widget::set_visible_rectangle. */
115 	virtual void set_visible_rectangle(const SDL_Rect& rectangle) override;
116 
117 	/***** ***** ***** inherited ****** *****/
118 
119 	/** See @ref styled_widget::get_active. */
120 	virtual bool get_active() const override;
121 
122 	/** See @ref styled_widget::get_state. */
123 	virtual unsigned get_state() const override;
124 
125 	/** See @ref widget::find_at. */
126 	virtual widget* find_at(const point& coordinate, const bool must_be_active) override;
127 
128 	/** See @ref widget::find_at. */
129 	virtual const widget* find_at(const point& coordinate, const bool must_be_active) const override;
130 
131 	/** See @ref widget::find. */
132 	widget* find(const std::string& id, const bool must_be_active) override;
133 
134 	/** See @ref widget::find. */
135 	const widget* find(const std::string& id, const bool must_be_active) const override;
136 
137 	/** See @ref widget::disable_click_dismiss. */
138 	bool disable_click_dismiss() const override;
139 
140 	/***** ***** ***** setters / getters for members ***** ****** *****/
141 
142 	/** @note shouldn't be called after being shown in a dialog. */
143 	void set_vertical_scrollbar_mode(const scrollbar_mode scrollbar_mode);
144 
get_vertical_scrollbar_mode() const145 	scrollbar_mode get_vertical_scrollbar_mode() const
146 	{
147 		return vertical_scrollbar_mode_;
148 	}
149 
150 	/** @note shouldn't be called after being shown in a dialog. */
151 	void set_horizontal_scrollbar_mode(const scrollbar_mode scrollbar_mode);
152 
get_horizontal_scrollbar_mode() const153 	scrollbar_mode get_horizontal_scrollbar_mode() const
154 	{
155 		return horizontal_scrollbar_mode_;
156 	}
157 
content_grid()158 	grid* content_grid()
159 	{
160 		return content_grid_.get();
161 	}
162 
content_grid() const163 	const grid* content_grid() const
164 	{
165 		return content_grid_.get();
166 	}
167 
content_visible_area() const168 	const SDL_Rect& content_visible_area() const
169 	{
170 		return content_visible_area_;
171 	}
172 
173 	/***** ***** ***** scrollbar helpers ***** ****** *****/
174 
175 	/* Returns at_end status of the vertical scrollbar.
176 	 *
177 	 */
178 	bool vertical_scrollbar_at_end();
179 
180 	/**
181 	 * Returns current position of the vertical scrollbar.
182 	 *
183 	 */
184 	unsigned get_vertical_scrollbar_item_position() const;
185 
186 	/**
187 	 * Move the vertical scrollbar to a position.
188 	 *
189 	 * @param position              The position to scroll to.
190 	 */
191 	void set_vertical_scrollbar_item_position(const unsigned position);
192 
193 	/**
194 	 * Returns current position of the horizontal scrollbar.
195 	 *
196 	 */
197 	unsigned get_horizontal_scrollbar_item_position() const;
198 
199 	/**
200 	 * Move the horizontal scrollbar to a position.
201 	 *
202 	 * @param position              The position to scroll to.
203 	 */
204 	void set_horizontal_scrollbar_item_position(const unsigned position);
205 
206 	/**
207 	 * Scrolls the vertical scrollbar.
208 	 *
209 	 * @param scroll              The position to scroll to.
210 	 */
211 	void scroll_vertical_scrollbar(const scrollbar_base::scroll_mode scroll);
212 
213 	/**
214 	 * Scrolls the horizontal scrollbar.
215 	 *
216 	 * @param scroll              The position to scroll to.
217 	 */
218 	void scroll_horizontal_scrollbar(const scrollbar_base::scroll_mode scroll);
219 
220 	/**
221 	 * Callback when the scrollbar moves (NOTE maybe only one callback needed).
222 	 * Maybe also make protected or private and add a friend.
223 	 */
vertical_scrollbar_moved()224 	void vertical_scrollbar_moved()
225 	{
226 		scrollbar_moved();
227 	}
228 
horizontal_scrollbar_moved()229 	void horizontal_scrollbar_moved()
230 	{
231 		scrollbar_moved();
232 	}
233 
234 protected:
235 	/**
236 	 * Shows a certain part of the content.
237 	 *
238 	 * When the part to be shown is bigger as the visible viewport the top
239 	 * left of the wanted rect will be the top left of the viewport.
240 	 *
241 	 * @param rect                The rect which should be visible.
242 	 */
243 	void show_content_rect(const SDL_Rect& rect);
244 
245 	/*
246 	 * The widget contains the following three grids.
247 	 *
248 	 * * _vertical_scrollbar_grid containing at least a widget named
249 	 *   _vertical_scrollbar
250 	 *
251 	 * * _horizontal_scrollbar_grid containing at least a widget named
252 	 *   _horizontal_scrollbar
253 	 *
254 	 * * _content_grid a grid which holds the contents of the area.
255 	 *
256 	 * NOTE maybe just hardcode these in the finalize phase...
257 	 *
258 	 */
259 
260 	/**
261 	 * Sets the status of the scrollbar buttons.
262 	 *
263 	 * This is needed after the scrollbar moves so the status of the buttons
264 	 * will be active or inactive as needed.
265 	 */
266 	void set_scrollbar_button_status();
267 
268 	/**
269 	 * Notification if the content of a child needs a resize.
270 	 *
271 	 * When a resize is required the container first can try to handle it
272 	 * itself. If it can't honor the request the function will call @ref
273 	 * window::invalidate_layout().
274 	 *
275 	 * @note Calling this function on a widget with size == (0, 0) results
276 	 * false but doesn't call invalidate_layout, the engine expects to be in
277 	 * build up phase with the layout already invalidated.
278 	 *
279 	 * @param force_sizing        If the contents fit do we want to force a
280 	 *                            resize? This is needed in the MP lobby since
281 	 *                            items might not be properly placed yet.
282 	 *                            (The listboxes with the player info need it.)
283 	 *
284 	 * @returns                   True if the resize is handled, false
285 	 *                            otherwise.
286 	 */
287 	bool content_resize_request(const bool force_sizing = false);
288 
289 	/**
290 	 * Request from the content to modify the size of the container.
291 	 *
292 	 * When the wanted resize fails the function will call @ref
293 	 * window::invalidate_layout().
294 	 *
295 	 *
296 	 * @note Calling this function on a widget with size == (0, 0) results
297 	 * false but doesn't call invalidate_layout, the engine expects to be in
298 	 * build up phase with the layout already invalidated.
299 	 *
300 	 * @note If @ref window::get_need_layout() is true the function returns
301 	 * false and doesn't try to fit the contents since a layout phase will be
302 	 * triggered anyway.
303 	 *
304 	 * @note This function might replace the @ref content_resize_request above.
305 	 *
306 	 * @param width_modification  The wanted modification to the width:
307 	 *                            * negative values reduce width.
308 	 *                            * zero leave width as is.
309 	 *                            * positive values increase width.
310 	 * @param height_modification The wanted modification to the height:
311 	 *                            * negative values reduce height.
312 	 *                            * zero leave height as is.
313 	 *                            * positive values increase height.
314 	 * @param width_modification_pos
315 	 *                            The position where the additional content was
316 	 *                            inserted/removed, defaults to -1 which means
317 	 *                            'at end'
318 	 * @param height_modification_pos
319 	 *                            The position where the additional content was
320 	 *                            inserted/removed, defaults to -1 which means
321 	 *                            'at end'
322 	 *
323 	 * @returns                   True is wanted modification is accepted false
324 	 *                            otherwise.
325 	 */
326 	bool content_resize_request(const int width_modification,
327 			const int height_modification,
328 			const int width_modification_pos = -1,
329 			const int height_modification_pos = -1);
330 
331 private:
332 	/**
333 	 * Helper for @ref content_resize_request.
334 	 *
335 	 * Handle the width modification.
336 	 */
337 	bool content_resize_width(const int width_modification, const int width_modification_pos);
338 
339 	/**
340 	 * Helper for @ref content_resize_request.
341 	 *
342 	 * Handle the height modification.
343 	 */
344 	bool content_resize_height(const int height_modification, const int width_modification_pos);
345 
346 protected:
347 	/***** ***** ***** ***** keyboard functions ***** ***** ***** *****/
348 
349 	/**
350 	 * Home key pressed.
351 	 *
352 	 * @param modifier            The SDL keyboard modifier when the key was
353 	 *                            pressed.
354 	 * @param handled             If the function handles the key it should
355 	 *                            set handled to true else do not modify it.
356 	 *                            This is used in the keyboard event
357 	 *                            changing.
358 	 */
359 	virtual void handle_key_home(SDL_Keymod modifier, bool& handled);
360 
361 	/**
362 	 * End key pressed.
363 	 *
364 	 * @param modifier            The SDL keyboard modifier when the key was
365 	 *                            pressed.
366 	 * @param handled             If the function handles the key it should
367 	 *                            set handled to true else do not modify it.
368 	 *                            This is used in the keyboard event
369 	 *                            changing.
370 	 */
371 	virtual void handle_key_end(SDL_Keymod modifier, bool& handled);
372 
373 	/**
374 	 * Page up key pressed.
375 	 *
376 	 * @param modifier            The SDL keyboard modifier when the key was
377 	 *                            pressed.
378 	 * @param handled             If the function handles the key it should
379 	 *                            set handled to true else do not modify it.
380 	 *                            This is used in the keyboard event
381 	 *                            changing.
382 	 */
383 	virtual void handle_key_page_up(SDL_Keymod modifier, bool& handled);
384 
385 	/**
386 	 * Page down key pressed.
387 	 *
388 	 * @param modifier            The SDL keyboard modifier when the key was
389 	 *                            pressed.
390 	 * @param handled             If the function handles the key it should
391 	 *                            set handled to true else do not modify it.
392 	 *                            This is used in the keyboard event
393 	 *                            changing.
394 	 */
395 	virtual void handle_key_page_down(SDL_Keymod modifier, bool& handled);
396 
397 	/**
398 	 * Up arrow key pressed.
399 	 *
400 	 * @param modifier            The SDL keyboard modifier when the key was
401 	 *                            pressed.
402 	 * @param handled             If the function handles the key it should
403 	 *                            set handled to true else do not modify it.
404 	 *                            This is used in the keyboard event
405 	 *                            changing.
406 	 */
407 	virtual void handle_key_up_arrow(SDL_Keymod modifier, bool& handled);
408 
409 	/**
410 	 * Down arrow key pressed.
411 	 *
412 	 * @param modifier            The SDL keyboard modifier when the key was
413 	 *                            pressed.
414 	 * @param handled             If the function handles the key it should
415 	 *                            set handled to true else do not modify it.
416 	 *                            This is used in the keyboard event
417 	 *                            changing.
418 	 */
419 	virtual void handle_key_down_arrow(SDL_Keymod modifier, bool& handled);
420 
421 	/**
422 	 * Left arrow key pressed.
423 	 *
424 	 * @param modifier            The SDL keyboard modifier when the key was
425 	 *                            pressed.
426 	 * @param handled             If the function handles the key it should
427 	 *                            set handled to true else do not modify it.
428 	 *                            This is used in the keyboard event
429 	 *                            changing.
430 	 */
431 	virtual void handle_key_left_arrow(SDL_Keymod modifier, bool& handled);
432 
433 	/**
434 	 * Right arrow key pressed.
435 	 *
436 	 * @param modifier            The SDL keyboard modifier when the key was
437 	 *                            pressed.
438 	 * @param handled             If the function handles the key it should
439 	 *                            set handled to true else do not modify it.
440 	 *                            This is used in the keyboard event
441 	 *                            changing.
442 	 */
443 	virtual void handle_key_right_arrow(SDL_Keymod modifier, bool& handled);
444 
445 private:
446 	/**
447 	 * Possible states of the widget.
448 	 *
449 	 * Note the order of the states must be the same as defined in settings.hpp.
450 	 */
451 	enum state_t {
452 		ENABLED,
453 		DISABLED,
454 	};
455 
456 	/**
457 	 * Current state of the widget.
458 	 *
459 	 * The state of the widget determines what to render and how the widget
460 	 * reacts to certain 'events'.
461 	 */
462 	state_t state_;
463 
464 	/**
465 	 * The mode of how to show the scrollbar.
466 	 *
467 	 * This value should only be modified before showing, doing it while
468 	 * showing results in UB.
469 	 */
470 	scrollbar_mode vertical_scrollbar_mode_, horizontal_scrollbar_mode_;
471 
472 	/** These are valid after finalize_setup(). */
473 	grid *vertical_scrollbar_grid_, *horizontal_scrollbar_grid_;
474 
475 	/** These are valid after finalize_setup(). */
476 	scrollbar_base *vertical_scrollbar_, *horizontal_scrollbar_;
477 
478 	/** The grid that holds the content. */
479 	std::unique_ptr<grid> content_grid_;
480 
481 	/** Dummy spacer to hold the contents location. */
482 	spacer* content_;
483 
484 	/**
485 	 * Cache for the visible area for the content.
486 	 *
487 	 * The visible area for the content needs to be updated when scrolling.
488 	 */
489 	SDL_Rect content_visible_area_;
490 
491 	/** The builder needs to call us so we do our setup. */
492 	void finalize_setup(); // FIXME make protected
493 
494 	/**
495 	 * Function for the subclasses to do their setup.
496 	 *
497 	 * This function is called at the end of finalize_setup().
498 	 */
finalize_subclass()499 	virtual void finalize_subclass()
500 	{
501 	}
502 
503 	/** See @ref widget::layout_children. */
504 	virtual void layout_children() override;
505 
506 	/** See @ref widget::impl_draw_children. */
507 	virtual void impl_draw_children(surface& frame_buffer, int x_offset, int y_offset) override;
508 
509 	/** See @ref widget::child_populate_dirty_list. */
510 	virtual void child_populate_dirty_list(window& caller, const std::vector<widget*>& call_stack) override;
511 
512 	/**
513 	 * Sets the size of the content grid.
514 	 *
515 	 * This function normally just updates the content grid but can be
516 	 * overridden by a subclass.
517 	 *
518 	 * @param origin              The origin for the content.
519 	 * @param size                The size of the content.
520 	 */
521 	virtual void set_content_size(const point& origin, const point& size);
522 
523 	/** Helper function which needs to be called after the scollbar moved. */
524 	void scrollbar_moved();
525 
526 public:
527 	/** Static type getter that does not rely on the widget being constructed. */
528 	static const std::string& type();
529 
530 private:
531 	/** See @ref styled_widget::get_control_type. */
532 	virtual const std::string& get_control_type() const override;
533 
534 	/***** ***** ***** signal handlers ***** ****** *****/
535 
536 	void signal_handler_sdl_key_down(
537 			const event::ui_event event, bool& handled, const SDL_Keycode key, SDL_Keymod modifier);
538 
539 	void signal_handler_sdl_wheel_up(const event::ui_event event, bool& handled);
540 	void signal_handler_sdl_wheel_down(const event::ui_event event, bool& handled);
541 	void signal_handler_sdl_wheel_left(const event::ui_event event, bool& handled);
542 	void signal_handler_sdl_wheel_right(const event::ui_event event, bool& handled);
543 	void signal_handler_sdl_touch_motion(const event::ui_event event, bool& handled,
544                                          const point& position, const point& distance);
545 
546 public:
horizontal_scrollbar()547 	scrollbar_base* horizontal_scrollbar()
548 	{
549 		return horizontal_scrollbar_;
550 	}
551 
vertical_scrollbar()552 	scrollbar_base* vertical_scrollbar()
553 	{
554 		return vertical_scrollbar_;
555 	}
556 
get_horizontal_scrollbar_grid()557 	grid* get_horizontal_scrollbar_grid()
558 	{
559 		return horizontal_scrollbar_grid_;
560 	}
561 
get_vertical_scrollbar_grid()562 	grid* get_vertical_scrollbar_grid()
563 	{
564 		return vertical_scrollbar_grid_;
565 	}
566 };
567 
568 } // namespace gui2
569