1 /*
2 * This file is part of Dune Legacy.
3 *
4 * Dune Legacy is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, either version 2 of the License, or
7 * (at your option) any later version.
8 *
9 * Dune Legacy is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with Dune Legacy. If not, see <http://www.gnu.org/licenses/>.
16 */
17
18 #include <GUI/DropDownBox.h>
19
20 #include <algorithm>
21
DropDownBox()22 DropDownBox::DropDownBox() : Widget() {
23 enableResizing(true,false);
24
25 numVisibleEntries = 7;
26
27 color = COLOR_DEFAULT;
28 bHover = false;
29 updateButtonSurface();
30
31 openListBoxButton.setOnClick(std::bind(&DropDownBox::onOpenListBoxButton, this));
32
33 listBox.setOnSelectionChange(std::bind(&DropDownBox::onSelectionChange, this, std::placeholders::_1));
34
35 pBackground = nullptr;
36 pForeground = nullptr;
37 pActiveForeground = nullptr;
38
39 bShowListBox = false;
40 bListBoxAbove = false;
41 bAutocloseListBoxOnSelectionChange = true;
42 bOnClickEnabled = true;
43
44 resize(getMinimumSize().x,getMinimumSize().y);
45 }
46
~DropDownBox()47 DropDownBox::~DropDownBox() {
48 invalidateForeground();
49 invalidateBackground();
50 }
51
handleMouseMovement(Sint32 x,Sint32 y,bool insideOverlay)52 void DropDownBox::handleMouseMovement(Sint32 x, Sint32 y, bool insideOverlay) {
53 if((x < 0) || (x >= getSize().x - openListBoxButton.getSize().x - 1) || (y < 0) || (y >= getSize().y)) {
54 bHover = false;
55 } else if((isEnabled() || (bOnClickEnabled && pOnClick)) && !insideOverlay) {
56 bHover = true;
57 } else {
58 bHover = false;
59 }
60
61 openListBoxButton.handleMouseMovement(x - (getSize().x - openListBoxButton.getSize().x - 1), y - 1, insideOverlay);
62
63 if(bShowListBox) {
64 listBox.handleMouseMovement(x, bListBoxAbove ? (y + listBox.getSize().y) : (y - getSize().y), insideOverlay);
65 }
66 }
67
handleMouseMovementOverlay(Sint32 x,Sint32 y)68 bool DropDownBox::handleMouseMovementOverlay(Sint32 x, Sint32 y) {
69 int newY = bListBoxAbove ? (y + listBox.getSize().y) : (y - getSize().y);
70 if(bShowListBox && x >= 0 && x < listBox.getSize().x && newY >= 0 && newY < listBox.getSize().y) {
71 return true;
72 } else {
73 return false;
74 }
75 }
76
handleMouseLeft(Sint32 x,Sint32 y,bool pressed)77 bool DropDownBox::handleMouseLeft(Sint32 x, Sint32 y, bool pressed) {
78 if((isEnabled() == false) || (isVisible() == false)) {
79 // onClick works even when widget is disabled
80 if(bOnClickEnabled && isVisible() && pOnClick) {
81 if((x>=0) && (x < getSize().x - openListBoxButton.getSize().x - 1)
82 && (y>=0) && (y < getSize().y) && (pressed == true)) {
83 pOnClick();
84 }
85 }
86
87 return true;
88 }
89
90 if(openListBoxButton.handleMouseLeft(x - (getSize().x - openListBoxButton.getSize().x - 1), y - 1, pressed)) {
91 setActive();
92 return true;
93 } else {
94 if((x>=0) && (x < getSize().x - openListBoxButton.getSize().x - 1)
95 && (y>=0) && (y < getSize().y) && (pressed == true)) {
96
97 if(bOnClickEnabled && pOnClick) {
98 pOnClick();
99 } else {
100 setActive();
101 onOpenListBoxButton();
102 }
103 return true;
104 } else {
105 return false;
106 }
107 }
108 }
109
handleMouseLeftOverlay(Sint32 x,Sint32 y,bool pressed)110 bool DropDownBox::handleMouseLeftOverlay(Sint32 x, Sint32 y, bool pressed) {
111 if((isEnabled() == false) || (isVisible() == false)) {
112 return false;
113 }
114
115 if(bShowListBox) {
116 if(listBox.handleMouseLeft(x, bListBoxAbove ? (y + listBox.getSize().y) : (y - getSize().y), pressed) == false) {
117 if(x < 0 || x >= getSize().x || y < 0 || y >= getSize().y) {
118 // if not on drop down box => click is handled by closing drop down box
119 bShowListBox = false;
120 return true;
121 } else {
122 // on drop down box we don't handle overlay click
123 return false;
124 }
125 } else {
126 return true;
127 }
128 } else {
129 return false;
130 }
131 }
132
handleMouseWheel(Sint32 x,Sint32 y,bool up)133 bool DropDownBox::handleMouseWheel(Sint32 x, Sint32 y, bool up) {
134 if((isEnabled() == false) || (isVisible() == false)) {
135 return false;
136 }
137
138 // forward mouse wheel event to list box
139 if(x >= 0 && x < getSize().x && y >= 0 && y < getSize().y) {
140 if(up) {
141 if(listBox.getSelectedIndex() < 0) {
142 listBox.setSelectedItem(0,true);
143 } else if(listBox.getSelectedIndex() > 0) {
144 listBox.setSelectedItem(listBox.getSelectedIndex()-1,true);
145 }
146 } else {
147 if(listBox.getSelectedIndex() < 0) {
148 listBox.setSelectedItem(0,true);
149 } else if(listBox.getSelectedIndex() < listBox.getNumEntries()-1) {
150 listBox.setSelectedItem(listBox.getSelectedIndex()+1,true);
151 }
152 }
153 return true;
154 } else {
155 return false;
156 }
157 }
158
handleMouseWheelOverlay(Sint32 x,Sint32 y,bool up)159 bool DropDownBox::handleMouseWheelOverlay(Sint32 x, Sint32 y, bool up) {
160 if((isEnabled() == false) || (isVisible() == false)) {
161 return false;
162 }
163
164 // forward mouse wheel event to list box
165 if(bShowListBox) {
166 int newY = bListBoxAbove ? (y + listBox.getSize().y) : (y - getSize().y);
167 listBox.handleMouseWheel(x,newY,up);
168 if(x >= 0 && x < listBox.getSize().x && newY >= 0 && newY < listBox.getSize().y) {
169 return true;
170 } else {
171 return false;
172 }
173 } else {
174 return false;
175 }
176 }
177
handleKeyPress(SDL_KeyboardEvent & key)178 bool DropDownBox::handleKeyPress(SDL_KeyboardEvent& key) {
179 if((isEnabled() == false) || (isVisible() == false)) {
180 return false;
181 }
182
183 Widget::handleKeyPress(key);
184 if(isActive()) {
185 // disable autoclosing of the list box
186 bool bSavedAutoclose = bAutocloseListBoxOnSelectionChange;
187 bAutocloseListBoxOnSelectionChange = false;
188
189 switch(key.keysym.sym) {
190 case SDLK_UP: {
191 if(listBox.getSelectedIndex() < 0) {
192 listBox.setSelectedItem(0,true);
193 } else if(listBox.getSelectedIndex() > 0) {
194 listBox.setSelectedItem(listBox.getSelectedIndex()-1,true);
195 }
196 } break;
197
198 case SDLK_DOWN: {
199 if(listBox.getSelectedIndex() < 0) {
200 listBox.setSelectedItem(0,true);
201 } else if(listBox.getSelectedIndex() < listBox.getNumEntries()-1) {
202 listBox.setSelectedItem(listBox.getSelectedIndex()+1,true);
203 }
204 } break;
205
206 case SDLK_SPACE: {
207 bShowListBox = !bShowListBox;
208 } break;
209
210 case SDLK_TAB: {
211 setInactive();
212 } break;
213
214 default: {
215 } break;
216 }
217
218 bAutocloseListBoxOnSelectionChange = bSavedAutoclose;
219 return true;
220 }
221
222 return false;
223
224 }
225
draw(Point position)226 void DropDownBox::draw(Point position) {
227 if(isVisible() == false) {
228 return;
229 }
230
231 updateBackground();
232
233 if(pBackground != nullptr) {
234 SDL_Rect dest = calcDrawingRect(pBackground, position.x, position.y);
235 SDL_RenderCopy(renderer, pBackground, nullptr, &dest);
236 }
237
238 updateForeground();
239
240 if(pForeground != nullptr && pActiveForeground != nullptr) {
241 if(((bHover == true) && pOnClick) || isActive()) {
242 SDL_Rect dest = calcDrawingRect(pActiveForeground, position.x + 2, position.y + 2);
243 SDL_RenderCopy(renderer, pActiveForeground, nullptr, &dest);
244 } else {
245 SDL_Rect dest = calcDrawingRect(pForeground, position.x + 2, position.y + 2);
246 SDL_RenderCopy(renderer, pForeground, nullptr, &dest);
247 }
248 }
249
250 openListBoxButton.draw(position + Point(getSize().x - openListBoxButton.getSize().x - 1, 1));
251 }
252
drawOverlay(Point position)253 void DropDownBox::drawOverlay(Point position) {
254 if(bShowListBox) {
255 bListBoxAbove = (position.y + listBox.getSize().y > getRendererHeight());
256 listBox.draw(position + Point(0,bListBoxAbove ? -listBox.getSize().y : getSize().y));
257 }
258 }
259
resize(Uint32 width,Uint32 height)260 void DropDownBox::resize(Uint32 width, Uint32 height) {
261 Widget::resize(width,height);
262
263 invalidateForeground();
264 invalidateBackground();
265
266 resizeListBox();
267 }
268
resizeListBox()269 void DropDownBox::resizeListBox() {
270 int listBoxHeight = std::max(1,std::min(numVisibleEntries,getNumEntries())) * (int) GUIStyle::getInstance().getListBoxEntryHeight() + 2;
271 listBox.resize(getSize().x-1, listBoxHeight);
272 }
273
setActive(bool bActive)274 void DropDownBox::setActive(bool bActive) {
275 if(bActive == false) {
276 bShowListBox = false;
277 openListBoxButton.setInactive();
278 } else {
279 openListBoxButton.setActive();
280 }
281 Widget::setActive(bActive);
282 }
283
onSelectionChange(bool bInteractive)284 void DropDownBox::onSelectionChange(bool bInteractive) {
285 invalidateForeground();
286
287 if(bAutocloseListBoxOnSelectionChange) {
288 bShowListBox = false;
289 }
290
291 if(pOnSelectionChange) {
292 pOnSelectionChange(bInteractive);
293 }
294 }
295
updateButtonSurface()296 void DropDownBox::updateButtonSurface() {
297 openListBoxButton.setSurfaces( GUIStyle::getInstance().createDropDownBoxButton(17,false,false,color), true,
298 GUIStyle::getInstance().createDropDownBoxButton(17,true,true,color), true,
299 GUIStyle::getInstance().createDropDownBoxButton(17,false,true,color), true);
300 }
301
invalidateForeground()302 void DropDownBox::invalidateForeground() {
303 if(pForeground != nullptr) {
304 SDL_DestroyTexture(pForeground);
305 pForeground = nullptr;
306 }
307
308 if(pActiveForeground != nullptr) {
309 SDL_DestroyTexture(pActiveForeground);
310 pActiveForeground = nullptr;
311 }
312 }
313
updateForeground()314 void DropDownBox::updateForeground() {
315 if(pForeground == nullptr && pActiveForeground == nullptr) {
316 if(listBox.getSelectedIndex() >= 0) {
317 pForeground = convertSurfaceToTexture(GUIStyle::getInstance().createListBoxEntry(getSize().x - 17, listBox.getEntry(listBox.getSelectedIndex()), false, color), true);
318 pActiveForeground = convertSurfaceToTexture(GUIStyle::getInstance().createListBoxEntry(getSize().x - 17, listBox.getEntry(listBox.getSelectedIndex()), true, color), true);
319 }
320 }
321 }
322
invalidateBackground()323 void DropDownBox::invalidateBackground() {
324 if(pBackground != nullptr) {
325 SDL_DestroyTexture(pBackground);
326 pBackground = nullptr;
327 }
328 }
329
updateBackground()330 void DropDownBox::updateBackground() {
331 if(pBackground == nullptr) {
332 pBackground = convertSurfaceToTexture(GUIStyle::getInstance().createWidgetBackground(getSize().x, getSize().y), true);
333 }
334 }
335