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