1 /***************************************************************************
2 * Mechanized Assault and Exploration Reloaded Projectfile *
3 * *
4 * This program 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 * This program 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 this program; if not, write to the *
16 * Free Software Foundation, Inc., *
17 * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
18 ***************************************************************************/
19
20 #include "ui/graphical/widget.h"
21
22 #include "ui/graphical/application.h"
23 #include "settings.h"
24 #include "video.h"
25 #include "utility/drawing.h"
26 #include "utility/color.h"
27
28 /*static*/ bool cWidget::drawDebugFrames = false;
29 /*static*/ cSignal<void ()> cWidget::drawDebugFramesChanged;
30
31 //------------------------------------------------------------------------------
toggleDrawDebugFrames()32 /*static*/ void cWidget::toggleDrawDebugFrames()
33 {
34 drawDebugFrames = !drawDebugFrames;
35 drawDebugFramesChanged();
36 }
37
38 //------------------------------------------------------------------------------
cWidget()39 cWidget::cWidget() :
40 parent (nullptr),
41 enabled (true),
42 hidden (false),
43 area (cPosition (0, 0), cPosition (0, 0))
44 {
45 createFrameSurface();
46
47 signalConnectionManager.connect (drawDebugFramesChanged, [this]() { createFrameSurface(); });
48 }
49
50 //------------------------------------------------------------------------------
cWidget(const cPosition & position)51 cWidget::cWidget (const cPosition& position) :
52 parent (nullptr),
53 enabled (true),
54 hidden (false),
55 area (position, position)
56 {
57 createFrameSurface();
58
59 signalConnectionManager.connect (drawDebugFramesChanged, [this]() { createFrameSurface(); });
60 }
61
62 //------------------------------------------------------------------------------
cWidget(const cBox<cPosition> & area_)63 cWidget::cWidget (const cBox<cPosition>& area_) :
64 parent (nullptr),
65 enabled (true),
66 hidden (false),
67 area (area_)
68 {
69 createFrameSurface();
70
71 signalConnectionManager.connect (drawDebugFramesChanged, [this]() { createFrameSurface(); });
72 }
73
74 //------------------------------------------------------------------------------
~cWidget()75 cWidget::~cWidget()
76 {
77 auto application = getActiveApplication();
78
79 if (application)
80 {
81 // release the mouse or key focus of this widget or any of its children.
82 // We have to release the focus of the children as well because after the parent is deleted
83 // the children have no possibility to access the application anymore
84 releaseFocusRecursive (*application);
85 }
86
87 // just to make sure the children will not access the parent during their clean up.
88 for (auto& child : children)
89 {
90 child->setParent (nullptr);
91 }
92 }
93
94 //------------------------------------------------------------------------------
getParent() const95 cWidget* cWidget::getParent() const
96 {
97 return parent;
98 }
99
100 //------------------------------------------------------------------------------
isEnabled() const101 bool cWidget::isEnabled() const
102 {
103 return enabled;
104 }
105
106 //------------------------------------------------------------------------------
disable()107 void cWidget::disable()
108 {
109 enabled = false;
110 }
111
112 //------------------------------------------------------------------------------
enable()113 void cWidget::enable()
114 {
115 enabled = true;
116 }
117
118 //------------------------------------------------------------------------------
isHidden() const119 bool cWidget::isHidden() const
120 {
121 return hidden;
122 }
123
124 //------------------------------------------------------------------------------
hide()125 void cWidget::hide()
126 {
127 hidden = true;
128 }
129
130 //------------------------------------------------------------------------------
show()131 void cWidget::show()
132 {
133 hidden = false;
134 }
135
136 //------------------------------------------------------------------------------
getPosition() const137 const cPosition& cWidget::getPosition() const
138 {
139 return area.getMinCorner();
140 }
141
142 //------------------------------------------------------------------------------
getEndPosition() const143 const cPosition& cWidget::getEndPosition() const
144 {
145 return area.getMaxCorner();
146 }
147
148 //------------------------------------------------------------------------------
moveTo(const cPosition & newPosition)149 void cWidget::moveTo (const cPosition& newPosition)
150 {
151 auto offset = newPosition - area.getMinCorner();
152 move (offset);
153 }
154
155 //------------------------------------------------------------------------------
move(const cPosition & offset)156 void cWidget::move (const cPosition& offset)
157 {
158 area.getMinCorner() += offset;
159 area.getMaxCorner() += offset;
160 handleMoved (offset);
161 }
162
163 //------------------------------------------------------------------------------
getSize() const164 cPosition cWidget::getSize() const
165 {
166 return area.getSize();
167 }
168
169 //------------------------------------------------------------------------------
resize(const cPosition & newSize)170 void cWidget::resize (const cPosition& newSize)
171 {
172 const auto oldSize = getSize();
173
174 if (oldSize != newSize)
175 {
176 area.resize (newSize);
177 handleResized (oldSize);
178 }
179 }
180
181 //------------------------------------------------------------------------------
fitToChildren()182 void cWidget::fitToChildren()
183 {
184 if (children.empty())
185 {
186 resize (cPosition (0, 0));
187 }
188 else
189 {
190 cBox<cPosition> newArea (children[0]->getPosition(), children[0]->getPosition());
191
192 for (size_t i = 0; i < children.size(); ++i)
193 {
194 newArea.add (children[i]->getArea());
195 }
196
197 setArea (newArea);
198 }
199 }
200
201 //------------------------------------------------------------------------------
getArea() const202 const cBox<cPosition>& cWidget::getArea() const
203 {
204 return area;
205 }
206
207 //------------------------------------------------------------------------------
setArea(const cBox<cPosition> & area_)208 void cWidget::setArea (const cBox<cPosition>& area_)
209 {
210 const cPosition newSize = area_.getSize();
211 const cPosition offset = area_.getMinCorner() - getArea().getMinCorner();
212
213 move (offset);
214 resize (newSize);
215 }
216
217 //------------------------------------------------------------------------------
addShortcut(std::unique_ptr<cShortcut> shortcut)218 cShortcut* cWidget::addShortcut (std::unique_ptr<cShortcut> shortcut)
219 {
220 if(shortcut == nullptr) return nullptr;
221
222 shortcuts.push_back (std::move (shortcut));
223 return shortcuts.back().get();
224 }
225
226 //------------------------------------------------------------------------------
getShortcuts() const227 const std::vector<std::unique_ptr<cShortcut>>& cWidget::getShortcuts() const
228 {
229 return shortcuts;
230 }
231
232 //------------------------------------------------------------------------------
getChildAt(const cPosition & position) const233 cWidget* cWidget::getChildAt (const cPosition& position) const
234 {
235 // reverse order because the last child is the one that will be drawn last and therefor
236 // is visually the one above all the others.
237 // We want to find this one first, because we will abort on the first child that
238 // intersects the point, regardless of whether there are other overlapping children.
239 for (auto i = children.rbegin(); i != children.rend(); ++i)
240 {
241 auto child = i->get();
242
243 if (!child->isEnabled()) continue;
244
245 if (child->isAt (position))
246 {
247 auto childChild = child->getChildAt (position);
248 return childChild ? childChild : child;
249 }
250 }
251 return nullptr;
252 }
253
254 //------------------------------------------------------------------------------
isAt(const cPosition & position) const255 bool cWidget::isAt (const cPosition& position) const
256 {
257 return !isHidden() && area.withinOrTouches (position);
258 }
259
260 //------------------------------------------------------------------------------
draw(SDL_Surface & destination,const cBox<cPosition> & clipRect)261 void cWidget::draw (SDL_Surface& destination, const cBox<cPosition>& clipRect)
262 {
263 if (isHidden()) return;
264
265 if (frameSurface && getArea().intersects (clipRect))
266 {
267 auto clipedArea = getArea().intersection (clipRect);
268
269 SDL_Rect position = clipedArea.toSdlRect();
270
271 clipedArea.getMinCorner() -= getPosition();
272 clipedArea.getMaxCorner() -= getPosition();
273
274 SDL_Rect source = clipedArea.toSdlRect();
275
276 SDL_BlitSurface (frameSurface.get(), &source, &destination, &position);
277 }
278
279 for (auto i = children.begin(); i != children.end(); ++i)
280 {
281 auto& child = *i->get();
282
283 if (child.isHidden()) continue;
284
285 if (child.getArea().intersects (clipRect))
286 {
287 child.draw (destination, child.getArea().intersection (clipRect));
288 }
289 }
290 }
291
292 //------------------------------------------------------------------------------
handleMouseMoved(cApplication & application,cMouse & mouse,const cPosition & offset)293 bool cWidget::handleMouseMoved (cApplication& application, cMouse& mouse, const cPosition& offset)
294 {
295 return false;
296 }
297
298 //------------------------------------------------------------------------------
handleMousePressed(cApplication & application,cMouse & mouse,eMouseButtonType button)299 bool cWidget::handleMousePressed (cApplication& application, cMouse& mouse, eMouseButtonType button)
300 {
301 return false;
302 }
303
304 //------------------------------------------------------------------------------
handleMouseReleased(cApplication & application,cMouse & mouse,eMouseButtonType button)305 bool cWidget::handleMouseReleased (cApplication& application, cMouse& mouse, eMouseButtonType button)
306 {
307 return false;
308 }
309
310 //------------------------------------------------------------------------------
handleMouseWheelMoved(cApplication & application,cMouse & mouse,const cPosition & amount)311 bool cWidget::handleMouseWheelMoved (cApplication& application, cMouse& mouse, const cPosition& amount)
312 {
313 return false;
314 }
315
316 //------------------------------------------------------------------------------
handleKeyPressed(cApplication & application,cKeyboard & keyboard,SDL_Keycode key)317 bool cWidget::handleKeyPressed (cApplication& application, cKeyboard& keyboard, SDL_Keycode key)
318 {
319 for (auto i = children.begin(); i != children.end(); ++i)
320 {
321 auto& child = *i->get();
322 if (child.handleKeyPressed (application, keyboard, key))
323 {
324 return true;
325 }
326 }
327 return false;
328 }
329
330 //------------------------------------------------------------------------------
handleKeyReleased(cApplication & application,cKeyboard & keyboard,SDL_Keycode key)331 bool cWidget::handleKeyReleased (cApplication& application, cKeyboard& keyboard, SDL_Keycode key)
332 {
333 for (auto i = children.begin(); i != children.end(); ++i)
334 {
335 auto& child = *i->get();
336 if (child.handleKeyReleased (application, keyboard, key))
337 {
338 return true;
339 }
340 }
341 return false;
342 }
343
344 //------------------------------------------------------------------------------
handleTextEntered(cApplication & application,cKeyboard & keyboard,const char * text)345 void cWidget::handleTextEntered (cApplication& application, cKeyboard& keyboard, const char* text)
346 {}
347
348 ////------------------------------------------------------------------------------
349 //void cWidget::handleHoveredOn (cApplication& application, cMouse& mouse)
350 //{}
351 //
352 ////------------------------------------------------------------------------------
353 //void cWidget::handleHoveredAway (cApplication& application, cMouse& mouse)
354 //{}
355
356 //------------------------------------------------------------------------------
handleGetKeyFocus(cApplication & application)357 bool cWidget::handleGetKeyFocus (cApplication& application)
358 {
359 return false;
360 }
361
362 //------------------------------------------------------------------------------
handleLooseKeyFocus(cApplication & application)363 void cWidget::handleLooseKeyFocus (cApplication& application)
364 {}
365
366 //------------------------------------------------------------------------------
handleGetMouseFocus(cApplication & application)367 void cWidget::handleGetMouseFocus (cApplication& application)
368 {}
369
370 //------------------------------------------------------------------------------
handleLooseMouseFocus(cApplication & application)371 void cWidget::handleLooseMouseFocus (cApplication& application)
372 {}
373
374 //------------------------------------------------------------------------------
handleMoved(const cPosition & offset)375 void cWidget::handleMoved (const cPosition& offset)
376 {
377 for (auto i = children.begin(); i != children.end(); ++i)
378 {
379 (*i)->move (offset);
380 }
381 }
382
383 //------------------------------------------------------------------------------
handleResized(const cPosition &)384 void cWidget::handleResized (const cPosition&)
385 {
386 createFrameSurface();
387 }
388
389 //------------------------------------------------------------------------------
setParent(cWidget * parent_)390 void cWidget::setParent (cWidget* parent_)
391 {
392 parent = parent_;
393 }
394
395 //------------------------------------------------------------------------------
removeChildren()396 void cWidget::removeChildren()
397 {
398 children.clear();
399 }
400
401 //------------------------------------------------------------------------------
hasChildren() const402 bool cWidget::hasChildren() const
403 {
404 return !children.empty();
405 }
406
407 //------------------------------------------------------------------------------
getActiveMouse() const408 cMouse* cWidget::getActiveMouse() const
409 {
410 return parent ? parent->getActiveMouse() : nullptr;
411 }
412
413 //------------------------------------------------------------------------------
getActiveKeyboard() const414 cKeyboard* cWidget::getActiveKeyboard() const
415 {
416 return parent ? parent->getActiveKeyboard() : nullptr;
417 }
418
419 //------------------------------------------------------------------------------
getActiveApplication() const420 cApplication* cWidget::getActiveApplication() const
421 {
422 return parent ? parent->getActiveApplication() : nullptr;
423 }
424
425 //------------------------------------------------------------------------------
createFrameSurface()426 void cWidget::createFrameSurface()
427 {
428 if (drawDebugFrames)
429 {
430 const auto size = getSize();
431
432 if (size.x() == 0 || size.y() == 0) return;
433
434 frameSurface = AutoSurface (SDL_CreateRGBSurface (0, size.x(), size.y(), Video.getColDepth(), 0, 0, 0, 0));
435 if (!frameSurface) return; // can happen when for some reason the size is invalid (e.g. negative)
436 SDL_SetColorKey (frameSurface.get(), SDL_TRUE, 0xFF00FF);
437 SDL_FillRect (frameSurface.get(), nullptr, 0xFF00FF);
438
439 drawRectangle (*frameSurface, cBox<cPosition> (cPosition (0, 0), size), cRgbColor::red());
440 }
441 else
442 {
443 frameSurface = nullptr;
444 }
445 }
446
447 //------------------------------------------------------------------------------
hitShortcuts(const cKeySequence & keySequence)448 bool cWidget::hitShortcuts (const cKeySequence& keySequence)
449 {
450 // TODO: remove code duplication with cApplication::hitShortcuts
451
452 bool anyMatch = false;
453 for (const auto& shortcut : shortcuts)
454 {
455 if (!shortcut->isActive()) continue;
456
457 const auto& shortcutSequence = shortcut->getKeySequence();
458
459 if (shortcutSequence.length() > keySequence.length()) continue;
460
461 bool match = true;
462 for (size_t j = 1; j <= shortcutSequence.length(); ++j)
463 {
464 if (keySequence[keySequence.length() - j] != shortcutSequence[shortcutSequence.length() - j])
465 {
466 match = false;
467 break;
468 }
469 }
470
471 if (match)
472 {
473 shortcut->triggered();
474 anyMatch = true;
475 }
476 }
477
478 for (const auto& child : children)
479 {
480 anyMatch = child->hitShortcuts (keySequence) || anyMatch;
481 }
482 return anyMatch;
483 }
484
485 //------------------------------------------------------------------------------
releaseFocusRecursive(cApplication & application)486 void cWidget::releaseFocusRecursive (cApplication& application)
487 {
488 if (application.hasKeyFocus (*this)) application.releaseKeyFocus (*this);
489 if (application.hasMouseFocus (*this)) application.releaseMouseFocus (*this);
490
491 if (application.hasKeyFocus() || application.hasMouseFocus())
492 {
493 for (auto& child : children)
494 {
495 child->releaseFocusRecursive (application);
496 }
497 }
498 }
499