1 /*
2 	This file is part of Warzone 2100.
3 	Copyright (C) 2020  Warzone 2100 Project
4 
5 	Warzone 2100 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 
10 	Warzone 2100 is distributed in the hope that it will be useful,
11 	but WITHOUT ANY WARRANTY; without even the implied warranty of
12 	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 	GNU General Public License for more details.
14 
15 	You should have received a copy of the GNU General Public License
16 	along with Warzone 2100; if not, write to the Free Software
17 	Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
18 */
19 /**
20  * @file
21  * Functions for scrollable list.
22  */
23 
24 #include "scrollablelist.h"
25 #include "lib/framework/input.h"
26 #include "lib/ivis_opengl/pieblitfunc.h"
27 
28 static const auto SCROLLBAR_WIDTH = 15;
29 
initialize()30 void ScrollableListWidget::initialize()
31 {
32 	attach(scrollBar = ScrollBarWidget::make());
33 	attach(listView = std::make_shared<ClipRectWidget>());
34 	scrollBar->show(false);
35 	backgroundColor.rgba = 0;
36 }
37 
geometryChanged()38 void ScrollableListWidget::geometryChanged()
39 {
40 	scrollBar->setGeometry(width() - SCROLLBAR_WIDTH, 0, SCROLLBAR_WIDTH, height());
41 	scrollBar->setViewSize(calculateListViewHeight());
42 	layoutDirty = true;
43 }
44 
run(W_CONTEXT * psContext)45 void ScrollableListWidget::run(W_CONTEXT *psContext)
46 {
47 	updateLayout();
48 	listView->setTopOffset(snapOffset ? snappedOffset() : scrollBar->position());
49 }
50 
51 /**
52  * Snap offset to first visible child.
53  *
54  * This wouldn't be necessary if it were possible to clip the rendering.
55  */
snappedOffset()56 uint32_t ScrollableListWidget::snappedOffset()
57 {
58 	for (auto child : listView->children())
59 	{
60 		if (child->y() >= scrollBar->position())
61 		{
62 			return child->y();
63 		}
64 	}
65 
66 	return 0;
67 }
68 
addItem(const std::shared_ptr<WIDGET> & item)69 void ScrollableListWidget::addItem(const std::shared_ptr<WIDGET> &item)
70 {
71 	listView->attach(item);
72 	layoutDirty = true;
73 }
74 
clear()75 void ScrollableListWidget::clear()
76 {
77 	listView->removeAllChildren();
78 	layoutDirty = true;
79 	updateLayout();
80 	listView->setTopOffset(0);
81 }
82 
updateLayout()83 void ScrollableListWidget::updateLayout()
84 {
85 	if (!layoutDirty) {
86 		return;
87 	}
88 	layoutDirty = false;
89 
90 	auto listViewWidthWithoutScrollBar = calculateListViewWidth();
91 	auto listViewWidthWithScrollBar = listViewWidthWithoutScrollBar - scrollBar->width();
92 	auto listViewHeight = calculateListViewHeight();
93 
94 	resizeChildren(listViewWidthWithScrollBar);
95 
96 	scrollBar->show(scrollableHeight > listViewHeight);
97 
98 	if (scrollBar->visible())
99 	{
100 		listView->setGeometry(padding.left, padding.top, listViewWidthWithScrollBar, listViewHeight);
101 	} else {
102 		resizeChildren(listViewWidthWithoutScrollBar);
103 		listView->setGeometry(padding.left, padding.top, listViewWidthWithoutScrollBar, listViewHeight);
104 	}
105 
106 	scrollBar->setScrollableSize(scrollableHeight);
107 }
108 
resizeChildren(uint32_t width)109 void ScrollableListWidget::resizeChildren(uint32_t width)
110 {
111 	scrollableHeight = 0;
112 	auto nextOffset = 0;
113 	for (auto child : listView->children())
114 	{
115 		child->setGeometry(0, nextOffset, width, child->height());
116 		scrollableHeight = nextOffset + child->height();
117 		nextOffset = scrollableHeight + itemSpacing;
118 	}
119 }
120 
calculateListViewHeight() const121 uint32_t ScrollableListWidget::calculateListViewHeight() const
122 {
123 	return height() - padding.top - padding.bottom;
124 }
125 
calculateListViewWidth() const126 uint32_t ScrollableListWidget::calculateListViewWidth() const
127 {
128 	return width() - padding.left - padding.right;
129 }
130 
processClickRecursive(W_CONTEXT * psContext,WIDGET_KEY key,bool wasPressed)131 bool ScrollableListWidget::processClickRecursive(W_CONTEXT *psContext, WIDGET_KEY key, bool wasPressed)
132 {
133 	scrollBar->incrementPosition(-getMouseWheelSpeed().y * 20);
134 	return WIDGET::processClickRecursive(psContext, key, wasPressed);
135 }
136 
enableScroll()137 void ScrollableListWidget::enableScroll()
138 {
139 	scrollBar->enable();
140 }
141 
disableScroll()142 void ScrollableListWidget::disableScroll()
143 {
144 	scrollBar->disable();
145 }
146 
setStickToBottom(bool value)147 void ScrollableListWidget::setStickToBottom(bool value)
148 {
149 	scrollBar->setStickToBottom(value);
150 }
151 
setPadding(Padding const & rect)152 void ScrollableListWidget::setPadding(Padding const &rect)
153 {
154 	padding = rect;
155 	layoutDirty = true;
156 }
157 
setBackgroundColor(PIELIGHT const & color)158 void ScrollableListWidget::setBackgroundColor(PIELIGHT const &color)
159 {
160 	backgroundColor = color;
161 }
162 
setSnapOffset(bool value)163 void ScrollableListWidget::setSnapOffset(bool value)
164 {
165 	snapOffset = value;
166 }
167 
setItemSpacing(uint32_t value)168 void ScrollableListWidget::setItemSpacing(uint32_t value)
169 {
170 	itemSpacing = value;
171 }
172 
display(int xOffset,int yOffset)173 void ScrollableListWidget::display(int xOffset, int yOffset)
174 {
175 	if (backgroundColor.rgba != 0)
176 	{
177 		int x0 = x() + xOffset;
178 		int y0 = y() + yOffset;
179 		pie_UniTransBoxFill(x0, y0, x0 + width(), y0 + height(), backgroundColor);
180 	}
181 }
182 
displayRecursive(WidgetGraphicsContext const & context)183 void ScrollableListWidget::displayRecursive(WidgetGraphicsContext const& context)
184 {
185 	updateLayout();
186 	WIDGET::displayRecursive(context);
187 }
188 
getScrollbarWidth() const189 int ScrollableListWidget::getScrollbarWidth() const
190 {
191 	return SCROLLBAR_WIDTH;
192 }
193 
getScrollPosition() const194 uint16_t ScrollableListWidget::getScrollPosition() const
195 {
196 	return scrollBar->position();
197 }
198 
setScrollPosition(uint16_t newPosition)199 void ScrollableListWidget::setScrollPosition(uint16_t newPosition)
200 {
201 	updateLayout();
202 	scrollBar->setPosition(newPosition);
203 	listView->setTopOffset(snapOffset ? snappedOffset() : scrollBar->position());
204 }
205