1 /*
2 This file is part of Warzone 2100.
3 Copyright (C) 1999-2004 Eidos Interactive
4 Copyright (C) 2005-2020 Warzone 2100 Project
5
6 Warzone 2100 is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2 of the License, or
9 (at your option) any later version.
10
11 Warzone 2100 is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
15
16 You should have received a copy of the GNU General Public License
17 along with Warzone 2100; if not, write to the Free Software
18 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
19 */
20 /** @file
21 * The main interface functions to the widget library
22 */
23
24 #include "lib/framework/frame.h"
25 #include "lib/framework/string_ext.h"
26 #include "lib/framework/utf.h"
27 #include "lib/ivis_opengl/textdraw.h"
28 #include "lib/ivis_opengl/pieblitfunc.h"
29 #include "lib/ivis_opengl/piestate.h"
30 #include "lib/ivis_opengl/screen.h"
31 #include "lib/gamelib/gtime.h"
32
33 #include "widget.h"
34
35 #include <utility>
36 #if defined(WZ_CC_MSVC)
37 #include "widgbase.h" // this is generated on the pre-build event.
38 #endif
39 #include "widgint.h"
40
41 #include "form.h"
42 #include "label.h"
43 #include "button.h"
44 #include "editbox.h"
45 #include "bar.h"
46 #include "slider.h"
47 #include "tip.h"
48
49 #include <algorithm>
50 #include <unordered_set>
51 #include <deque>
52
53 static bool bWidgetsActive = true;
54
55 /* The widget the mouse is over this update */
56 static auto psMouseOverWidget = std::weak_ptr<WIDGET>();
57 static auto psClickDownWidgetScreen = std::shared_ptr<W_SCREEN>();
58 static auto psMouseOverWidgetScreen = std::shared_ptr<W_SCREEN>();
59
60 static WIDGET_AUDIOCALLBACK AudioCallback = nullptr;
61 static SWORD HilightAudioID = -1;
62 static SWORD ClickedAudioID = -1;
63 static SWORD ErrorAudioID = -1;
64
65 static WIDGET_KEY lastReleasedKey_DEPRECATED = WKEY_NONE;
66
67 static std::deque<std::shared_ptr<WIDGET>> widgetDeletionQueue;
68 static std::vector<std::function<void()>> widgetScheduledTasks;
69
70 static bool debugBoundingBoxesOnly = false;
71
72 #ifdef DEBUG
73 #include "lib/framework/demangle.hpp"
74 static std::unordered_set<const WIDGET*> debugLiveWidgets;
75 static std::shared_ptr<W_SCREEN> psCurrentlyRunningScreen;
76 #endif
77
78 struct OverlayScreen
79 {
80 std::shared_ptr<W_SCREEN> psScreen;
81 uint16_t zOrder;
82 };
83 static std::vector<OverlayScreen> overlays;
84 static std::unordered_set<std::shared_ptr<W_SCREEN>> overlaySet; // for quick lookup
85 static std::unordered_set<std::shared_ptr<W_SCREEN>> overlaysToDelete;
86
runScheduledTasks()87 static void runScheduledTasks()
88 {
89 auto tasksCopy = std::move(widgetScheduledTasks);
90 for (auto task: tasksCopy)
91 {
92 task();
93 }
94 }
95
_widgDebugAssertIfRunningScreen(const char * function)96 static inline void _widgDebugAssertIfRunningScreen(const char *function)
97 {
98 #ifdef DEBUG
99 if (psCurrentlyRunningScreen != nullptr)
100 {
101 debug(LOG_INFO, "%s is being called from within a screen's click / run handlers", function);
102 if (assertEnabled)
103 {
104 (void)wz_assert(psCurrentlyRunningScreen == nullptr);
105 }
106 }
107 #else
108 // do nothing
109 #endif
110 }
111 #define widgDebugAssertIfRunningScreen() _widgDebugAssertIfRunningScreen(__FUNCTION__)
112
113 /* Initialise the widget module */
widgInitialise()114 bool widgInitialise()
115 {
116 tipInitialise();
117 return true;
118 }
119
120
121 // Reset the widgets.
122 //
widgReset(void)123 void widgReset(void)
124 {
125 tipInitialise();
126 }
127
128
129 /* Shut down the widget module */
widgShutDown(void)130 void widgShutDown(void)
131 {
132 psClickDownWidgetScreen = nullptr;
133 psMouseOverWidgetScreen = nullptr;
134 tipShutdown();
135 overlays.clear();
136 overlaySet.clear();
137 overlaysToDelete.clear();
138 #ifdef DEBUG
139 if (!debugLiveWidgets.empty())
140 {
141 // Some widgets still exist - there may be reference cycles, non-cleared references, or other bugs
142 for (auto widget : debugLiveWidgets)
143 {
144 debug(LOG_ERROR, "Widget not cleaned up: %p (type: %s)", (const void*)widget, cxxDemangle(typeid(*widget).name()).c_str());
145 }
146 ASSERT(debugLiveWidgets.empty(), "There are %zu widgets that were not cleaned up.", debugLiveWidgets.size());
147 }
148 #endif
149 }
150
widgScheduleTask(std::function<void ()> task)151 void widgScheduleTask(std::function<void ()> task)
152 {
153 widgetScheduledTasks.push_back(task);
154 }
155
widgRegisterOverlayScreen(const std::shared_ptr<W_SCREEN> & psScreen,uint16_t zOrder)156 void widgRegisterOverlayScreen(const std::shared_ptr<W_SCREEN> &psScreen, uint16_t zOrder)
157 {
158 OverlayScreen newOverlay {psScreen, zOrder};
159 auto it = std::find_if(overlays.begin(), overlays.end(), [psScreen](const OverlayScreen& overlay) -> bool {
160 return overlay.psScreen == psScreen;
161 });
162 if (it != overlays.end())
163 {
164 // screen already exists in overlay stack
165 if (zOrder == it->zOrder)
166 {
167 // no need to update - duplicate call (same zOrder)
168 return;
169 }
170 else
171 {
172 // remove the existing entry
173 overlays.erase(it);
174 it = overlays.end();
175 // fall-through to inserting it again in the new zOrder
176 }
177 }
178 // the screens are stored in decreasing z-order
179 overlays.insert(std::upper_bound(overlays.begin(), overlays.end(), newOverlay, [](const OverlayScreen& a, const OverlayScreen& b){ return a.zOrder > b.zOrder; }), newOverlay);
180 overlaySet.insert(psScreen);
181 }
182
183 // Note: If priorScreen is not the "regular" screen (i.e. if priorScreen is an overlay screen) it should already be registered
widgRegisterOverlayScreenOnTopOfScreen(const std::shared_ptr<W_SCREEN> & psScreen,const std::shared_ptr<W_SCREEN> & priorScreen)184 void widgRegisterOverlayScreenOnTopOfScreen(const std::shared_ptr<W_SCREEN> &psScreen, const std::shared_ptr<W_SCREEN> &priorScreen)
185 {
186 auto it = std::find_if(overlays.begin(), overlays.end(), [priorScreen](const OverlayScreen& overlay) -> bool {
187 return overlay.psScreen == priorScreen;
188 });
189 if (it != overlays.end())
190 {
191 OverlayScreen newOverlay {psScreen, it->zOrder}; // use the same z-order as priorScreen, but insert *before* it in the list (i.e. "above" it, since overlays are stored in decreasing z-order)
192 overlays.insert(it, newOverlay);
193 overlaySet.insert(psScreen);
194 }
195 else
196 {
197 // priorScreen does not exist in the overlays list, so it is probably the "regular" screen
198 // just insert this overlay at the bottom of the overlay list
199 OverlayScreen newOverlay {psScreen, 0};
200 overlays.insert(overlays.end(), newOverlay);
201 overlaySet.insert(psScreen);
202 }
203 }
204
widgRemoveOverlayScreen(const std::shared_ptr<W_SCREEN> & psScreen)205 void widgRemoveOverlayScreen(const std::shared_ptr<W_SCREEN> &psScreen)
206 {
207 auto it = std::find_if(overlays.begin(), overlays.end(), [psScreen](const OverlayScreen& overlay) -> bool {
208 return overlay.psScreen == psScreen;
209 });
210 if (it != overlays.end())
211 {
212 overlaysToDelete.insert(psScreen);
213 }
214 }
215
cleanupDeletedOverlays()216 static void cleanupDeletedOverlays()
217 {
218 if (overlaysToDelete.empty()) { return; }
219 overlays.erase(
220 std::remove_if(
221 overlays.begin(), overlays.end(),
222 [](const OverlayScreen& a) { return overlaysToDelete.count(a.psScreen) > 0; }
223 ),
224 overlays.end()
225 );
226 for (const auto& overlayToDelete : overlaysToDelete)
227 {
228 overlaySet.erase(overlayToDelete);
229 }
230 overlaysToDelete.clear();
231 }
232
isMouseOverWidget() const233 bool WIDGET::isMouseOverWidget() const
234 {
235 return psMouseOverWidget.lock().get() == this;
236 }
237
setTransparentToClicks(bool hasClickTransparency)238 void WIDGET::setTransparentToClicks(bool hasClickTransparency)
239 {
240 isTransparentToClicks = hasClickTransparency;
241 }
242
transparentToClicks() const243 bool WIDGET::transparentToClicks() const
244 {
245 return isTransparentToClicks;
246 }
247
248 template<typename Iterator>
iterateOverlayScreens(Iterator iter,Iterator end,const std::function<bool (const OverlayScreen & overlay)> & func)249 static inline void iterateOverlayScreens(Iterator iter, Iterator end, const std::function<bool (const OverlayScreen& overlay)>& func)
250 {
251 ASSERT_OR_RETURN(, func.operator bool(), "Requires a valid func");
252 for ( ; iter != end; ++iter )
253 {
254 if (!func(*iter))
255 {
256 break; // stop enumerating
257 }
258 }
259 // now that we aren't in the middle of enumerating overlays, handling removing any that were queued for deletion
260 cleanupDeletedOverlays();
261 }
262
263 // enumerate the overlay screens in decreasing z-order (i.e. "top-down")
forEachOverlayScreen(const std::function<bool (const OverlayScreen & overlay)> & func)264 static inline void forEachOverlayScreen(const std::function<bool (const OverlayScreen& overlay)>& func)
265 {
266 // the screens are stored in decreasing z-order
267 iterateOverlayScreens(overlays.cbegin(), overlays.cend(), func);
268 }
269
270 // enumerate the overlay screens in increasing z-order (i.e. "bottom-up")
forEachOverlayScreenBottomUp(const std::function<bool (const OverlayScreen & overlay)> & func)271 static inline void forEachOverlayScreenBottomUp(const std::function<bool (const OverlayScreen& overlay)>& func)
272 {
273 iterateOverlayScreens(overlays.crbegin(), overlays.crend(), func);
274 }
275
isScreenARegisteredOverlay(const std::shared_ptr<W_SCREEN> & psScreen)276 static bool isScreenARegisteredOverlay(const std::shared_ptr<W_SCREEN> &psScreen)
277 {
278 if (!psScreen) { return false; }
279 return overlaySet.count(psScreen) > 0;
280 }
281
isMouseOverScreenOverlayChild(int mx,int my)282 bool isMouseOverScreenOverlayChild(int mx, int my)
283 {
284 if (psMouseOverWidgetScreen != nullptr)
285 {
286 return isScreenARegisteredOverlay(psMouseOverWidgetScreen);
287 }
288 bool bMouseIsOverOverlayChild = false;
289 if (auto mouseOverWidget = psMouseOverWidget.lock())
290 {
291 // Fallback - the mouse is over a widget, but the widget does not have a screen pointer
292 // Hit-test each overlay screen to identify if the mouse is over one
293 // (Note: This can currently occur with DropdownWidget members.)
294 forEachOverlayScreen([mx, my, &bMouseIsOverOverlayChild](const OverlayScreen& overlay)
295 {
296 auto psRoot = overlay.psScreen->psForm;
297
298 // Hit-test all children of root form
299 for (const auto &psCurr: psRoot->children())
300 {
301 if (!psCurr->visible() || !psCurr->hitTest(mx, my))
302 {
303 continue; // Skip any hidden widgets, or widgets the click missed.
304 }
305
306 // hit test succeeded for child widget
307 bMouseIsOverOverlayChild = true;
308 return false; // stop enumerating
309 }
310 return true; // keep enumerating
311 });
312 }
313 return bMouseIsOverOverlayChild;
314 }
315
isMouseClickDownOnScreenOverlayChild()316 bool isMouseClickDownOnScreenOverlayChild()
317 {
318 if (!psClickDownWidgetScreen) { return false; }
319 return isScreenARegisteredOverlay(psClickDownWidgetScreen);
320 }
321
deleteOldWidgets()322 static void deleteOldWidgets()
323 {
324 while (!widgetDeletionQueue.empty())
325 {
326 std::shared_ptr<WIDGET> guiltyWidget = widgetDeletionQueue.front();
327 widgDelete(guiltyWidget.get());
328 widgetDeletionQueue.pop_front();
329 }
330 }
331
W_INIT()332 W_INIT::W_INIT()
333 : formID(0)
334 , majorID(0)
335 , id(0)
336 , style(0)
337 , x(0), y(0)
338 , width(0), height(0)
339 , pDisplay(nullptr)
340 , pCallback(nullptr)
341 , pUserData(nullptr)
342 , UserData(0)
343 , calcLayout(nullptr)
344 , onDelete(nullptr)
345 , customHitTest(nullptr)
346 , initPUserDataFunc(nullptr)
347 {}
348
WIDGET(W_INIT const * init,WIDGET_TYPE type)349 WIDGET::WIDGET(W_INIT const *init, WIDGET_TYPE type)
350 : id(init->id)
351 , type(type)
352 , style(init->style)
353 , displayFunction(init->pDisplay)
354 , callback(init->pCallback)
355 , pUserData(init->pUserData)
356 , UserData(init->UserData)
357 , calcLayout(init->calcLayout)
358 , onDelete(init->onDelete)
359 , customHitTest(init->customHitTest)
360 , dim(init->x, init->y, init->width, init->height)
361 , dirty(true)
362 {
363 /* Initialize and set the pUserData if necessary */
364 if (init->initPUserDataFunc != nullptr)
365 {
366 assert(pUserData == nullptr); // if the initPUserDataFunc is set, pUserData should not be already set
367 pUserData = init->initPUserDataFunc();
368 }
369
370 // if calclayout is not null, call it
371 callCalcLayout();
372
373 #ifdef DEBUG
374 debugLiveWidgets.insert(this);
375 #endif
376 }
377
WIDGET(WIDGET_TYPE type)378 WIDGET::WIDGET(WIDGET_TYPE type)
379 : id(0xFFFFEEEEu)
380 , type(type)
381 , style(0)
382 , displayFunction(nullptr)
383 , callback(nullptr)
384 , pUserData(nullptr)
385 , UserData(0)
386 , calcLayout(nullptr)
387 , onDelete(nullptr)
388 , customHitTest(nullptr)
389 , dim(0, 0, 1, 1)
390 , dirty(true)
391 {
392 #ifdef DEBUG
393 debugLiveWidgets.insert(this);
394 #endif
395 }
396
~WIDGET()397 WIDGET::~WIDGET()
398 {
399 if (onDelete != nullptr)
400 {
401 onDelete(this); // Call the onDelete function to handle any extra logic
402 }
403
404 tipStop(this); // Stop showing tooltip, if we are.
405
406 #ifdef DEBUG
407 debugLiveWidgets.erase(this);
408 #endif
409 }
410
deleteLater()411 void WIDGET::deleteLater()
412 {
413 auto shared_widget_ptr = shared_from_this();
414 ASSERT_OR_RETURN(, std::find(widgetDeletionQueue.begin(), widgetDeletionQueue.end(), shared_widget_ptr) == widgetDeletionQueue.end(), "Called deleteLater() twice on the same widget.");
415 widgetDeletionQueue.push_back(shared_widget_ptr);
416 }
417
setGeometry(WzRect const & r)418 void WIDGET::setGeometry(WzRect const &r)
419 {
420 if (dim == r)
421 {
422 return; // Nothing to do.
423 }
424 dim = r;
425 geometryChanged();
426 dirty = true;
427 }
428
screenSizeDidChange(int oldWidth,int oldHeight,int newWidth,int newHeight)429 void WIDGET::screenSizeDidChange(int oldWidth, int oldHeight, int newWidth, int newHeight)
430 {
431 // Default implementation of screenSizeDidChange calls its own calcLayout callback function (if present)
432 callCalcLayout();
433
434 // Then propagates the event to all children
435 for (auto const &psCurr: childWidgets)
436 {
437 psCurr->screenSizeDidChange(oldWidth, oldHeight, newWidth, newHeight);
438 }
439 }
440
setCalcLayout(const WIDGET_CALCLAYOUT_FUNC & calcLayoutFunc)441 void WIDGET::setCalcLayout(const WIDGET_CALCLAYOUT_FUNC& calcLayoutFunc)
442 {
443 calcLayout = calcLayoutFunc;
444 callCalcLayout();
445 }
446
callCalcLayout()447 void WIDGET::callCalcLayout()
448 {
449 if (calcLayout)
450 {
451 calcLayout(this, screenWidth, screenHeight, screenWidth, screenHeight);
452 }
453 #ifdef DEBUG
454 // // FOR DEBUGGING:
455 // // To help track down WIDGETs missing a calc layout function
456 // // (because something isn't properly re-adjusting when live window resizing occurs)
457 // // uncomment the else branch below.
458 // else
459 // {
460 // debug(LOG_ERROR, "Object is missing calc layout function");
461 // }
462 #endif
463 }
464
setOnDelete(const WIDGET_ONDELETE_FUNC & onDeleteFunc)465 void WIDGET::setOnDelete(const WIDGET_ONDELETE_FUNC& onDeleteFunc)
466 {
467 onDelete = onDeleteFunc;
468 }
469
setCustomHitTest(const WIDGET_HITTEST_FUNC & newCustomHitTestFunc)470 void WIDGET::setCustomHitTest(const WIDGET_HITTEST_FUNC& newCustomHitTestFunc)
471 {
472 customHitTest = newCustomHitTestFunc;
473 }
474
attach(const std::shared_ptr<WIDGET> & widget)475 void WIDGET::attach(const std::shared_ptr<WIDGET> &widget)
476 {
477 ASSERT_OR_RETURN(, widget != nullptr && widget->parentWidget.expired(), "Bad attach.");
478 widget->parentWidget = shared_from_this();
479 widget->setScreenPointer(screenPointer.lock());
480 childWidgets.push_back(widget);
481 }
482
detach(const std::shared_ptr<WIDGET> & widget)483 void WIDGET::detach(const std::shared_ptr<WIDGET> &widget)
484 {
485 ASSERT_OR_RETURN(, widget != nullptr && !widget->parentWidget.expired(), "Bad detach.");
486
487 widget->parentWidget.reset();
488 widget->setScreenPointer(nullptr);
489 childWidgets.erase(std::find(childWidgets.begin(), childWidgets.end(), widget));
490
491 widgetLost(widget.get());
492 }
493
setScreenPointer(const std::shared_ptr<W_SCREEN> & screen)494 void WIDGET::setScreenPointer(const std::shared_ptr<W_SCREEN> &screen)
495 {
496 if (screenPointer.lock() == screen)
497 {
498 return;
499 }
500
501 if (isMouseOverWidget())
502 {
503 psMouseOverWidget.reset();
504 }
505
506 if (auto lockedScreen = screenPointer.lock())
507 {
508 if (lockedScreen->hasFocus(*this))
509 {
510 lockedScreen->psFocus.reset();
511 }
512 if (lockedScreen->isLastHighlight(*this))
513 {
514 lockedScreen->lastHighlight.reset();
515 }
516 }
517
518 screenPointer = screen;
519 for (auto const &child: childWidgets)
520 {
521 child->setScreenPointer(screen);
522 }
523 }
524
widgetLost(WIDGET * widget)525 void WIDGET::widgetLost(WIDGET *widget)
526 {
527 if (auto lockedParent = parentWidget.lock())
528 {
529 lockedParent->widgetLost(widget); // We don't care about the lost widget, maybe the parent does. (Special code for W_TABFORM.)
530 }
531 }
532
~W_SCREEN()533 W_SCREEN::~W_SCREEN()
534 {
535 if (auto focusedWidget = psFocus.lock())
536 {
537 // must trigger a resignation of focus
538 focusedWidget->focusLost();
539 }
540 psFocus.reset();
541 }
542
initialize()543 void W_SCREEN::initialize()
544 {
545 W_FORMINIT sInit;
546 sInit.id = 0;
547 sInit.style = WFORM_PLAIN | WFORM_INVISIBLE;
548 sInit.x = 0;
549 sInit.y = 0;
550 sInit.width = screenWidth - 1;
551 sInit.height = screenHeight - 1;
552
553 psForm = std::make_shared<W_FORM>(&sInit);
554 psForm->screenPointer = shared_from_this();
555 }
556
screenSizeDidChange(unsigned int oldWidth,unsigned int oldHeight,unsigned int newWidth,unsigned int newHeight)557 void W_SCREEN::screenSizeDidChange(unsigned int oldWidth, unsigned int oldHeight, unsigned int newWidth, unsigned int newHeight)
558 {
559 // resize the top-level form
560 psForm->setGeometry(0, 0, screenWidth - 1, screenHeight - 1);
561
562 // inform the top-level form of the event
563 psForm->screenSizeDidChange(oldWidth, oldHeight, newWidth, newHeight);
564 }
565
566 /* Check whether an ID has been used on a form */
widgCheckIDForm(WIDGET * psForm,UDWORD id)567 static bool widgCheckIDForm(WIDGET *psForm, UDWORD id)
568 {
569 if (psForm->id == id)
570 {
571 return true;
572 }
573 for (auto const &child: psForm->children())
574 {
575 if (widgCheckIDForm(child.get(), id))
576 {
577 return true;
578 }
579 }
580 return false;
581 }
582
widgAddWidget(const std::shared_ptr<W_SCREEN> & psScreen,W_INIT const * psInit,const std::shared_ptr<WIDGET> & widget)583 static bool widgAddWidget(const std::shared_ptr<W_SCREEN> &psScreen, W_INIT const *psInit, const std::shared_ptr<WIDGET> &widget)
584 {
585 ASSERT_OR_RETURN(false, widget != nullptr, "Invalid widget");
586 ASSERT_OR_RETURN(false, psScreen != nullptr, "Invalid screen pointer");
587 ASSERT_OR_RETURN(false, !widgCheckIDForm(psScreen->psForm.get(), psInit->id), "ID number has already been used (%d)", psInit->id);
588 // Find the form to add the widget to.
589 W_FORM *psParent;
590 if (psInit->formID == 0)
591 {
592 /* Add to the base form */
593 psParent = psScreen->psForm.get();
594 }
595 else
596 {
597 psParent = (W_FORM *)widgGetFromID(psScreen, psInit->formID);
598 }
599 ASSERT_OR_RETURN(false, psParent != nullptr && psParent->type == WIDG_FORM, "Could not find parent form from formID");
600
601 psParent->attach(widget);
602 return true;
603 }
604
605 /* Add a form to the widget screen */
widgAddForm(const std::shared_ptr<W_SCREEN> & psScreen,const W_FORMINIT * psInit)606 W_FORM *widgAddForm(const std::shared_ptr<W_SCREEN> &psScreen, const W_FORMINIT *psInit)
607 {
608 ASSERT_OR_RETURN(nullptr, psScreen != nullptr, "Invalid screen pointer");
609 std::shared_ptr<W_FORM> psForm = nullptr;
610 if (psInit->style & WFORM_CLICKABLE)
611 {
612 psForm = std::make_shared<W_CLICKFORM>(psInit);
613 }
614 else
615 {
616 psForm = std::make_shared<W_FORM>(psInit);
617 }
618
619 return widgAddWidget(psScreen, psInit, psForm) ? psForm.get() : nullptr;
620 }
621
622 /* Add a label to the widget screen */
widgAddLabel(const std::shared_ptr<W_SCREEN> & psScreen,const W_LABINIT * psInit)623 W_LABEL *widgAddLabel(const std::shared_ptr<W_SCREEN> &psScreen, const W_LABINIT *psInit)
624 {
625 ASSERT_OR_RETURN(nullptr, psScreen != nullptr, "Invalid screen pointer");
626 auto psLabel = std::make_shared<W_LABEL>(psInit);
627 return widgAddWidget(psScreen, psInit, psLabel) ? psLabel.get() : nullptr;
628 }
629
630 /* Add a button to the widget screen */
widgAddButton(const std::shared_ptr<W_SCREEN> & psScreen,const W_BUTINIT * psInit)631 W_BUTTON *widgAddButton(const std::shared_ptr<W_SCREEN> &psScreen, const W_BUTINIT *psInit)
632 {
633 ASSERT_OR_RETURN(nullptr, psScreen != nullptr, "Invalid screen pointer");
634 auto psButton = std::make_shared<W_BUTTON>(psInit);
635 return widgAddWidget(psScreen, psInit, psButton) ? psButton.get() : nullptr;
636 }
637
638 /* Add an edit box to the widget screen */
widgAddEditBox(const std::shared_ptr<W_SCREEN> & psScreen,const W_EDBINIT * psInit)639 W_EDITBOX *widgAddEditBox(const std::shared_ptr<W_SCREEN> &psScreen, const W_EDBINIT *psInit)
640 {
641 ASSERT_OR_RETURN(nullptr, psScreen != nullptr, "Invalid screen pointer");
642 auto psEdBox = std::make_shared<W_EDITBOX>(psInit);
643 return widgAddWidget(psScreen, psInit, psEdBox) ? psEdBox.get() : nullptr;
644 }
645
646 /* Add a bar graph to the widget screen */
widgAddBarGraph(const std::shared_ptr<W_SCREEN> & psScreen,const W_BARINIT * psInit)647 W_BARGRAPH *widgAddBarGraph(const std::shared_ptr<W_SCREEN> &psScreen, const W_BARINIT *psInit)
648 {
649 ASSERT_OR_RETURN(nullptr, psScreen != nullptr, "Invalid screen pointer");
650 auto psBarGraph = std::make_shared<W_BARGRAPH>(psInit);
651 return widgAddWidget(psScreen, psInit, psBarGraph) ? psBarGraph.get() : nullptr;
652 }
653
654 /* Add a slider to a form */
widgAddSlider(const std::shared_ptr<W_SCREEN> & psScreen,const W_SLDINIT * psInit)655 W_SLIDER *widgAddSlider(const std::shared_ptr<W_SCREEN> &psScreen, const W_SLDINIT *psInit)
656 {
657 ASSERT_OR_RETURN(nullptr, psScreen != nullptr, "Invalid screen pointer");
658 auto psSlider = std::make_shared<W_SLIDER>(psInit);
659 return widgAddWidget(psScreen, psInit, psSlider) ? psSlider.get() : nullptr;
660 }
661
662 /* Delete a widget from the screen */
widgDelete(WIDGET * widget)663 void widgDelete(WIDGET *widget)
664 {
665 if (!widget)
666 {
667 return;
668 }
669 widgDebugAssertIfRunningScreen();
670
671 if (auto lockedScreen = widget->screenPointer.lock())
672 {
673 if (auto widgetWithFocus = lockedScreen->getWidgetWithFocus())
674 {
675 if (widgetWithFocus->hasAncestor(widget))
676 {
677 // must trigger a resignation of focus
678 lockedScreen->setFocus(nullptr);
679 }
680 }
681 if (lockedScreen->psForm.get() == widget)
682 {
683 lockedScreen->psForm = nullptr;
684 }
685 }
686
687 if (auto parent = widget->parent())
688 {
689 parent->detach(widget);
690 }
691 }
692
693 /* Delete a widget from the screen */
widgDelete(const std::shared_ptr<W_SCREEN> & psScreen,UDWORD id)694 void widgDelete(const std::shared_ptr<W_SCREEN> &psScreen, UDWORD id)
695 {
696 ASSERT_OR_RETURN(, psScreen != nullptr, "Invalid screen pointer");
697 widgDelete(widgGetFromID(psScreen, id));
698 }
widgDeleteLater(const std::shared_ptr<W_SCREEN> & psScreen,UDWORD id)699 void widgDeleteLater(const std::shared_ptr<W_SCREEN> &psScreen, UDWORD id)
700 {
701 ASSERT_OR_RETURN(, psScreen != nullptr, "Invalid screen pointer");
702 auto psWidget = widgGetFromID(psScreen, id);
703 if (psWidget)
704 {
705 psWidget->deleteLater();
706 }
707 }
708
709 /* Find a widget on a form from its id number */
widgFormGetFromID(const std::shared_ptr<WIDGET> & widget,UDWORD id)710 std::shared_ptr<WIDGET> widgFormGetFromID(const std::shared_ptr<WIDGET>& widget, UDWORD id)
711 {
712 if (widget->id == id)
713 {
714 return widget->shared_from_this();
715 }
716 for (auto const &child: widget->children())
717 {
718 std::shared_ptr<WIDGET> w = widgFormGetFromID(child, id);
719 if (w != nullptr)
720 {
721 return w;
722 }
723 }
724 return nullptr;
725 }
726
727 /* Find a widget in a screen from its ID number */
widgGetFromID(const std::shared_ptr<W_SCREEN> & psScreen,UDWORD id)728 WIDGET *widgGetFromID(const std::shared_ptr<W_SCREEN> &psScreen, UDWORD id)
729 {
730 ASSERT_OR_RETURN(nullptr, psScreen != nullptr, "Invalid screen pointer");
731 return widgFormGetFromID(psScreen->psForm, id).get();
732 }
733
widgHide(const std::shared_ptr<W_SCREEN> & psScreen,UDWORD id)734 void widgHide(const std::shared_ptr<W_SCREEN> &psScreen, UDWORD id)
735 {
736 ASSERT_OR_RETURN(, psScreen != nullptr, "Invalid screen pointer");
737 WIDGET *psWidget = widgGetFromID(psScreen, id);
738 ASSERT_OR_RETURN(, psWidget != nullptr, "Couldn't find widget from id.");
739
740 psWidget->hide();
741 }
742
widgReveal(const std::shared_ptr<W_SCREEN> & psScreen,UDWORD id)743 void widgReveal(const std::shared_ptr<W_SCREEN> &psScreen, UDWORD id)
744 {
745 ASSERT_OR_RETURN(, psScreen != nullptr, "Invalid screen pointer");
746 WIDGET *psWidget = widgGetFromID(psScreen, id);
747 ASSERT_OR_RETURN(, psWidget != nullptr, "Couldn't find widget from id.");
748
749 psWidget->show();
750 }
751
752 /* Return the ID of the widget the mouse was over this frame */
widgGetMouseOver(const std::shared_ptr<W_SCREEN> & psScreen)753 UDWORD widgGetMouseOver(const std::shared_ptr<W_SCREEN> &psScreen)
754 {
755 ASSERT_OR_RETURN(0, psScreen != nullptr, "Invalid screen pointer");
756 /* Don't actually need the screen parameter at the moment - but it might be
757 handy if psMouseOverWidget needs to stop being a static and moves into
758 the screen structure */
759
760 if (auto lockedMouserOverWidget = psMouseOverWidget.lock())
761 {
762 return lockedMouserOverWidget->id;
763 }
764
765 return 0;
766 }
767
isMouseOverSomeWidget(const std::shared_ptr<W_SCREEN> & psScreen)768 bool isMouseOverSomeWidget(const std::shared_ptr<W_SCREEN> &psScreen)
769 {
770 if (auto lockedMouserOverWidget = psMouseOverWidget.lock())
771 {
772 return lockedMouserOverWidget != psScreen->psForm;
773 }
774
775 return false;
776 }
777
778 /* Return the user data for a widget */
widgGetUserData(const std::shared_ptr<W_SCREEN> & psScreen,UDWORD id)779 void *widgGetUserData(const std::shared_ptr<W_SCREEN> &psScreen, UDWORD id)
780 {
781 ASSERT_OR_RETURN(nullptr, psScreen != nullptr, "Invalid screen pointer");
782 WIDGET *psWidget;
783
784 psWidget = widgGetFromID(psScreen, id);
785 if (psWidget)
786 {
787 return psWidget->pUserData;
788 }
789
790 return nullptr;
791 }
792
793
794 /* Return the user data for a widget */
widgGetUserData2(const std::shared_ptr<W_SCREEN> & psScreen,UDWORD id)795 UDWORD widgGetUserData2(const std::shared_ptr<W_SCREEN> &psScreen, UDWORD id)
796 {
797 ASSERT_OR_RETURN(0, psScreen != nullptr, "Invalid screen pointer");
798 WIDGET *psWidget;
799
800 psWidget = widgGetFromID(psScreen, id);
801 if (psWidget)
802 {
803 return psWidget->UserData;
804 }
805
806 return 0;
807 }
808
809
810 /* Set user data for a widget */
widgSetUserData(const std::shared_ptr<W_SCREEN> & psScreen,UDWORD id,void * UserData)811 void widgSetUserData(const std::shared_ptr<W_SCREEN> &psScreen, UDWORD id, void *UserData)
812 {
813 ASSERT_OR_RETURN(, psScreen != nullptr, "Invalid screen pointer");
814 WIDGET *psWidget;
815
816 psWidget = widgGetFromID(psScreen, id);
817 if (psWidget)
818 {
819 psWidget->pUserData = UserData;
820 }
821 }
822
823 /* Set user data for a widget */
widgSetUserData2(const std::shared_ptr<W_SCREEN> & psScreen,UDWORD id,UDWORD UserData)824 void widgSetUserData2(const std::shared_ptr<W_SCREEN> &psScreen, UDWORD id, UDWORD UserData)
825 {
826 ASSERT_OR_RETURN(, psScreen != nullptr, "Invalid screen pointer");
827 WIDGET *psWidget;
828
829 psWidget = widgGetFromID(psScreen, id);
830 if (psWidget)
831 {
832 psWidget->UserData = UserData;
833 }
834 }
835
setTip(std::string)836 void WIDGET::setTip(std::string)
837 {
838 ASSERT(false, "Can't set widget type %u's tip.", type);
839 }
840
841 /* Set tip string for a widget */
widgSetTip(const std::shared_ptr<W_SCREEN> & psScreen,UDWORD id,std::string pTip)842 void widgSetTip(const std::shared_ptr<W_SCREEN> &psScreen, UDWORD id, std::string pTip)
843 {
844 ASSERT_OR_RETURN(, psScreen != nullptr, "Invalid screen pointer");
845 WIDGET *psWidget = widgGetFromID(psScreen, id);
846
847 if (!psWidget)
848 {
849 return;
850 }
851
852 psWidget->setTip(std::move(pTip));
853 }
854
855 /* Return which key was used to press the last returned widget */
widgGetButtonKey_DEPRECATED(const std::shared_ptr<W_SCREEN> & psScreen)856 UDWORD widgGetButtonKey_DEPRECATED(const std::shared_ptr<W_SCREEN> &psScreen)
857 {
858 ASSERT_OR_RETURN(lastReleasedKey_DEPRECATED, psScreen != nullptr, "Invalid screen pointer");
859 /* Don't actually need the screen parameter at the moment - but it might be
860 handy if released needs to stop being a static and moves into
861 the screen structure */
862
863 return lastReleasedKey_DEPRECATED;
864 }
865
getState()866 unsigned WIDGET::getState()
867 {
868 ASSERT(false, "Can't get widget type %u's state.", type);
869 return 0;
870 }
871
872 /* Get a button or clickable form's state */
widgGetButtonState(const std::shared_ptr<W_SCREEN> & psScreen,UDWORD id)873 UDWORD widgGetButtonState(const std::shared_ptr<W_SCREEN> &psScreen, UDWORD id)
874 {
875 ASSERT_OR_RETURN(0, psScreen != nullptr, "Invalid screen pointer");
876 /* Get the button */
877 WIDGET *psWidget = widgGetFromID(psScreen, id);
878 ASSERT_OR_RETURN(0, psWidget, "Couldn't find widget by ID %u", id);
879
880 return psWidget->getState();
881 }
882
setFlash(bool)883 void WIDGET::setFlash(bool)
884 {
885 ASSERT(false, "Can't set widget type %u's flash.", type);
886 }
887
widgSetButtonFlash(const std::shared_ptr<W_SCREEN> & psScreen,UDWORD id)888 void widgSetButtonFlash(const std::shared_ptr<W_SCREEN> &psScreen, UDWORD id)
889 {
890 ASSERT_OR_RETURN(, psScreen != nullptr, "Invalid screen pointer");
891 /* Get the button */
892 WIDGET *psWidget = widgGetFromID(psScreen, id);
893 ASSERT_OR_RETURN(, psWidget, "Couldn't find widget by ID %u", id);
894
895 psWidget->setFlash(true);
896 }
897
widgClearButtonFlash(const std::shared_ptr<W_SCREEN> & psScreen,UDWORD id)898 void widgClearButtonFlash(const std::shared_ptr<W_SCREEN> &psScreen, UDWORD id)
899 {
900 ASSERT_OR_RETURN(, psScreen != nullptr, "Invalid screen pointer");
901 /* Get the button */
902 WIDGET *psWidget = widgGetFromID(psScreen, id);
903 ASSERT_OR_RETURN(, psWidget, "Couldn't find widget by ID %u", id);
904
905 psWidget->setFlash(false);
906 }
907
908
setState(unsigned)909 void WIDGET::setState(unsigned)
910 {
911 ASSERT(false, "Can't set widget type %u's state.", type);
912 }
913
914 /* Set a button or clickable form's state */
widgSetButtonState(const std::shared_ptr<W_SCREEN> & psScreen,UDWORD id,UDWORD state)915 void widgSetButtonState(const std::shared_ptr<W_SCREEN> &psScreen, UDWORD id, UDWORD state)
916 {
917 ASSERT_OR_RETURN(, psScreen != nullptr, "Invalid screen pointer");
918 /* Get the button */
919 WIDGET *psWidget = widgGetFromID(psScreen, id);
920 ASSERT_OR_RETURN(, psWidget, "Couldn't find widget by ID %u", id);
921
922 psWidget->setState(state);
923 }
924
getString() const925 WzString WIDGET::getString() const
926 {
927 ASSERT(false, "Can't get widget type %u's string.", type);
928 return WzString();
929 }
930
931 /* Return a pointer to a buffer containing the current string of a widget.
932 * NOTE: The string must be copied out of the buffer
933 */
widgGetString(const std::shared_ptr<W_SCREEN> & psScreen,UDWORD id)934 const char *widgGetString(const std::shared_ptr<W_SCREEN> &psScreen, UDWORD id)
935 {
936 ASSERT_OR_RETURN("", psScreen != nullptr, "Invalid screen pointer");
937 const WIDGET *psWidget = widgGetFromID(psScreen, id);
938 ASSERT_OR_RETURN("", psWidget, "Couldn't find widget by ID %u", id);
939
940 static WzString ret; // Must be static so it isn't immediately freed when this function returns.
941 ret = psWidget->getString();
942 return ret.toUtf8().c_str();
943 }
944
widgGetWzString(const std::shared_ptr<W_SCREEN> & psScreen,UDWORD id)945 const WzString& widgGetWzString(const std::shared_ptr<W_SCREEN> &psScreen, UDWORD id)
946 {
947 static WzString emptyString = WzString();
948 ASSERT_OR_RETURN(emptyString, psScreen != nullptr, "Invalid screen pointer");
949 const WIDGET *psWidget = widgGetFromID(psScreen, id);
950 ASSERT_OR_RETURN(emptyString, psWidget, "Couldn't find widget by ID %u", id);
951
952 static WzString ret; // Must be static so it isn't immediately freed when this function returns.
953 ret = psWidget->getString();
954 return ret;
955 }
956
setString(WzString)957 void WIDGET::setString(WzString)
958 {
959 ASSERT(false, "Can't set widget type %u's string.", type);
960 }
961
962 /* Set the text in a widget */
widgSetString(const std::shared_ptr<W_SCREEN> & psScreen,UDWORD id,const char * pText)963 void widgSetString(const std::shared_ptr<W_SCREEN> &psScreen, UDWORD id, const char *pText)
964 {
965 ASSERT_OR_RETURN(, psScreen != nullptr, "Invalid screen pointer");
966 /* Get the widget */
967 WIDGET *psWidget = widgGetFromID(psScreen, id);
968 ASSERT_OR_RETURN(, psWidget, "Couldn't find widget by ID %u", id);
969
970 if (psWidget->type == WIDG_EDITBOX && psScreen->hasFocus(*psWidget))
971 {
972 psScreen->setFocus(nullptr);
973 }
974
975 psWidget->setString(WzString::fromUtf8(pText));
976 }
977
processCallbacksRecursive(W_CONTEXT * psContext)978 void WIDGET::processCallbacksRecursive(W_CONTEXT *psContext)
979 {
980 /* Go through all the widgets on the form */
981 for (auto const &psCurr: childWidgets)
982 {
983 /* Call the callback */
984 if (psCurr->callback)
985 {
986 psCurr->callback(psCurr.get(), psContext);
987 }
988
989 /* and then recurse */
990 W_CONTEXT sFormContext(psContext);
991 sFormContext.mx = psContext->mx - psCurr->x();
992 sFormContext.my = psContext->my - psCurr->y();
993 sFormContext.xOffset = psContext->xOffset + psCurr->x();
994 sFormContext.yOffset = psContext->yOffset + psCurr->y();
995 psCurr->processCallbacksRecursive(&sFormContext);
996 }
997 }
998
999 /* Process all the widgets on a form.
1000 * mx and my are the coords of the mouse relative to the form origin.
1001 */
runRecursive(W_CONTEXT * psContext)1002 void WIDGET::runRecursive(W_CONTEXT *psContext)
1003 {
1004 /* Process the form's widgets */
1005 for (auto const &psCurr: childWidgets)
1006 {
1007 /* Skip any hidden widgets */
1008 if (!psCurr->visible())
1009 {
1010 continue;
1011 }
1012
1013 /* Found a sub form, so set up the context */
1014 W_CONTEXT sFormContext(psContext);
1015 sFormContext.mx = psContext->mx - psCurr->x();
1016 sFormContext.my = psContext->my - psCurr->y();
1017 sFormContext.xOffset = psContext->xOffset + psCurr->x();
1018 sFormContext.yOffset = psContext->yOffset + psCurr->y();
1019
1020 /* Process it */
1021 psCurr->runRecursive(&sFormContext);
1022
1023 /* Run the widget */
1024 psCurr->run(psContext);
1025 }
1026 }
1027
hitTest(int x,int y)1028 bool WIDGET::hitTest(int x, int y)
1029 {
1030 // default hit-testing bounding rect (based on the widget's x, y, width, height)
1031 bool hitTestResult = dim.contains(x, y);
1032
1033 if(customHitTest)
1034 {
1035 // if the default bounding-rect hit-test succeeded, use the custom hit-testing func
1036 hitTestResult = hitTestResult && customHitTest(this, x, y);
1037 }
1038
1039 return hitTestResult;
1040 }
1041
processClickRecursive(W_CONTEXT * psContext,WIDGET_KEY key,bool wasPressed)1042 bool WIDGET::processClickRecursive(W_CONTEXT *psContext, WIDGET_KEY key, bool wasPressed)
1043 {
1044 bool didProcessClick = false;
1045
1046 W_CONTEXT shiftedContext(psContext);
1047 shiftedContext.mx = psContext->mx - x();
1048 shiftedContext.my = psContext->my - y();
1049 shiftedContext.xOffset = psContext->xOffset + x();
1050 shiftedContext.yOffset = psContext->yOffset + y();
1051
1052 bool skipProcessingChildren = false;
1053 if (type == WIDG_FORM && ((W_FORM *)this)->disableChildren)
1054 {
1055 skipProcessingChildren = true;
1056 }
1057
1058 if (!skipProcessingChildren)
1059 {
1060 // Process subwidgets.
1061 for (auto const &psCurr: childWidgets)
1062 {
1063 if (!psCurr->visible() || !psCurr->hitTest(shiftedContext.mx, shiftedContext.my))
1064 {
1065 continue; // Skip any hidden widgets, or widgets the click missed.
1066 }
1067
1068 // Process it (recursively).
1069 didProcessClick = psCurr->processClickRecursive(&shiftedContext, key, wasPressed) || didProcessClick;
1070 }
1071 }
1072
1073 if (!visible())
1074 {
1075 // special case for root form
1076 // since the processClickRecursive of children is only called if they are visible
1077 // this should only trigger for the root form of a screen that's been set to invisible
1078 return didProcessClick;
1079 }
1080
1081 if (psMouseOverWidget.expired())
1082 {
1083 psMouseOverWidget = shared_from_this(); // Mark that the mouse is over a widget (if we haven't already).
1084 }
1085
1086 if (isMouseOverWidget())
1087 {
1088 if (auto lockedScreen = screenPointer.lock())
1089 {
1090 if (!lockedScreen->isLastHighlight(*this))
1091 {
1092 if (auto lockedLastHighligh = lockedScreen->lastHighlight.lock())
1093 {
1094 lockedLastHighligh->highlightLost();
1095 }
1096 lockedScreen->lastHighlight = shared_from_this(); // Mark that the mouse is over a widget (if we haven't already).
1097 highlight(psContext);
1098 }
1099 if (psMouseOverWidgetScreen != lockedScreen)
1100 {
1101 if (psMouseOverWidgetScreen)
1102 {
1103 // ensure the last mouseOverWidgetScreen receives highlightLost event
1104 if (auto lockedLastHighlight = psMouseOverWidgetScreen->lastHighlight.lock())
1105 {
1106 lockedLastHighlight->highlightLost();
1107 }
1108 psMouseOverWidgetScreen->lastHighlight.reset();
1109 }
1110 // update psMouseOverWidgetScreen
1111 psMouseOverWidgetScreen = lockedScreen;
1112 }
1113 }
1114 else
1115 {
1116 // Note: There are rare exceptions where this can happen, if a WIDGET is doing something funky like drawing widgets it hasn't attached.
1117 psMouseOverWidgetScreen = nullptr;
1118 }
1119
1120 if (key == WKEY_NONE)
1121 {
1122 // We're just checking mouse position, and this isn't a click, but return that we handled it
1123 return true;
1124 }
1125 }
1126
1127 if (key == WKEY_NONE)
1128 {
1129 return didProcessClick; // Just checking mouse position, not a click.
1130 }
1131
1132 if (isMouseOverWidget())
1133 {
1134 auto psClickedWidget = shared_from_this();
1135 if (isTransparentToClicks)
1136 {
1137 do {
1138 psClickedWidget = psClickedWidget->parent();
1139 } while (psClickedWidget != nullptr && psClickedWidget->isTransparentToClicks);
1140 if (psClickedWidget == nullptr)
1141 {
1142 return false;
1143 }
1144 }
1145 if (wasPressed)
1146 {
1147 psClickedWidget->clicked(psContext, key);
1148 psClickDownWidgetScreen = psClickedWidget->screenPointer.lock();
1149 }
1150 else
1151 {
1152 psClickedWidget->released(psContext, key);
1153 psClickDownWidgetScreen.reset();
1154 }
1155 didProcessClick = true;
1156 }
1157
1158 return didProcessClick;
1159 }
1160
1161
1162 /* Execute a set of widgets for one cycle.
1163 * Returns a list of activated widgets.
1164 */
widgRunScreen(const std::shared_ptr<W_SCREEN> & psScreen)1165 WidgetTriggers const &widgRunScreen(const std::shared_ptr<W_SCREEN> &psScreen)
1166 {
1167 static WidgetTriggers assertReturn;
1168 ASSERT_OR_RETURN(assertReturn, psScreen != nullptr, "Invalid screen pointer");
1169 psScreen->retWidgets.clear();
1170 cleanupDeletedOverlays();
1171
1172 /* Initialise the context */
1173 W_CONTEXT sContext = W_CONTEXT::ZeroContext();
1174 psMouseOverWidget.reset();
1175
1176 #ifdef DEBUG
1177 psCurrentlyRunningScreen = psScreen;
1178 #endif
1179
1180 // Note which keys have been pressed
1181 lastReleasedKey_DEPRECATED = WKEY_NONE;
1182 if (getWidgetsStatus())
1183 {
1184 MousePresses const &clicks = inputGetClicks();
1185 for (MousePresses::const_iterator c = clicks.begin(); c != clicks.end(); ++c)
1186 {
1187 WIDGET_KEY wkey;
1188 switch (c->key)
1189 {
1190 case MOUSE_LMB: wkey = WKEY_PRIMARY; break;
1191 case MOUSE_RMB: wkey = WKEY_SECONDARY; break;
1192 default: continue; // Who cares about other mouse buttons?
1193 }
1194 bool pressed;
1195 switch (c->action)
1196 {
1197 case MousePress::Press: pressed = true; break;
1198 case MousePress::Release: pressed = false; break;
1199 default: continue;
1200 }
1201 sContext.mx = c->pos.x;
1202 sContext.my = c->pos.y;
1203 bool didProcessClick = false;
1204 forEachOverlayScreen([&sContext, &didProcessClick, wkey, pressed](const OverlayScreen& overlay) -> bool
1205 {
1206 didProcessClick = overlay.psScreen->psForm->processClickRecursive(&sContext, wkey, pressed);
1207 return !didProcessClick;
1208 });
1209 if (!didProcessClick)
1210 {
1211 psScreen->psForm->processClickRecursive(&sContext, wkey, pressed);
1212 }
1213
1214 lastReleasedKey_DEPRECATED = wkey;
1215 }
1216 }
1217
1218 sContext.mx = mouseX();
1219 sContext.my = mouseY();
1220 bool didProcessClick = false;
1221 forEachOverlayScreen([&sContext, &didProcessClick](const OverlayScreen& overlay) -> bool
1222 {
1223 didProcessClick = overlay.psScreen->psForm->processClickRecursive(&sContext, WKEY_NONE, true); // Update highlights and psMouseOverWidget.
1224 return !didProcessClick;
1225 });
1226 if (!didProcessClick)
1227 {
1228 psScreen->psForm->processClickRecursive(&sContext, WKEY_NONE, true); // Update highlights and psMouseOverWidget.
1229 }
1230 if (psMouseOverWidget.lock() == nullptr)
1231 {
1232 psMouseOverWidgetScreen.reset();
1233 }
1234
1235 /* Process the screen's widgets */
1236 forEachOverlayScreen([&sContext](const OverlayScreen& overlay) -> bool
1237 {
1238 overlay.psScreen->psForm->runRecursive(&sContext);
1239 return true;
1240 });
1241 psScreen->psForm->runRecursive(&sContext);
1242
1243 #ifdef DEBUG
1244 psCurrentlyRunningScreen = nullptr;
1245 #endif
1246
1247 runScheduledTasks();
1248 deleteOldWidgets(); // Delete any widgets that called deleteLater() while being run.
1249
1250 /* Return the ID of a pressed button or finished edit box if any */
1251 return psScreen->retWidgets;
1252 }
1253
1254 /* Set the id number for widgRunScreen to return */
setReturn(const std::shared_ptr<WIDGET> & psWidget)1255 void W_SCREEN::setReturn(const std::shared_ptr<WIDGET> &psWidget)
1256 {
1257 WidgetTrigger trigger;
1258 trigger.widget = psWidget;
1259 retWidgets.push_back(trigger);
1260 }
1261
displayRecursive(WidgetGraphicsContext const & context)1262 void WIDGET::displayRecursive(WidgetGraphicsContext const &context)
1263 {
1264 if (context.clipContains(geometry())) {
1265 if (debugBoundingBoxesOnly)
1266 {
1267 // Display bounding boxes.
1268 PIELIGHT col;
1269 col.byte.r = 128 + iSinSR(realTime, 2000, 127); col.byte.g = 128 + iSinSR(realTime + 667, 2000, 127); col.byte.b = 128 + iSinSR(realTime + 1333, 2000, 127); col.byte.a = 128;
1270 iV_Box(context.getXOffset() + x(), context.getYOffset() + y(), context.getXOffset() + x() + width() - 1, context.getYOffset() + y() + height() - 1, col);
1271 }
1272 else if (displayFunction)
1273 {
1274 displayFunction(this, context.getXOffset(), context.getYOffset());
1275 }
1276 else
1277 {
1278 // Display widget.
1279 display(context.getXOffset(), context.getYOffset());
1280 }
1281 }
1282
1283 if (type == WIDG_FORM && ((W_FORM *)this)->disableChildren)
1284 {
1285 return;
1286 }
1287
1288 auto childrenContext = context.translatedBy(x(), y());
1289
1290 // If this is a clickable form, the widgets on it have to move when it's down.
1291 if (type == WIDG_FORM && (((W_FORM *)this)->style & WFORM_NOCLICKMOVE) == 0)
1292 {
1293 if ((((W_FORM *)this)->style & WFORM_CLICKABLE) != 0 &&
1294 (((W_CLICKFORM *)this)->state & (WBUT_DOWN | WBUT_LOCK | WBUT_CLICKLOCK)) != 0)
1295 {
1296 childrenContext = childrenContext.translatedBy(1, 1);
1297 }
1298 }
1299
1300 // Display the widgets on this widget.
1301 for (auto const &child: childWidgets)
1302 {
1303 if (child->visible())
1304 {
1305 child->displayRecursive(childrenContext);
1306 }
1307 }
1308 }
1309
1310 /* Display the screen's widgets in their current state
1311 * (Call after calling widgRunScreen, this allows the input
1312 * processing to be separated from the display of the widgets).
1313 */
widgDisplayScreen(const std::shared_ptr<W_SCREEN> & psScreen)1314 void widgDisplayScreen(const std::shared_ptr<W_SCREEN> &psScreen)
1315 {
1316 ASSERT_OR_RETURN(, psScreen != nullptr, "Invalid screen pointer");
1317 // To toggle debug bounding boxes: Press: Left Shift -- -- --------------
1318 // Left Ctrl ------------ -- -- ----
1319 static const int debugSequence[] = { -1, 0, 1, 3, 1, 3, 1, 3, 2, 3, 2, 3, 2, 3, 1, 0, -1};
1320 static int const *debugLoc = debugSequence;
1321 static bool debugBoundingBoxes = false;
1322 int debugCode = keyDown(KEY_LCTRL) + 2 * keyDown(KEY_LSHIFT);
1323 debugLoc = debugLoc[1] == -1 ? debugSequence : debugLoc[0] == debugCode ? debugLoc : debugLoc[1] == debugCode ? debugLoc + 1 : debugSequence;
1324 debugBoundingBoxes = debugBoundingBoxes ^ (debugLoc[1] == -1);
1325
1326 cleanupDeletedOverlays();
1327
1328 /* Process any user callback functions */
1329 W_CONTEXT sContext = W_CONTEXT::ZeroContext();
1330 sContext.mx = mouseX();
1331 sContext.my = mouseY();
1332 psScreen->psForm->processCallbacksRecursive(&sContext);
1333
1334 // Display the widgets.
1335 psScreen->psForm->displayRecursive();
1336
1337 // Always overlays on-top (i.e. draw them last)
1338 forEachOverlayScreenBottomUp([&sContext](const OverlayScreen& overlay) -> bool
1339 {
1340 overlay.psScreen->psForm->processCallbacksRecursive(&sContext);
1341 overlay.psScreen->psForm->displayRecursive();
1342 return true;
1343 }); // <- enumerate in *increasing* z-order for drawing
1344
1345 deleteOldWidgets(); // Delete any widgets that called deleteLater() while being displayed.
1346
1347 /* Display the tool tip if there is one */
1348 tipDisplay();
1349
1350 if (debugBoundingBoxes)
1351 {
1352 debugBoundingBoxesOnly = true;
1353 psScreen->psForm->displayRecursive();
1354 debugBoundingBoxesOnly = false;
1355 }
1356 }
1357
setFocus(const std::shared_ptr<WIDGET> & widget)1358 void W_SCREEN::setFocus(const std::shared_ptr<WIDGET> &widget)
1359 {
1360 if (auto locked = psFocus.lock())
1361 {
1362 locked->focusLost();
1363 }
1364 psFocus = widget;
1365 }
1366
WidgSetAudio(WIDGET_AUDIOCALLBACK Callback,SWORD HilightID,SWORD ClickedID,SWORD ErrorID)1367 void WidgSetAudio(WIDGET_AUDIOCALLBACK Callback, SWORD HilightID, SWORD ClickedID, SWORD ErrorID)
1368 {
1369 AudioCallback = Callback;
1370 HilightAudioID = HilightID;
1371 ClickedAudioID = ClickedID;
1372 ErrorAudioID = ErrorID;
1373 }
1374
WidgGetAudioCallback(void)1375 WIDGET_AUDIOCALLBACK WidgGetAudioCallback(void)
1376 {
1377 return AudioCallback;
1378 }
1379
WidgGetHilightAudioID(void)1380 SWORD WidgGetHilightAudioID(void)
1381 {
1382 return HilightAudioID;
1383 }
1384
WidgGetClickedAudioID(void)1385 SWORD WidgGetClickedAudioID(void)
1386 {
1387 return ClickedAudioID;
1388 }
1389
WidgGetErrorAudioID(void)1390 SWORD WidgGetErrorAudioID(void)
1391 {
1392 return ErrorAudioID;
1393 }
1394
setWidgetsStatus(bool var)1395 void setWidgetsStatus(bool var)
1396 {
1397 bWidgetsActive = var;
1398 }
1399
getWidgetsStatus()1400 bool getWidgetsStatus()
1401 {
1402 return bWidgetsActive;
1403 }
1404
clipContains(WzRect const & rect) const1405 bool WidgetGraphicsContext::clipContains(WzRect const& rect) const
1406 {
1407 return !clipped || clipRect.contains({offset.x + rect.x(), offset.y + rect.y(), rect.width(), rect.height()});
1408 }
1409
translatedBy(int32_t x,int32_t y) const1410 WidgetGraphicsContext WidgetGraphicsContext::translatedBy(int32_t x, int32_t y) const
1411 {
1412 WidgetGraphicsContext newContext(*this);
1413 newContext.offset.x += x;
1414 newContext.offset.y += y;
1415 return newContext;
1416 }
1417
clippedBy(WzRect const & newRect) const1418 WidgetGraphicsContext WidgetGraphicsContext::clippedBy(WzRect const &newRect) const
1419 {
1420 WidgetGraphicsContext newContext(*this);
1421 newContext.clipRect = {
1422 offset.x + newRect.x(),
1423 offset.y + newRect.y(),
1424 newRect.width(),
1425 newRect.height()
1426 };
1427
1428 if (clipped) {
1429 newContext.clipRect = newContext.clipRect.intersectionWith(clipRect);
1430 }
1431
1432 newContext.clipped = true;
1433
1434 return newContext;
1435 }
1436