1 /*****************************************************************************
2  * Copyright (c) 2014-2020 OpenRCT2 developers
3  *
4  * For a complete list of all authors, please refer to contributors.md
5  * Interested in contributing? Visit https://github.com/OpenRCT2/OpenRCT2
6  *
7  * OpenRCT2 is licensed under the GNU General Public License version 3.
8  *****************************************************************************/
9 
10 #include "Window.h"
11 
12 #include "../Context.h"
13 #include "../Editor.h"
14 #include "../Game.h"
15 #include "../Input.h"
16 #include "../OpenRCT2.h"
17 #include "../audio/audio.h"
18 #include "../config/Config.h"
19 #include "../core/Guard.hpp"
20 #include "../drawing/Drawing.h"
21 #include "../interface/Cursors.h"
22 #include "../localisation/Localisation.h"
23 #include "../localisation/StringIds.h"
24 #include "../platform/platform.h"
25 #include "../ride/RideAudio.h"
26 #include "../scenario/Scenario.h"
27 #include "../sprites.h"
28 #include "../ui/UiContext.h"
29 #include "../ui/WindowManager.h"
30 #include "../world/Map.h"
31 #include "Viewport.h"
32 #include "Widget.h"
33 #include "Window_internal.h"
34 
35 #include <algorithm>
36 #include <cmath>
37 #include <functional>
38 #include <iterator>
39 #include <list>
40 
41 std::list<std::shared_ptr<rct_window>> g_window_list;
42 rct_window* gWindowAudioExclusive;
43 
44 widget_identifier gCurrentTextBox = { { 255, 0 }, 0 };
45 char gTextBoxInput[TEXT_INPUT_SIZE] = { 0 };
46 int32_t gMaxTextBoxInputLength = 0;
47 int32_t gTextBoxFrameNo = 0;
48 bool gUsingWidgetTextBox = false;
49 TextInputSession* gTextInput;
50 
51 uint16_t gWindowUpdateTicks;
52 uint16_t gWindowMapFlashingFlags;
53 colour_t gCurrentWindowColours[4];
54 
55 // converted from uint16_t values at 0x009A41EC - 0x009A4230
56 // these are percentage coordinates of the viewport to centre to, if a window is obscuring a location, the next is tried
57 // clang-format off
58 static constexpr const float window_scroll_locations[][2] = {
59     { 0.5f, 0.5f },
60     { 0.75f, 0.5f },
61     { 0.25f, 0.5f },
62     { 0.5f, 0.75f },
63     { 0.5f, 0.25f },
64     { 0.75f, 0.75f },
65     { 0.75f, 0.25f },
66     { 0.25f, 0.75f },
67     { 0.25f, 0.25f },
68     { 0.125f, 0.5f },
69     { 0.875f, 0.5f },
70     { 0.5f, 0.125f },
71     { 0.5f, 0.875f },
72     { 0.875f, 0.125f },
73     { 0.875f, 0.875f },
74     { 0.125f, 0.875f },
75     { 0.125f, 0.125f },
76 };
77 // clang-format on
78 
79 namespace WindowCloseFlags
80 {
81     static constexpr uint32_t None = 0;
82     static constexpr uint32_t IterateReverse = (1 << 0);
83     static constexpr uint32_t CloseSingle = (1 << 1);
84 } // namespace WindowCloseFlags
85 
86 static void window_draw_core(rct_drawpixelinfo* dpi, rct_window* w, int32_t left, int32_t top, int32_t right, int32_t bottom);
87 static void window_draw_single(rct_drawpixelinfo* dpi, rct_window* w, int32_t left, int32_t top, int32_t right, int32_t bottom);
88 
window_get_iterator(const rct_window * w)89 std::list<std::shared_ptr<rct_window>>::iterator window_get_iterator(const rct_window* w)
90 {
91     return std::find_if(g_window_list.begin(), g_window_list.end(), [w](const std::shared_ptr<rct_window>& w2) -> bool {
92         return w == w2.get();
93     });
94 }
95 
window_visit_each(std::function<void (rct_window *)> func)96 void window_visit_each(std::function<void(rct_window*)> func)
97 {
98     auto windowList = g_window_list;
99     for (auto& w : windowList)
100     {
101         func(w.get());
102     }
103 }
104 
105 /**
106  *
107  *  rct2: 0x006ED7B0
108  */
window_dispatch_update_all()109 void window_dispatch_update_all()
110 {
111     // gTooltipNotShownTicks++;
112     window_visit_each([&](rct_window* w) { window_event_update_call(w); });
113 }
114 
window_update_all_viewports()115 void window_update_all_viewports()
116 {
117     window_visit_each([&](rct_window* w) {
118         if (w->viewport != nullptr && window_is_visible(w))
119         {
120             viewport_update_position(w);
121         }
122     });
123 }
124 
125 /**
126  *
127  *  rct2: 0x006E77A1
128  */
window_update_all()129 void window_update_all()
130 {
131     // window_update_all_viewports();
132 
133     // 1000 tick update
134     gWindowUpdateTicks += gCurrentDeltaTime;
135     if (gWindowUpdateTicks >= 1000)
136     {
137         gWindowUpdateTicks = 0;
138 
139         window_visit_each([](rct_window* w) { window_event_periodic_update_call(w); });
140     }
141 
142     // Border flash invalidation
143     window_visit_each([](rct_window* w) {
144         if (w->flags & WF_WHITE_BORDER_MASK)
145         {
146             w->flags -= WF_WHITE_BORDER_ONE;
147             if (!(w->flags & WF_WHITE_BORDER_MASK))
148             {
149                 w->Invalidate();
150             }
151         }
152     });
153 
154     auto windowManager = OpenRCT2::GetContext()->GetUiContext()->GetWindowManager();
155     windowManager->UpdateMouseWheel();
156 }
157 
window_close_surplus(int32_t cap,int8_t avoid_classification)158 static void window_close_surplus(int32_t cap, int8_t avoid_classification)
159 {
160     // find the amount of windows that are currently open
161     auto count = static_cast<int32_t>(g_window_list.size());
162     // difference between amount open and cap = amount to close
163     auto diff = count - WINDOW_LIMIT_RESERVED - cap;
164     for (auto i = 0; i < diff; i++)
165     {
166         // iterates through the list until it finds the newest window, or a window that can be closed
167         rct_window* foundW{};
168         for (auto& w : g_window_list)
169         {
170             if (!(w->flags & (WF_STICK_TO_BACK | WF_STICK_TO_FRONT | WF_NO_AUTO_CLOSE)))
171             {
172                 foundW = w.get();
173                 break;
174             }
175         }
176         // skip window if window matches specified rct_windowclass (as user may be modifying via options)
177         if (avoid_classification != -1 && foundW != nullptr && foundW->classification == avoid_classification)
178         {
179             continue;
180         }
181         window_close(foundW);
182     }
183 }
184 
185 /*
186  * Changes the maximum amount of windows allowed
187  */
window_set_window_limit(int32_t value)188 void window_set_window_limit(int32_t value)
189 {
190     int32_t prev = gConfigGeneral.window_limit;
191     int32_t val = std::clamp(value, WINDOW_LIMIT_MIN, WINDOW_LIMIT_MAX);
192     gConfigGeneral.window_limit = val;
193     config_save_default();
194     // Checks if value decreases and then closes surplus
195     // windows if one sets a limit lower than the number of windows open
196     if (val < prev)
197     {
198         window_close_surplus(val, WC_OPTIONS);
199     }
200 }
201 
202 /**
203  * Closes the specified window.
204  *  rct2: 0x006ECD4C
205  *
206  * @param window The window to close (esi).
207  */
window_close(rct_window * w)208 void window_close(rct_window* w)
209 {
210     auto itWindow = window_get_iterator(w);
211     if (itWindow == g_window_list.end())
212         return;
213 
214     // Explicit copy of the shared ptr to keep the memory valid.
215     std::shared_ptr<rct_window> window = *itWindow;
216 
217     window_event_close_call(window.get());
218 
219     // Remove viewport
220     window->RemoveViewport();
221 
222     // Invalidate the window (area)
223     window->Invalidate();
224 
225     // The window list may have been modified in the close event
226     itWindow = window_get_iterator(w);
227     if (itWindow != g_window_list.end())
228         g_window_list.erase(itWindow);
229 }
230 
window_close_by_condition(TPred pred,uint32_t flags=WindowCloseFlags::None)231 template<typename TPred> static void window_close_by_condition(TPred pred, uint32_t flags = WindowCloseFlags::None)
232 {
233     bool listUpdated;
234     do
235     {
236         listUpdated = false;
237 
238         auto closeSingle = [&](std::shared_ptr<rct_window> window) -> bool {
239             if (!pred(window.get()))
240             {
241                 return false;
242             }
243 
244             // Keep track of current amount, if a new window is created upon closing
245             // we need to break this current iteration and restart.
246             size_t previousCount = g_window_list.size();
247 
248             window_close(window.get());
249 
250             if ((flags & WindowCloseFlags::CloseSingle) != 0)
251             {
252                 // Only close a single one.
253                 return true;
254             }
255 
256             if (previousCount >= g_window_list.size())
257             {
258                 // A new window was created during the close event.
259                 return true;
260             }
261 
262             // Keep closing windows.
263             return false;
264         };
265 
266         // The closest to something like for_each_if is using find_if in order to avoid duplicate code
267         // to change the loop direction.
268         auto windowList = g_window_list;
269         if ((flags & WindowCloseFlags::IterateReverse) != 0)
270             listUpdated = std::find_if(windowList.rbegin(), windowList.rend(), closeSingle) != windowList.rend();
271         else
272             listUpdated = std::find_if(windowList.begin(), windowList.end(), closeSingle) != windowList.end();
273 
274         // If requested to close only a single window and a new window was created during close
275         // we ignore it.
276         if ((flags & WindowCloseFlags::CloseSingle) != 0)
277             break;
278 
279     } while (listUpdated);
280 }
281 
282 /**
283  * Closes all windows with the specified window class.
284  *  rct2: 0x006ECCF4
285  * @param cls (cl) with bit 15 set
286  */
window_close_by_class(rct_windowclass cls)287 void window_close_by_class(rct_windowclass cls)
288 {
289     window_close_by_condition([&](rct_window* w) -> bool { return w->classification == cls; });
290 }
291 
292 /**
293  * Closes all windows with specified window class and number.
294  *  rct2: 0x006ECCF4
295  * @param cls (cl) without bit 15 set
296  * @param number (dx)
297  */
window_close_by_number(rct_windowclass cls,rct_windownumber number)298 void window_close_by_number(rct_windowclass cls, rct_windownumber number)
299 {
300     window_close_by_condition([cls, number](rct_window* w) -> bool { return w->classification == cls && w->number == number; });
301 }
302 
303 /**
304  * Finds the first window with the specified window class.
305  *  rct2: 0x006EA8A0
306  * @param cls (cl) with bit 15 set
307  * @returns the window or NULL if no window was found.
308  */
window_find_by_class(rct_windowclass cls)309 rct_window* window_find_by_class(rct_windowclass cls)
310 {
311     for (auto& w : g_window_list)
312     {
313         if (w->classification == cls)
314         {
315             return w.get();
316         }
317     }
318     return nullptr;
319 }
320 
321 /**
322  * Finds the first window with the specified window class and number.
323  *  rct2: 0x006EA8A0
324  * @param cls (cl) without bit 15 set
325  * @param number (dx)
326  * @returns the window or NULL if no window was found.
327  */
window_find_by_number(rct_windowclass cls,rct_windownumber number)328 rct_window* window_find_by_number(rct_windowclass cls, rct_windownumber number)
329 {
330     for (auto& w : g_window_list)
331     {
332         if (w->classification == cls && w->number == number)
333         {
334             return w.get();
335         }
336     }
337     return nullptr;
338 }
339 
340 /**
341  * Closes the top-most window
342  *
343  *  rct2: 0x006E403C
344  */
window_close_top()345 void window_close_top()
346 {
347     window_close_by_class(WC_DROPDOWN);
348 
349     if (gScreenFlags & SCREEN_FLAGS_SCENARIO_EDITOR)
350     {
351         if (gEditorStep != EditorStep::LandscapeEditor)
352             return;
353     }
354 
355     auto pred = [](rct_window* w) -> bool { return !(w->flags & (WF_STICK_TO_BACK | WF_STICK_TO_FRONT)); };
356     window_close_by_condition(pred, WindowCloseFlags::CloseSingle | WindowCloseFlags::IterateReverse);
357 }
358 
359 /**
360  * Closes all open windows
361  *
362  *  rct2: 0x006EE927
363  */
window_close_all()364 void window_close_all()
365 {
366     window_close_by_class(WC_DROPDOWN);
367     window_close_by_condition([](rct_window* w) -> bool { return !(w->flags & (WF_STICK_TO_BACK | WF_STICK_TO_FRONT)); });
368 }
369 
window_close_all_except_class(rct_windowclass cls)370 void window_close_all_except_class(rct_windowclass cls)
371 {
372     window_close_by_class(WC_DROPDOWN);
373 
374     window_close_by_condition([cls](rct_window* w) -> bool {
375         return w->classification != cls && !(w->flags & (WF_STICK_TO_BACK | WF_STICK_TO_FRONT));
376     });
377 }
378 
379 /**
380  * Closes all windows, save for those having any of the passed flags.
381  */
window_close_all_except_flags(uint16_t flags)382 void window_close_all_except_flags(uint16_t flags)
383 {
384     window_close_by_condition([flags](rct_window* w) -> bool { return !(w->flags & flags); });
385 }
386 
387 /**
388  *
389  *  rct2: 0x006EA845
390  */
window_find_from_point(const ScreenCoordsXY & screenCoords)391 rct_window* window_find_from_point(const ScreenCoordsXY& screenCoords)
392 {
393     for (auto it = g_window_list.rbegin(); it != g_window_list.rend(); it++)
394     {
395         auto& w = *it;
396         if (screenCoords.x < w->windowPos.x || screenCoords.x >= w->windowPos.x + w->width || screenCoords.y < w->windowPos.y
397             || screenCoords.y >= w->windowPos.y + w->height)
398             continue;
399 
400         if (w->flags & WF_NO_BACKGROUND)
401         {
402             auto widgetIndex = window_find_widget_from_point(w.get(), screenCoords);
403             if (widgetIndex == -1)
404                 continue;
405         }
406 
407         return w.get();
408     }
409 
410     return nullptr;
411 }
412 
413 /**
414  *
415  *  rct2: 0x006EA594
416  * x (ax)
417  * y (bx)
418  * returns widget_index (edx)
419  * EDI NEEDS TO BE SET TO w->widgets[widget_index] AFTER
420  */
window_find_widget_from_point(rct_window * w,const ScreenCoordsXY & screenCoords)421 rct_widgetindex window_find_widget_from_point(rct_window* w, const ScreenCoordsXY& screenCoords)
422 {
423     // Invalidate the window
424     window_event_invalidate_call(w);
425 
426     // Find the widget at point x, y
427     rct_widgetindex widget_index = -1;
428     for (int32_t i = 0;; i++)
429     {
430         const auto& widget = w->widgets[i];
431         if (widget.type == WindowWidgetType::Last)
432         {
433             break;
434         }
435 
436         if (widget.type != WindowWidgetType::Empty && widget.IsVisible())
437         {
438             if (screenCoords.x >= w->windowPos.x + widget.left && screenCoords.x <= w->windowPos.x + widget.right
439                 && screenCoords.y >= w->windowPos.y + widget.top && screenCoords.y <= w->windowPos.y + widget.bottom)
440             {
441                 widget_index = i;
442             }
443         }
444     }
445 
446     // Return next widget if a dropdown
447     if (widget_index != -1)
448     {
449         const auto& widget = w->widgets[widget_index];
450         if (widget.type == WindowWidgetType::DropdownMenu)
451             widget_index++;
452     }
453 
454     // Return the widget index
455     return widget_index;
456 }
457 
458 /**
459  * Invalidates the specified window.
460  *  rct2: 0x006EB13A
461  *
462  * @param window The window to invalidate (esi).
463  */
window_invalidate_by_condition(TPred pred)464 template<typename TPred> static void window_invalidate_by_condition(TPred pred)
465 {
466     window_visit_each([pred](rct_window* w) {
467         if (pred(w))
468         {
469             w->Invalidate();
470         }
471     });
472 }
473 
474 /**
475  * Invalidates all windows with the specified window class.
476  *  rct2: 0x006EC3AC
477  * @param cls (al) with bit 14 set
478  */
window_invalidate_by_class(rct_windowclass cls)479 void window_invalidate_by_class(rct_windowclass cls)
480 {
481     window_invalidate_by_condition([cls](rct_window* w) -> bool { return w->classification == cls; });
482 }
483 
484 /**
485  * Invalidates all windows with the specified window class and number.
486  *  rct2: 0x006EC3AC
487  */
window_invalidate_by_number(rct_windowclass cls,rct_windownumber number)488 void window_invalidate_by_number(rct_windowclass cls, rct_windownumber number)
489 {
490     window_invalidate_by_condition(
491         [cls, number](rct_window* w) -> bool { return w->classification == cls && w->number == number; });
492 }
493 
494 /**
495  * Invalidates all windows.
496  */
window_invalidate_all()497 void window_invalidate_all()
498 {
499     window_visit_each([](rct_window* w) { w->Invalidate(); });
500 }
501 
502 /**
503  * Invalidates the specified widget of a window.
504  *  rct2: 0x006EC402
505  */
widget_invalidate(rct_window * w,rct_widgetindex widgetIndex)506 void widget_invalidate(rct_window* w, rct_widgetindex widgetIndex)
507 {
508     assert(w != nullptr);
509 #ifdef DEBUG
510     for (int32_t i = 0; i <= widgetIndex; i++)
511     {
512         assert(w->widgets[i].type != WindowWidgetType::Last);
513     }
514 #endif
515 
516     const auto& widget = w->widgets[widgetIndex];
517     if (widget.left == -2)
518         return;
519 
520     gfx_set_dirty_blocks({ { w->windowPos + ScreenCoordsXY{ widget.left, widget.top } },
521                            { w->windowPos + ScreenCoordsXY{ widget.right + 1, widget.bottom + 1 } } });
522 }
523 
widget_invalidate_by_condition(TPred pred)524 template<typename TPred> static void widget_invalidate_by_condition(TPred pred)
525 {
526     window_visit_each([pred](rct_window* w) {
527         if (pred(w))
528         {
529             w->Invalidate();
530         }
531     });
532 }
533 
534 /**
535  * Invalidates the specified widget of all windows that match the specified window class.
536  */
widget_invalidate_by_class(rct_windowclass cls,rct_widgetindex widgetIndex)537 void widget_invalidate_by_class(rct_windowclass cls, rct_widgetindex widgetIndex)
538 {
539     window_visit_each([cls, widgetIndex](rct_window* w) {
540         if (w->classification == cls)
541         {
542             widget_invalidate(w, widgetIndex);
543         }
544     });
545 }
546 
547 /**
548  * Invalidates the specified widget of all windows that match the specified window class and number.
549  *  rct2: 0x006EC3AC
550  */
widget_invalidate_by_number(rct_windowclass cls,rct_windownumber number,rct_widgetindex widgetIndex)551 void widget_invalidate_by_number(rct_windowclass cls, rct_windownumber number, rct_widgetindex widgetIndex)
552 {
553     window_visit_each([cls, number, widgetIndex](rct_window* w) {
554         if (w->classification == cls && w->number == number)
555         {
556             widget_invalidate(w, widgetIndex);
557         }
558     });
559 }
560 
561 /**
562  *
563  *  rct2: 0x006EAE4E
564  *
565  * @param w The window (esi).
566  */
window_update_scroll_widgets(rct_window * w)567 void window_update_scroll_widgets(rct_window* w)
568 {
569     int32_t scrollIndex, width, height, scrollPositionChanged;
570     rct_widgetindex widgetIndex;
571     rct_widget* widget;
572 
573     widgetIndex = 0;
574     scrollIndex = 0;
575     assert(w != nullptr);
576     for (widget = w->widgets; widget->type != WindowWidgetType::Last; widget++, widgetIndex++)
577     {
578         if (widget->type != WindowWidgetType::Scroll)
579             continue;
580 
581         auto& scroll = w->scrolls[scrollIndex];
582         width = 0;
583         height = 0;
584         window_get_scroll_size(w, scrollIndex, &width, &height);
585         if (height == 0)
586         {
587             scroll.v_top = 0;
588         }
589         else if (width == 0)
590         {
591             scroll.h_left = 0;
592         }
593         width++;
594         height++;
595 
596         scrollPositionChanged = 0;
597         if ((widget->content & SCROLL_HORIZONTAL) && width != scroll.h_right)
598         {
599             scrollPositionChanged = 1;
600             scroll.h_right = width;
601         }
602 
603         if ((widget->content & SCROLL_VERTICAL) && height != scroll.v_bottom)
604         {
605             scrollPositionChanged = 1;
606             scroll.v_bottom = height;
607         }
608 
609         if (scrollPositionChanged)
610         {
611             WidgetScrollUpdateThumbs(w, widgetIndex);
612             w->Invalidate();
613         }
614         scrollIndex++;
615     }
616 }
617 
window_get_scroll_data_index(rct_window * w,rct_widgetindex widget_index)618 int32_t window_get_scroll_data_index(rct_window* w, rct_widgetindex widget_index)
619 {
620     int32_t i, result;
621 
622     result = 0;
623     assert(w != nullptr);
624     for (i = 0; i < widget_index; i++)
625     {
626         const auto& widget = w->widgets[i];
627         if (widget.type == WindowWidgetType::Scroll)
628             result++;
629     }
630     return result;
631 }
632 
633 /**
634  *
635  *  rct2: 0x006ECDA4
636  */
window_bring_to_front(rct_window * w)637 rct_window* window_bring_to_front(rct_window* w)
638 {
639     if (!(w->flags & (WF_STICK_TO_BACK | WF_STICK_TO_FRONT)))
640     {
641         auto itSourcePos = window_get_iterator(w);
642         if (itSourcePos != g_window_list.end())
643         {
644             // Insert in front of the first non-stick-to-front window
645             auto itDestPos = g_window_list.begin();
646             for (auto it = g_window_list.rbegin(); it != g_window_list.rend(); it++)
647             {
648                 auto& w2 = *it;
649                 if (!(w2->flags & WF_STICK_TO_FRONT))
650                 {
651                     itDestPos = it.base();
652                     break;
653                 }
654             }
655 
656             g_window_list.splice(itDestPos, g_window_list, itSourcePos);
657             w->Invalidate();
658 
659             if (w->windowPos.x + w->width < 20)
660             {
661                 int32_t i = 20 - w->windowPos.x;
662                 w->windowPos.x += i;
663                 if (w->viewport != nullptr)
664                     w->viewport->pos.x += i;
665                 w->Invalidate();
666             }
667         }
668     }
669     return w;
670 }
671 
window_bring_to_front_by_class_with_flags(rct_windowclass cls,uint16_t flags)672 rct_window* window_bring_to_front_by_class_with_flags(rct_windowclass cls, uint16_t flags)
673 {
674     rct_window* w;
675 
676     w = window_find_by_class(cls);
677     if (w != nullptr)
678     {
679         w->flags |= flags;
680         w->Invalidate();
681         w = window_bring_to_front(w);
682     }
683 
684     return w;
685 }
686 
window_bring_to_front_by_class(rct_windowclass cls)687 rct_window* window_bring_to_front_by_class(rct_windowclass cls)
688 {
689     return window_bring_to_front_by_class_with_flags(cls, WF_WHITE_BORDER_MASK);
690 }
691 
692 /**
693  *
694  *  rct2: 0x006ED78A
695  * cls (cl)
696  * number (dx)
697  */
window_bring_to_front_by_number(rct_windowclass cls,rct_windownumber number)698 rct_window* window_bring_to_front_by_number(rct_windowclass cls, rct_windownumber number)
699 {
700     rct_window* w;
701 
702     w = window_find_by_number(cls, number);
703     if (w != nullptr)
704     {
705         w->flags |= WF_WHITE_BORDER_MASK;
706         w->Invalidate();
707         w = window_bring_to_front(w);
708     }
709 
710     return w;
711 }
712 
713 /**
714  *
715  *  rct2: 0x006EE65A
716  */
window_push_others_right(rct_window * window)717 void window_push_others_right(rct_window* window)
718 {
719     window_visit_each([window](rct_window* w) {
720         if (w == window)
721             return;
722         if (w->flags & (WF_STICK_TO_BACK | WF_STICK_TO_FRONT))
723             return;
724         if (w->windowPos.x >= window->windowPos.x + window->width)
725             return;
726         if (w->windowPos.x + w->width <= window->windowPos.x)
727             return;
728         if (w->windowPos.y >= window->windowPos.y + window->height)
729             return;
730         if (w->windowPos.y + w->height <= window->windowPos.y)
731             return;
732 
733         w->Invalidate();
734         if (window->windowPos.x + window->width + 13 >= context_get_width())
735             return;
736         auto push_amount = window->windowPos.x + window->width - w->windowPos.x + 3;
737         w->windowPos.x += push_amount;
738         w->Invalidate();
739         if (w->viewport != nullptr)
740             w->viewport->pos.x += push_amount;
741     });
742 }
743 
744 /**
745  *
746  *  rct2: 0x006EE6EA
747  */
window_push_others_below(rct_window * w1)748 void window_push_others_below(rct_window* w1)
749 {
750     // Enumerate through all other windows
751     window_visit_each([w1](rct_window* w2) {
752         if (w1 == w2)
753             return;
754         // ?
755         if (w2->flags & (WF_STICK_TO_BACK | WF_STICK_TO_FRONT))
756             return;
757         // Check if w2 intersects with w1
758         if (w2->windowPos.x > (w1->windowPos.x + w1->width) || w2->windowPos.x + w2->width < w1->windowPos.x)
759             return;
760         if (w2->windowPos.y > (w1->windowPos.y + w1->height) || w2->windowPos.y + w2->height < w1->windowPos.y)
761             return;
762 
763         // Check if there is room to push it down
764         if (w1->windowPos.y + w1->height + 80 >= context_get_height())
765             return;
766 
767         // Invalidate the window's current area
768         w2->Invalidate();
769 
770         int32_t push_amount = w1->windowPos.y + w1->height - w2->windowPos.y + 3;
771         w2->windowPos.y += push_amount;
772 
773         // Invalidate the window's new area
774         w2->Invalidate();
775 
776         // Update viewport position if necessary
777         if (w2->viewport != nullptr)
778             w2->viewport->pos.y += push_amount;
779     });
780 }
781 
782 /**
783  *
784  *  rct2: 0x006EE2E4
785  */
window_get_main()786 rct_window* window_get_main()
787 {
788     for (auto& w : g_window_list)
789     {
790         if (w->classification == WC_MAIN_WINDOW)
791         {
792             return w.get();
793         }
794     }
795     return nullptr;
796 }
797 
798 /**
799  *
800  *  rct2: 0x006E7C9C
801  * @param w (esi)
802  * @param x (eax)
803  * @param y (ecx)
804  * @param z (edx)
805  */
window_scroll_to_location(rct_window * w,const CoordsXYZ & coords)806 void window_scroll_to_location(rct_window* w, const CoordsXYZ& coords)
807 {
808     assert(w != nullptr);
809     window_unfollow_sprite(w);
810     if (w->viewport != nullptr)
811     {
812         int16_t height = tile_element_height(coords);
813         if (coords.z < height - 16)
814         {
815             if (!(w->viewport->flags & 1 << 0))
816             {
817                 w->viewport->flags |= 1 << 0;
818                 w->Invalidate();
819             }
820         }
821         else
822         {
823             if (w->viewport->flags & 1 << 0)
824             {
825                 w->viewport->flags &= ~(1 << 0);
826                 w->Invalidate();
827             }
828         }
829 
830         auto screenCoords = translate_3d_to_2d_with_z(get_current_rotation(), coords);
831 
832         int32_t i = 0;
833         if (!(gScreenFlags & SCREEN_FLAGS_TITLE_DEMO))
834         {
835             bool found = false;
836             while (!found)
837             {
838                 auto x2 = w->viewport->pos.x + static_cast<int32_t>(w->viewport->width * window_scroll_locations[i][0]);
839                 auto y2 = w->viewport->pos.y + static_cast<int32_t>(w->viewport->height * window_scroll_locations[i][1]);
840 
841                 auto it = window_get_iterator(w);
842                 for (; it != g_window_list.end(); it++)
843                 {
844                     auto w2 = (*it).get();
845                     auto x1 = w2->windowPos.x - 10;
846                     auto y1 = w2->windowPos.y - 10;
847                     if (x2 >= x1 && x2 <= w2->width + x1 + 20)
848                     {
849                         if (y2 >= y1 && y2 <= w2->height + y1 + 20)
850                         {
851                             // window is covering this area, try the next one
852                             i++;
853                             found = false;
854                             break;
855                         }
856                     }
857                 }
858                 if (it == g_window_list.end())
859                 {
860                     found = true;
861                 }
862                 if (i >= static_cast<int32_t>(std::size(window_scroll_locations)))
863                 {
864                     i = 0;
865                     found = true;
866                 }
867             }
868         }
869         // rct2: 0x006E7C76
870         if (w->viewport_target_sprite == SPRITE_INDEX_NULL)
871         {
872             if (!(w->flags & WF_NO_SCROLLING))
873             {
874                 w->savedViewPos = screenCoords
875                     - ScreenCoordsXY{ static_cast<int32_t>(w->viewport->view_width * window_scroll_locations[i][0]),
876                                       static_cast<int32_t>(w->viewport->view_height * window_scroll_locations[i][1]) };
877                 w->flags |= WF_SCROLLING_TO_LOCATION;
878             }
879         }
880     }
881 }
882 
883 /**
884  *
885  *  rct2: 0x00688956
886  */
call_event_viewport_rotate_on_all_windows()887 static void call_event_viewport_rotate_on_all_windows()
888 {
889     window_visit_each([](rct_window* w) { window_event_viewport_rotate_call(w); });
890 }
891 
892 /**
893  *
894  *  rct2: 0x0068881A
895  * direction can be used to alter the camera rotation:
896  *      1: clockwise
897  *      -1: anti-clockwise
898  */
window_rotate_camera(rct_window * w,int32_t direction)899 void window_rotate_camera(rct_window* w, int32_t direction)
900 {
901     rct_viewport* viewport = w->viewport;
902     if (viewport == nullptr)
903         return;
904 
905     auto windowPos = ScreenCoordsXY{ (viewport->width >> 1), (viewport->height >> 1) } + viewport->pos;
906 
907     // has something to do with checking if middle of the viewport is obstructed
908     rct_viewport* other;
909     auto mapXYCoords = screen_get_map_xy(windowPos, &other);
910     CoordsXYZ coords{};
911 
912     // other != viewport probably triggers on viewports in ride or guest window?
913     // naoXYCoords is nullopt if middle of viewport is obstructed by another window?
914     if (!mapXYCoords.has_value() || other != viewport)
915     {
916         auto viewPos = ScreenCoordsXY{ (viewport->view_width >> 1), (viewport->view_height >> 1) } + viewport->viewPos;
917 
918         coords = viewport_adjust_for_map_height(viewPos);
919     }
920     else
921     {
922         coords.x = mapXYCoords->x;
923         coords.y = mapXYCoords->y;
924         coords.z = tile_element_height(coords);
925     }
926 
927     gCurrentRotation = (get_current_rotation() + direction) & 3;
928 
929     auto centreLoc = centre_2d_coordinates(coords, viewport);
930 
931     if (centreLoc.has_value())
932     {
933         w->savedViewPos = centreLoc.value();
934         viewport->viewPos = *centreLoc;
935     }
936 
937     w->Invalidate();
938 
939     call_event_viewport_rotate_on_all_windows();
940     reset_all_sprite_quadrant_placements();
941 }
942 
window_viewport_get_map_coords_by_cursor(rct_window * w,int32_t * map_x,int32_t * map_y,int32_t * offset_x,int32_t * offset_y)943 void window_viewport_get_map_coords_by_cursor(
944     rct_window* w, int32_t* map_x, int32_t* map_y, int32_t* offset_x, int32_t* offset_y)
945 {
946     // Get mouse position to offset against.
947     auto mouseCoords = context_get_cursor_position_scaled();
948 
949     // Compute map coordinate by mouse position.
950     auto viewportPos = w->viewport->ScreenToViewportCoord(mouseCoords);
951     auto coordsXYZ = viewport_adjust_for_map_height(viewportPos);
952     auto mapCoords = viewport_coord_to_map_coord(viewportPos, coordsXYZ.z);
953     *map_x = mapCoords.x;
954     *map_y = mapCoords.y;
955 
956     // Get viewport coordinates centring around the tile.
957     int32_t z = tile_element_height(mapCoords);
958 
959     auto centreLoc = centre_2d_coordinates({ mapCoords.x, mapCoords.y, z }, w->viewport);
960     if (!centreLoc)
961     {
962         log_error("Invalid location.");
963         return;
964     }
965 
966     // Rebase mouse position onto centre of window, and compensate for zoom level.
967     int32_t rebased_x = ((w->width / 2) - mouseCoords.x) * w->viewport->zoom;
968     int32_t rebased_y = ((w->height / 2) - mouseCoords.y) * w->viewport->zoom;
969 
970     // Compute cursor offset relative to tile.
971     *offset_x = (w->savedViewPos.x - (centreLoc->x + rebased_x)) * w->viewport->zoom;
972     *offset_y = (w->savedViewPos.y - (centreLoc->y + rebased_y)) * w->viewport->zoom;
973 }
974 
window_viewport_centre_tile_around_cursor(rct_window * w,int32_t map_x,int32_t map_y,int32_t offset_x,int32_t offset_y)975 void window_viewport_centre_tile_around_cursor(rct_window* w, int32_t map_x, int32_t map_y, int32_t offset_x, int32_t offset_y)
976 {
977     // Get viewport coordinates centring around the tile.
978     int32_t z = tile_element_height({ map_x, map_y });
979     auto centreLoc = centre_2d_coordinates({ map_x, map_y, z }, w->viewport);
980 
981     if (!centreLoc.has_value())
982     {
983         log_error("Invalid location.");
984         return;
985     }
986 
987     // Get mouse position to offset against.
988     auto mouseCoords = context_get_cursor_position_scaled();
989 
990     // Rebase mouse position onto centre of window, and compensate for zoom level.
991     int32_t rebased_x = ((w->width >> 1) - mouseCoords.x) * w->viewport->zoom;
992     int32_t rebased_y = ((w->height >> 1) - mouseCoords.y) * w->viewport->zoom;
993 
994     // Apply offset to the viewport.
995     w->savedViewPos = { centreLoc->x + rebased_x + (offset_x / w->viewport->zoom),
996                         centreLoc->y + rebased_y + (offset_y / w->viewport->zoom) };
997 }
998 
999 /**
1000  * For all windows with viewports, ensure they do not have a zoom level less than the minimum.
1001  */
window_check_all_valid_zoom()1002 void window_check_all_valid_zoom()
1003 {
1004     window_visit_each([](rct_window* w) {
1005         if (w->viewport != nullptr && w->viewport->zoom < ZoomLevel::min())
1006         {
1007             window_zoom_set(w, ZoomLevel::min(), false);
1008         }
1009     });
1010 }
1011 
window_zoom_set(rct_window * w,ZoomLevel zoomLevel,bool atCursor)1012 void window_zoom_set(rct_window* w, ZoomLevel zoomLevel, bool atCursor)
1013 {
1014     rct_viewport* v = w->viewport;
1015     if (v == nullptr)
1016         return;
1017 
1018     zoomLevel = std::clamp(zoomLevel, ZoomLevel::min(), ZoomLevel::max());
1019     if (v->zoom == zoomLevel)
1020         return;
1021 
1022     // Zooming to cursor? Remember where we're pointing at the moment.
1023     int32_t saved_map_x = 0;
1024     int32_t saved_map_y = 0;
1025     int32_t offset_x = 0;
1026     int32_t offset_y = 0;
1027     if (gConfigGeneral.zoom_to_cursor && atCursor)
1028     {
1029         window_viewport_get_map_coords_by_cursor(w, &saved_map_x, &saved_map_y, &offset_x, &offset_y);
1030     }
1031 
1032     // Zoom in
1033     while (v->zoom > zoomLevel)
1034     {
1035         v->zoom--;
1036         w->savedViewPos.x += v->view_width / 4;
1037         w->savedViewPos.y += v->view_height / 4;
1038         v->view_width /= 2;
1039         v->view_height /= 2;
1040     }
1041 
1042     // Zoom out
1043     while (v->zoom < zoomLevel)
1044     {
1045         v->zoom++;
1046         w->savedViewPos.x -= v->view_width / 2;
1047         w->savedViewPos.y -= v->view_height / 2;
1048         v->view_width *= 2;
1049         v->view_height *= 2;
1050     }
1051 
1052     // Zooming to cursor? Centre around the tile we were hovering over just now.
1053     if (gConfigGeneral.zoom_to_cursor && atCursor)
1054     {
1055         window_viewport_centre_tile_around_cursor(w, saved_map_x, saved_map_y, offset_x, offset_y);
1056     }
1057 
1058     // HACK: Prevents the redraw from failing when there is
1059     // a window on top of the viewport.
1060     window_bring_to_front(w);
1061     w->Invalidate();
1062 }
1063 
1064 /**
1065  *
1066  *  rct2: 0x006887A6
1067  */
window_zoom_in(rct_window * w,bool atCursor)1068 void window_zoom_in(rct_window* w, bool atCursor)
1069 {
1070     window_zoom_set(w, w->viewport->zoom - 1, atCursor);
1071 }
1072 
1073 /**
1074  *
1075  *  rct2: 0x006887E0
1076  */
window_zoom_out(rct_window * w,bool atCursor)1077 void window_zoom_out(rct_window* w, bool atCursor)
1078 {
1079     window_zoom_set(w, w->viewport->zoom + 1, atCursor);
1080 }
1081 
main_window_zoom(bool zoomIn,bool atCursor)1082 void main_window_zoom(bool zoomIn, bool atCursor)
1083 {
1084     if (gScreenFlags & SCREEN_FLAGS_TITLE_DEMO)
1085         return;
1086     if (!(gScreenFlags & SCREEN_FLAGS_SCENARIO_EDITOR) || gEditorStep == EditorStep::LandscapeEditor)
1087     {
1088         if (!(gScreenFlags & SCREEN_FLAGS_TRACK_MANAGER))
1089         {
1090             auto* mainWindow = window_get_main();
1091             if (mainWindow != nullptr)
1092                 window_zoom_set(mainWindow, mainWindow->viewport->zoom + (zoomIn ? -1 : 1), atCursor);
1093         }
1094     }
1095 }
1096 
1097 /**
1098  * Splits a drawing of a window into regions that can be seen and are not hidden
1099  * by other opaque overlapping windows.
1100  */
window_draw(rct_drawpixelinfo * dpi,rct_window * w,int32_t left,int32_t top,int32_t right,int32_t bottom)1101 void window_draw(rct_drawpixelinfo* dpi, rct_window* w, int32_t left, int32_t top, int32_t right, int32_t bottom)
1102 {
1103     if (!window_is_visible(w))
1104         return;
1105 
1106     // Divide the draws up for only the visible regions of the window recursively
1107     auto itPos = window_get_iterator(w);
1108     for (auto it = std::next(itPos); it != g_window_list.end(); it++)
1109     {
1110         // Check if this window overlaps w
1111         auto topwindow = it->get();
1112         if (topwindow->windowPos.x >= right || topwindow->windowPos.y >= bottom)
1113             continue;
1114         if (topwindow->windowPos.x + topwindow->width <= left || topwindow->windowPos.y + topwindow->height <= top)
1115             continue;
1116         if (topwindow->flags & WF_TRANSPARENT)
1117             continue;
1118 
1119         // A window overlaps w, split up the draw into two regions where the window starts to overlap
1120         if (topwindow->windowPos.x > left)
1121         {
1122             // Split draw at topwindow.left
1123             window_draw_core(dpi, w, left, top, topwindow->windowPos.x, bottom);
1124             window_draw_core(dpi, w, topwindow->windowPos.x, top, right, bottom);
1125         }
1126         else if (topwindow->windowPos.x + topwindow->width < right)
1127         {
1128             // Split draw at topwindow.right
1129             window_draw_core(dpi, w, left, top, topwindow->windowPos.x + topwindow->width, bottom);
1130             window_draw_core(dpi, w, topwindow->windowPos.x + topwindow->width, top, right, bottom);
1131         }
1132         else if (topwindow->windowPos.y > top)
1133         {
1134             // Split draw at topwindow.top
1135             window_draw_core(dpi, w, left, top, right, topwindow->windowPos.y);
1136             window_draw_core(dpi, w, left, topwindow->windowPos.y, right, bottom);
1137         }
1138         else if (topwindow->windowPos.y + topwindow->height < bottom)
1139         {
1140             // Split draw at topwindow.bottom
1141             window_draw_core(dpi, w, left, top, right, topwindow->windowPos.y + topwindow->height);
1142             window_draw_core(dpi, w, left, topwindow->windowPos.y + topwindow->height, right, bottom);
1143         }
1144 
1145         // Drawing for this region should be done now, exit
1146         return;
1147     }
1148 
1149     // No windows overlap
1150     window_draw_core(dpi, w, left, top, right, bottom);
1151 }
1152 
1153 /**
1154  * Draws the given window and any other overlapping transparent windows.
1155  */
window_draw_core(rct_drawpixelinfo * dpi,rct_window * w,int32_t left,int32_t top,int32_t right,int32_t bottom)1156 static void window_draw_core(rct_drawpixelinfo* dpi, rct_window* w, int32_t left, int32_t top, int32_t right, int32_t bottom)
1157 {
1158     // Clamp region
1159     left = std::max<int32_t>(left, w->windowPos.x);
1160     top = std::max<int32_t>(top, w->windowPos.y);
1161     right = std::min<int32_t>(right, w->windowPos.x + w->width);
1162     bottom = std::min<int32_t>(bottom, w->windowPos.y + w->height);
1163     if (left >= right)
1164         return;
1165     if (top >= bottom)
1166         return;
1167 
1168     // Draw the window and any other overlapping transparent windows
1169     for (auto it = window_get_iterator(w); it != g_window_list.end(); it++)
1170     {
1171         auto v = (*it).get();
1172         if ((w == v || (v->flags & WF_TRANSPARENT)) && window_is_visible(v))
1173         {
1174             window_draw_single(dpi, v, left, top, right, bottom);
1175         }
1176     }
1177 }
1178 
window_draw_single(rct_drawpixelinfo * dpi,rct_window * w,int32_t left,int32_t top,int32_t right,int32_t bottom)1179 static void window_draw_single(rct_drawpixelinfo* dpi, rct_window* w, int32_t left, int32_t top, int32_t right, int32_t bottom)
1180 {
1181     // Copy dpi so we can crop it
1182     rct_drawpixelinfo copy = *dpi;
1183     dpi = &copy;
1184 
1185     // Clamp left to 0
1186     int32_t overflow = left - dpi->x;
1187     if (overflow > 0)
1188     {
1189         dpi->x += overflow;
1190         dpi->width -= overflow;
1191         if (dpi->width <= 0)
1192             return;
1193         dpi->pitch += overflow;
1194         dpi->bits += overflow;
1195     }
1196 
1197     // Clamp width to right
1198     overflow = dpi->x + dpi->width - right;
1199     if (overflow > 0)
1200     {
1201         dpi->width -= overflow;
1202         if (dpi->width <= 0)
1203             return;
1204         dpi->pitch += overflow;
1205     }
1206 
1207     // Clamp top to 0
1208     overflow = top - dpi->y;
1209     if (overflow > 0)
1210     {
1211         dpi->y += overflow;
1212         dpi->height -= overflow;
1213         if (dpi->height <= 0)
1214             return;
1215         dpi->bits += (dpi->width + dpi->pitch) * overflow;
1216     }
1217 
1218     // Clamp height to bottom
1219     overflow = dpi->y + dpi->height - bottom;
1220     if (overflow > 0)
1221     {
1222         dpi->height -= overflow;
1223         if (dpi->height <= 0)
1224             return;
1225     }
1226 
1227     // Invalidate modifies the window colours so first get the correct
1228     // colour before setting the global variables for the string painting
1229     window_event_invalidate_call(w);
1230 
1231     // Text colouring
1232     gCurrentWindowColours[0] = NOT_TRANSLUCENT(w->colours[0]);
1233     gCurrentWindowColours[1] = NOT_TRANSLUCENT(w->colours[1]);
1234     gCurrentWindowColours[2] = NOT_TRANSLUCENT(w->colours[2]);
1235     gCurrentWindowColours[3] = NOT_TRANSLUCENT(w->colours[3]);
1236 
1237     window_event_paint_call(w, dpi);
1238 }
1239 
1240 /**
1241  *
1242  *  rct2: 0x00685BE1
1243  *
1244  * @param dpi (edi)
1245  * @param w (esi)
1246  */
window_draw_viewport(rct_drawpixelinfo * dpi,rct_window * w)1247 void window_draw_viewport(rct_drawpixelinfo* dpi, rct_window* w)
1248 {
1249     viewport_render(dpi, w->viewport, { { dpi->x, dpi->y }, { dpi->x + dpi->width, dpi->y + dpi->height } });
1250 }
1251 
window_set_position(rct_window * w,const ScreenCoordsXY & screenCoords)1252 void window_set_position(rct_window* w, const ScreenCoordsXY& screenCoords)
1253 {
1254     window_move_position(w, screenCoords - w->windowPos);
1255 }
1256 
window_move_position(rct_window * w,const ScreenCoordsXY & deltaCoords)1257 void window_move_position(rct_window* w, const ScreenCoordsXY& deltaCoords)
1258 {
1259     if (deltaCoords.x == 0 && deltaCoords.y == 0)
1260         return;
1261 
1262     // Invalidate old region
1263     w->Invalidate();
1264 
1265     // Translate window and viewport
1266     w->windowPos += deltaCoords;
1267     if (w->viewport != nullptr)
1268     {
1269         w->viewport->pos += deltaCoords;
1270     }
1271 
1272     // Invalidate new region
1273     w->Invalidate();
1274 }
1275 
window_resize(rct_window * w,int32_t dw,int32_t dh)1276 void window_resize(rct_window* w, int32_t dw, int32_t dh)
1277 {
1278     if (dw == 0 && dh == 0)
1279         return;
1280 
1281     // Invalidate old region
1282     w->Invalidate();
1283 
1284     // Clamp new size to minimum and maximum
1285     w->width = std::clamp<int32_t>(w->width + dw, w->min_width, w->max_width);
1286     w->height = std::clamp<int32_t>(w->height + dh, w->min_height, w->max_height);
1287 
1288     window_event_resize_call(w);
1289     window_event_invalidate_call(w);
1290 
1291     // Update scroll widgets
1292     for (int32_t i = 0; i < 3; i++)
1293     {
1294         auto& scroll = w->scrolls[i];
1295         scroll.h_right = WINDOW_SCROLL_UNDEFINED;
1296         scroll.v_bottom = WINDOW_SCROLL_UNDEFINED;
1297     }
1298     window_update_scroll_widgets(w);
1299 
1300     // Invalidate new region
1301     w->Invalidate();
1302 }
1303 
window_set_resize(rct_window * w,int32_t minWidth,int32_t minHeight,int32_t maxWidth,int32_t maxHeight)1304 void window_set_resize(rct_window* w, int32_t minWidth, int32_t minHeight, int32_t maxWidth, int32_t maxHeight)
1305 {
1306     w->min_width = minWidth;
1307     w->min_height = minHeight;
1308     w->max_width = maxWidth;
1309     w->max_height = maxHeight;
1310 
1311     // Clamp width and height to minimum and maximum
1312     int32_t width = std::clamp<int32_t>(w->width, std::min(minWidth, maxWidth), std::max(minWidth, maxWidth));
1313     int32_t height = std::clamp<int32_t>(w->height, std::min(minHeight, maxHeight), std::max(minHeight, maxHeight));
1314 
1315     // Resize window if size has changed
1316     if (w->width != width || w->height != height)
1317     {
1318         w->Invalidate();
1319         w->width = width;
1320         w->height = height;
1321         w->Invalidate();
1322     }
1323 }
1324 
1325 /**
1326  *
1327  *  rct2: 0x006EE212
1328  *
1329  * @param tool (al)
1330  * @param widgetIndex (dx)
1331  * @param w (esi)
1332  */
tool_set(rct_window * w,rct_widgetindex widgetIndex,Tool tool)1333 bool tool_set(rct_window* w, rct_widgetindex widgetIndex, Tool tool)
1334 {
1335     if (input_test_flag(INPUT_FLAG_TOOL_ACTIVE))
1336     {
1337         if (w->classification == gCurrentToolWidget.window_classification && w->number == gCurrentToolWidget.window_number
1338             && widgetIndex == gCurrentToolWidget.widget_index)
1339         {
1340             tool_cancel();
1341             return true;
1342         }
1343 
1344         tool_cancel();
1345     }
1346 
1347     input_set_flag(INPUT_FLAG_TOOL_ACTIVE, true);
1348     input_set_flag(INPUT_FLAG_6, false);
1349     gCurrentToolId = tool;
1350     gCurrentToolWidget.window_classification = w->classification;
1351     gCurrentToolWidget.window_number = w->number;
1352     gCurrentToolWidget.widget_index = widgetIndex;
1353     return false;
1354 }
1355 
1356 /**
1357  *
1358  *  rct2: 0x006EE281
1359  */
tool_cancel()1360 void tool_cancel()
1361 {
1362     if (input_test_flag(INPUT_FLAG_TOOL_ACTIVE))
1363     {
1364         input_set_flag(INPUT_FLAG_TOOL_ACTIVE, false);
1365 
1366         map_invalidate_selection_rect();
1367         map_invalidate_map_selection_tiles();
1368 
1369         // Reset map selection
1370         gMapSelectFlags = 0;
1371 
1372         if (gCurrentToolWidget.widget_index != -1)
1373         {
1374             // Invalidate tool widget
1375             widget_invalidate_by_number(
1376                 gCurrentToolWidget.window_classification, gCurrentToolWidget.window_number, gCurrentToolWidget.widget_index);
1377 
1378             // Abort tool event
1379             rct_window* w = window_find_by_number(gCurrentToolWidget.window_classification, gCurrentToolWidget.window_number);
1380             if (w != nullptr)
1381                 window_event_tool_abort_call(w, gCurrentToolWidget.widget_index);
1382         }
1383     }
1384 }
1385 
window_event_close_call(rct_window * w)1386 void window_event_close_call(rct_window* w)
1387 {
1388     if (w->event_handlers == nullptr)
1389         w->OnClose();
1390     else if (w->event_handlers->close != nullptr)
1391         w->event_handlers->close(w);
1392 }
1393 
window_event_mouse_up_call(rct_window * w,rct_widgetindex widgetIndex)1394 void window_event_mouse_up_call(rct_window* w, rct_widgetindex widgetIndex)
1395 {
1396     if (w->event_handlers == nullptr)
1397         w->OnMouseUp(widgetIndex);
1398     else if (w->event_handlers->mouse_up != nullptr)
1399         w->event_handlers->mouse_up(w, widgetIndex);
1400 }
1401 
window_event_resize_call(rct_window * w)1402 void window_event_resize_call(rct_window* w)
1403 {
1404     if (w->event_handlers == nullptr)
1405         w->OnResize();
1406     else if (w->event_handlers->resize != nullptr)
1407         w->event_handlers->resize(w);
1408 }
1409 
window_event_mouse_down_call(rct_window * w,rct_widgetindex widgetIndex)1410 void window_event_mouse_down_call(rct_window* w, rct_widgetindex widgetIndex)
1411 {
1412     if (w->event_handlers == nullptr)
1413         w->OnMouseDown(widgetIndex);
1414     else if (w->event_handlers->mouse_down != nullptr)
1415         w->event_handlers->mouse_down(w, widgetIndex, &w->widgets[widgetIndex]);
1416 }
1417 
window_event_dropdown_call(rct_window * w,rct_widgetindex widgetIndex,int32_t dropdownIndex)1418 void window_event_dropdown_call(rct_window* w, rct_widgetindex widgetIndex, int32_t dropdownIndex)
1419 {
1420     if (w->event_handlers == nullptr)
1421     {
1422         if (dropdownIndex != -1)
1423         {
1424             w->OnDropdown(widgetIndex, dropdownIndex);
1425         }
1426     }
1427     else if (w->event_handlers->dropdown != nullptr)
1428     {
1429         w->event_handlers->dropdown(w, widgetIndex, dropdownIndex);
1430     }
1431 }
1432 
window_event_unknown_05_call(rct_window * w)1433 void window_event_unknown_05_call(rct_window* w)
1434 {
1435     if (w->event_handlers != nullptr)
1436         if (w->event_handlers->unknown_05 != nullptr)
1437             w->event_handlers->unknown_05(w);
1438 }
1439 
window_event_update_call(rct_window * w)1440 void window_event_update_call(rct_window* w)
1441 {
1442     if (w->event_handlers == nullptr)
1443         w->OnUpdate();
1444     else if (w->event_handlers->update != nullptr)
1445         w->event_handlers->update(w);
1446 }
1447 
window_event_periodic_update_call(rct_window * w)1448 void window_event_periodic_update_call(rct_window* w)
1449 {
1450     if (w->event_handlers == nullptr)
1451         w->OnPeriodicUpdate();
1452     else if (w->event_handlers->periodic_update != nullptr)
1453         w->event_handlers->periodic_update(w);
1454 }
1455 
window_event_unknown_08_call(rct_window * w)1456 void window_event_unknown_08_call(rct_window* w)
1457 {
1458     if (w->event_handlers != nullptr)
1459         if (w->event_handlers->unknown_08 != nullptr)
1460             w->event_handlers->unknown_08(w);
1461 }
1462 
window_event_tool_update_call(rct_window * w,rct_widgetindex widgetIndex,const ScreenCoordsXY & screenCoords)1463 void window_event_tool_update_call(rct_window* w, rct_widgetindex widgetIndex, const ScreenCoordsXY& screenCoords)
1464 {
1465     if (w->event_handlers == nullptr)
1466         w->OnToolUpdate(widgetIndex, screenCoords);
1467     else if (w->event_handlers->tool_update != nullptr)
1468         w->event_handlers->tool_update(w, widgetIndex, screenCoords);
1469 }
1470 
window_event_tool_down_call(rct_window * w,rct_widgetindex widgetIndex,const ScreenCoordsXY & screenCoords)1471 void window_event_tool_down_call(rct_window* w, rct_widgetindex widgetIndex, const ScreenCoordsXY& screenCoords)
1472 {
1473     if (w->event_handlers == nullptr)
1474         w->OnToolDown(widgetIndex, screenCoords);
1475     else if (w->event_handlers->tool_down != nullptr)
1476         w->event_handlers->tool_down(w, widgetIndex, screenCoords);
1477 }
1478 
window_event_tool_drag_call(rct_window * w,rct_widgetindex widgetIndex,const ScreenCoordsXY & screenCoords)1479 void window_event_tool_drag_call(rct_window* w, rct_widgetindex widgetIndex, const ScreenCoordsXY& screenCoords)
1480 {
1481     if (w->event_handlers == nullptr)
1482         w->OnToolDrag(widgetIndex, screenCoords);
1483     else if (w->event_handlers->tool_drag != nullptr)
1484         w->event_handlers->tool_drag(w, widgetIndex, screenCoords);
1485 }
1486 
window_event_tool_up_call(rct_window * w,rct_widgetindex widgetIndex,const ScreenCoordsXY & screenCoords)1487 void window_event_tool_up_call(rct_window* w, rct_widgetindex widgetIndex, const ScreenCoordsXY& screenCoords)
1488 {
1489     if (w->event_handlers == nullptr)
1490         w->OnToolUp(widgetIndex, screenCoords);
1491     else if (w->event_handlers->tool_up != nullptr)
1492         w->event_handlers->tool_up(w, widgetIndex, screenCoords);
1493 }
1494 
window_event_tool_abort_call(rct_window * w,rct_widgetindex widgetIndex)1495 void window_event_tool_abort_call(rct_window* w, rct_widgetindex widgetIndex)
1496 {
1497     if (w->event_handlers == nullptr)
1498         w->OnToolAbort(widgetIndex);
1499     else if (w->event_handlers->tool_abort != nullptr)
1500         w->event_handlers->tool_abort(w, widgetIndex);
1501 }
1502 
window_event_unknown_0E_call(rct_window * w)1503 void window_event_unknown_0E_call(rct_window* w)
1504 {
1505     if (w->event_handlers != nullptr)
1506         if (w->event_handlers->unknown_0E != nullptr)
1507             w->event_handlers->unknown_0E(w);
1508 }
1509 
window_get_scroll_size(rct_window * w,int32_t scrollIndex,int32_t * width,int32_t * height)1510 void window_get_scroll_size(rct_window* w, int32_t scrollIndex, int32_t* width, int32_t* height)
1511 {
1512     if (w->event_handlers == nullptr)
1513     {
1514         auto size = w->OnScrollGetSize(scrollIndex);
1515         if (width != nullptr)
1516             *width = size.width;
1517         if (height != nullptr)
1518             *height = size.height;
1519     }
1520     else if (w->event_handlers->get_scroll_size != nullptr)
1521     {
1522         w->event_handlers->get_scroll_size(w, scrollIndex, width, height);
1523     }
1524 }
1525 
window_event_scroll_mousedown_call(rct_window * w,int32_t scrollIndex,const ScreenCoordsXY & screenCoords)1526 void window_event_scroll_mousedown_call(rct_window* w, int32_t scrollIndex, const ScreenCoordsXY& screenCoords)
1527 {
1528     if (w->event_handlers == nullptr)
1529         w->OnScrollMouseDown(scrollIndex, screenCoords);
1530     else if (w->event_handlers->scroll_mousedown != nullptr)
1531         w->event_handlers->scroll_mousedown(w, scrollIndex, screenCoords);
1532 }
1533 
window_event_scroll_mousedrag_call(rct_window * w,int32_t scrollIndex,const ScreenCoordsXY & screenCoords)1534 void window_event_scroll_mousedrag_call(rct_window* w, int32_t scrollIndex, const ScreenCoordsXY& screenCoords)
1535 {
1536     if (w->event_handlers == nullptr)
1537         w->OnScrollMouseDrag(scrollIndex, screenCoords);
1538     else if (w->event_handlers->scroll_mousedrag != nullptr)
1539         w->event_handlers->scroll_mousedrag(w, scrollIndex, screenCoords);
1540 }
1541 
window_event_scroll_mouseover_call(rct_window * w,int32_t scrollIndex,const ScreenCoordsXY & screenCoords)1542 void window_event_scroll_mouseover_call(rct_window* w, int32_t scrollIndex, const ScreenCoordsXY& screenCoords)
1543 {
1544     if (w->event_handlers == nullptr)
1545         w->OnScrollMouseOver(scrollIndex, screenCoords);
1546     else if (w->event_handlers->scroll_mouseover != nullptr)
1547         w->event_handlers->scroll_mouseover(w, scrollIndex, screenCoords);
1548 }
1549 
window_event_textinput_call(rct_window * w,rct_widgetindex widgetIndex,char * text)1550 void window_event_textinput_call(rct_window* w, rct_widgetindex widgetIndex, char* text)
1551 {
1552     if (w->event_handlers == nullptr)
1553     {
1554         if (text != nullptr)
1555         {
1556             w->OnTextInput(widgetIndex, text);
1557         }
1558     }
1559     else if (w->event_handlers->text_input != nullptr)
1560     {
1561         w->event_handlers->text_input(w, widgetIndex, text);
1562     }
1563 }
1564 
window_event_viewport_rotate_call(rct_window * w)1565 void window_event_viewport_rotate_call(rct_window* w)
1566 {
1567     if (w->event_handlers == nullptr)
1568         w->OnViewportRotate();
1569     else if (w->event_handlers != nullptr)
1570         if (w->event_handlers->viewport_rotate != nullptr)
1571             w->event_handlers->viewport_rotate(w);
1572 }
1573 
window_event_unknown_15_call(rct_window * w,int32_t scrollIndex,int32_t scrollAreaType)1574 void window_event_unknown_15_call(rct_window* w, int32_t scrollIndex, int32_t scrollAreaType)
1575 {
1576     if (w->event_handlers != nullptr)
1577         if (w->event_handlers->unknown_15 != nullptr)
1578             w->event_handlers->unknown_15(w, scrollIndex, scrollAreaType);
1579 }
1580 
window_event_tooltip_call(rct_window * w,const rct_widgetindex widgetIndex,const rct_string_id fallback)1581 OpenRCT2String window_event_tooltip_call(rct_window* w, const rct_widgetindex widgetIndex, const rct_string_id fallback)
1582 {
1583     if (w->event_handlers == nullptr)
1584     {
1585         return w->OnTooltip(widgetIndex, fallback);
1586     }
1587 
1588     if (w->event_handlers->tooltip != nullptr)
1589     {
1590         return w->event_handlers->tooltip(w, widgetIndex, fallback);
1591     }
1592 
1593     return { fallback, {} };
1594 }
1595 
window_event_cursor_call(rct_window * w,rct_widgetindex widgetIndex,const ScreenCoordsXY & screenCoords)1596 CursorID window_event_cursor_call(rct_window* w, rct_widgetindex widgetIndex, const ScreenCoordsXY& screenCoords)
1597 {
1598     CursorID cursorId = CursorID::Arrow;
1599     if (w->event_handlers != nullptr)
1600         if (w->event_handlers->cursor != nullptr)
1601             w->event_handlers->cursor(w, widgetIndex, screenCoords, &cursorId);
1602     return cursorId;
1603 }
1604 
window_event_moved_call(rct_window * w,const ScreenCoordsXY & screenCoords)1605 void window_event_moved_call(rct_window* w, const ScreenCoordsXY& screenCoords)
1606 {
1607     if (w->event_handlers != nullptr)
1608         if (w->event_handlers->moved != nullptr)
1609             w->event_handlers->moved(w, screenCoords);
1610 }
1611 
window_event_invalidate_call(rct_window * w)1612 void window_event_invalidate_call(rct_window* w)
1613 {
1614     if (w->event_handlers == nullptr)
1615         w->OnPrepareDraw();
1616     else if (w->event_handlers->invalidate != nullptr)
1617         w->event_handlers->invalidate(w);
1618 }
1619 
window_event_paint_call(rct_window * w,rct_drawpixelinfo * dpi)1620 void window_event_paint_call(rct_window* w, rct_drawpixelinfo* dpi)
1621 {
1622     if (w->event_handlers == nullptr)
1623         w->OnDraw(*dpi);
1624     else if (w->event_handlers->paint != nullptr)
1625         w->event_handlers->paint(w, dpi);
1626 }
1627 
window_event_scroll_paint_call(rct_window * w,rct_drawpixelinfo * dpi,int32_t scrollIndex)1628 void window_event_scroll_paint_call(rct_window* w, rct_drawpixelinfo* dpi, int32_t scrollIndex)
1629 {
1630     if (w->event_handlers == nullptr)
1631         w->OnScrollDraw(scrollIndex, *dpi);
1632     else if (w->event_handlers->scroll_paint != nullptr)
1633         w->event_handlers->scroll_paint(w, dpi, scrollIndex);
1634 }
1635 
1636 /**
1637  *
1638  *  rct2: 0x006ED710
1639  * Called after a window resize to move windows if they
1640  * are going to be out of sight.
1641  */
window_relocate_windows(int32_t width,int32_t height)1642 void window_relocate_windows(int32_t width, int32_t height)
1643 {
1644     int32_t new_location = 8;
1645     window_visit_each([width, height, &new_location](rct_window* w) {
1646         // Work out if the window requires moving
1647         if (w->windowPos.x + 10 < width)
1648         {
1649             if (w->flags & (WF_STICK_TO_BACK | WF_STICK_TO_FRONT))
1650             {
1651                 if (w->windowPos.y - 22 < height)
1652                 {
1653                     return;
1654                 }
1655             }
1656             if (w->windowPos.y + 10 < height)
1657             {
1658                 return;
1659             }
1660         }
1661 
1662         // Calculate the new locations
1663         auto newWinPos = w->windowPos;
1664         w->windowPos = { new_location, new_location + TOP_TOOLBAR_HEIGHT + 1 };
1665 
1666         // Move the next new location so windows are not directly on top
1667         new_location += 8;
1668 
1669         // Adjust the viewport if required.
1670         if (w->viewport != nullptr)
1671         {
1672             w->viewport->pos -= newWinPos - w->windowPos;
1673         }
1674     });
1675 }
1676 
1677 /**
1678  * rct2: 0x0066B905
1679  */
window_resize_gui(int32_t width,int32_t height)1680 void window_resize_gui(int32_t width, int32_t height)
1681 {
1682     window_resize_gui_scenario_editor(width, height);
1683     if (gScreenFlags & SCREEN_FLAGS_EDITOR)
1684         return;
1685 
1686     rct_window* titleWind = window_find_by_class(WC_TITLE_MENU);
1687     if (titleWind != nullptr)
1688     {
1689         titleWind->windowPos.x = (width - titleWind->width) / 2;
1690         titleWind->windowPos.y = height - 182;
1691     }
1692 
1693     rct_window* exitWind = window_find_by_class(WC_TITLE_EXIT);
1694     if (exitWind != nullptr)
1695     {
1696         exitWind->windowPos.x = width - 40;
1697         exitWind->windowPos.y = height - 64;
1698     }
1699 
1700     rct_window* optionsWind = window_find_by_class(WC_TITLE_OPTIONS);
1701     if (optionsWind != nullptr)
1702     {
1703         optionsWind->windowPos.x = width - 80;
1704     }
1705 
1706     gfx_invalidate_screen();
1707 }
1708 
1709 /**
1710  * rct2: 0x0066F0DD
1711  */
window_resize_gui_scenario_editor(int32_t width,int32_t height)1712 void window_resize_gui_scenario_editor(int32_t width, int32_t height)
1713 {
1714     auto* mainWind = window_get_main();
1715     if (mainWind != nullptr)
1716     {
1717         rct_viewport* viewport = mainWind->viewport;
1718         mainWind->width = width;
1719         mainWind->height = height;
1720         viewport->width = width;
1721         viewport->height = height;
1722         viewport->view_width = width * viewport->zoom;
1723         viewport->view_height = height * viewport->zoom;
1724         if (mainWind->widgets != nullptr && mainWind->widgets[WC_MAIN_WINDOW__0].type == WindowWidgetType::Viewport)
1725         {
1726             mainWind->widgets[WC_MAIN_WINDOW__0].right = width;
1727             mainWind->widgets[WC_MAIN_WINDOW__0].bottom = height;
1728         }
1729     }
1730 
1731     rct_window* topWind = window_find_by_class(WC_TOP_TOOLBAR);
1732     if (topWind != nullptr)
1733     {
1734         topWind->width = std::max(640, width);
1735     }
1736 
1737     rct_window* bottomWind = window_find_by_class(WC_BOTTOM_TOOLBAR);
1738     if (bottomWind != nullptr)
1739     {
1740         bottomWind->windowPos.y = height - 32;
1741         bottomWind->width = std::max(640, width);
1742     }
1743 }
1744 
1745 /* Based on rct2: 0x6987ED and another version from window_park */
window_align_tabs(rct_window * w,rct_widgetindex start_tab_id,rct_widgetindex end_tab_id)1746 void window_align_tabs(rct_window* w, rct_widgetindex start_tab_id, rct_widgetindex end_tab_id)
1747 {
1748     int32_t i, x = w->widgets[start_tab_id].left;
1749     int32_t tab_width = w->widgets[start_tab_id].width();
1750 
1751     for (i = start_tab_id; i <= end_tab_id; i++)
1752     {
1753         if (!(w->disabled_widgets & (1LL << i)))
1754         {
1755             auto& widget = w->widgets[i];
1756             widget.left = x;
1757             widget.right = x + tab_width;
1758             x += tab_width + 1;
1759         }
1760     }
1761 }
1762 
1763 /**
1764  *
1765  *  rct2: 0x006CBCC3
1766  */
window_close_construction_windows()1767 void window_close_construction_windows()
1768 {
1769     window_close_by_class(WC_RIDE_CONSTRUCTION);
1770     window_close_by_class(WC_FOOTPATH);
1771     window_close_by_class(WC_TRACK_DESIGN_LIST);
1772     window_close_by_class(WC_TRACK_DESIGN_PLACE);
1773 }
1774 
1775 /**
1776  * Update zoom based volume attenuation for ride music and clear music list.
1777  *  rct2: 0x006BC348
1778  */
window_update_viewport_ride_music()1779 void window_update_viewport_ride_music()
1780 {
1781     OpenRCT2::RideAudio::ClearAllViewportInstances();
1782     g_music_tracking_viewport = nullptr;
1783 
1784     for (auto it = g_window_list.rbegin(); it != g_window_list.rend(); it++)
1785     {
1786         auto w = it->get();
1787         auto viewport = w->viewport;
1788         if (viewport == nullptr || !(viewport->flags & VIEWPORT_FLAG_SOUND_ON))
1789             continue;
1790 
1791         g_music_tracking_viewport = viewport;
1792         gWindowAudioExclusive = w;
1793 
1794         if (viewport->zoom <= 0)
1795             OpenRCT2::Audio::gVolumeAdjustZoom = 0;
1796         else if (viewport->zoom == 1)
1797             OpenRCT2::Audio::gVolumeAdjustZoom = 30;
1798         else
1799             OpenRCT2::Audio::gVolumeAdjustZoom = 60;
1800         break;
1801     }
1802 }
1803 
window_snap_left(rct_window * w,int32_t proximity)1804 static void window_snap_left(rct_window* w, int32_t proximity)
1805 {
1806     const auto* mainWindow = window_get_main();
1807     auto wBottom = w->windowPos.y + w->height;
1808     auto wLeftProximity = w->windowPos.x - (proximity * 2);
1809     auto wRightProximity = w->windowPos.x + (proximity * 2);
1810     auto rightMost = INT32_MIN;
1811 
1812     window_visit_each([&](rct_window* w2) {
1813         if (w2 == w || w2 == mainWindow)
1814             return;
1815 
1816         auto right = w2->windowPos.x + w2->width;
1817 
1818         if (wBottom < w2->windowPos.y || w->windowPos.y > w2->windowPos.y + w2->height)
1819             return;
1820 
1821         if (right < wLeftProximity || right > wRightProximity)
1822             return;
1823 
1824         rightMost = std::max(rightMost, right);
1825     });
1826 
1827     if (0 >= wLeftProximity && 0 <= wRightProximity)
1828         rightMost = std::max(rightMost, 0);
1829 
1830     if (rightMost != INT32_MIN)
1831         w->windowPos.x = rightMost;
1832 }
1833 
window_snap_top(rct_window * w,int32_t proximity)1834 static void window_snap_top(rct_window* w, int32_t proximity)
1835 {
1836     const auto* mainWindow = window_get_main();
1837     auto wRight = w->windowPos.x + w->width;
1838     auto wTopProximity = w->windowPos.y - (proximity * 2);
1839     auto wBottomProximity = w->windowPos.y + (proximity * 2);
1840     auto bottomMost = INT32_MIN;
1841 
1842     window_visit_each([&](rct_window* w2) {
1843         if (w2 == w || w2 == mainWindow)
1844             return;
1845 
1846         auto bottom = w2->windowPos.y + w2->height;
1847 
1848         if (wRight < w2->windowPos.x || w->windowPos.x > w2->windowPos.x + w2->width)
1849             return;
1850 
1851         if (bottom < wTopProximity || bottom > wBottomProximity)
1852             return;
1853 
1854         bottomMost = std::max(bottomMost, bottom);
1855     });
1856 
1857     if (0 >= wTopProximity && 0 <= wBottomProximity)
1858         bottomMost = std::max(bottomMost, 0);
1859 
1860     if (bottomMost != INT32_MIN)
1861         w->windowPos.y = bottomMost;
1862 }
1863 
window_snap_right(rct_window * w,int32_t proximity)1864 static void window_snap_right(rct_window* w, int32_t proximity)
1865 {
1866     const auto* mainWindow = window_get_main();
1867     auto wRight = w->windowPos.x + w->width;
1868     auto wBottom = w->windowPos.y + w->height;
1869     auto wLeftProximity = wRight - (proximity * 2);
1870     auto wRightProximity = wRight + (proximity * 2);
1871     auto leftMost = INT32_MAX;
1872 
1873     window_visit_each([&](rct_window* w2) {
1874         if (w2 == w || w2 == mainWindow)
1875             return;
1876 
1877         if (wBottom < w2->windowPos.y || w->windowPos.y > w2->windowPos.y + w2->height)
1878             return;
1879 
1880         if (w2->windowPos.x < wLeftProximity || w2->windowPos.x > wRightProximity)
1881             return;
1882 
1883         leftMost = std::min<int32_t>(leftMost, w2->windowPos.x);
1884     });
1885 
1886     auto screenWidth = context_get_width();
1887     if (screenWidth >= wLeftProximity && screenWidth <= wRightProximity)
1888         leftMost = std::min(leftMost, screenWidth);
1889 
1890     if (leftMost != INT32_MAX)
1891         w->windowPos.x = leftMost - w->width;
1892 }
1893 
window_snap_bottom(rct_window * w,int32_t proximity)1894 static void window_snap_bottom(rct_window* w, int32_t proximity)
1895 {
1896     const auto* mainWindow = window_get_main();
1897     auto wRight = w->windowPos.x + w->width;
1898     auto wBottom = w->windowPos.y + w->height;
1899     auto wTopProximity = wBottom - (proximity * 2);
1900     auto wBottomProximity = wBottom + (proximity * 2);
1901     auto topMost = INT32_MAX;
1902 
1903     window_visit_each([&](rct_window* w2) {
1904         if (w2 == w || w2 == mainWindow)
1905             return;
1906 
1907         if (wRight < w2->windowPos.x || w->windowPos.x > w2->windowPos.x + w2->width)
1908             return;
1909 
1910         if (w2->windowPos.y < wTopProximity || w2->windowPos.y > wBottomProximity)
1911             return;
1912 
1913         topMost = std::min<int32_t>(topMost, w2->windowPos.y);
1914     });
1915 
1916     auto screenHeight = context_get_height();
1917     if (screenHeight >= wTopProximity && screenHeight <= wBottomProximity)
1918         topMost = std::min(topMost, screenHeight);
1919 
1920     if (topMost != INT32_MAX)
1921         w->windowPos.y = topMost - w->height;
1922 }
1923 
window_move_and_snap(rct_window * w,ScreenCoordsXY newWindowCoords,int32_t snapProximity)1924 void window_move_and_snap(rct_window* w, ScreenCoordsXY newWindowCoords, int32_t snapProximity)
1925 {
1926     auto originalPos = w->windowPos;
1927     int32_t minY = (gScreenFlags & SCREEN_FLAGS_TITLE_DEMO) ? 1 : TOP_TOOLBAR_HEIGHT + 2;
1928 
1929     newWindowCoords.y = std::clamp(newWindowCoords.y, minY, context_get_height() - 34);
1930 
1931     if (snapProximity > 0)
1932     {
1933         w->windowPos = newWindowCoords;
1934 
1935         window_snap_right(w, snapProximity);
1936         window_snap_bottom(w, snapProximity);
1937         window_snap_left(w, snapProximity);
1938         window_snap_top(w, snapProximity);
1939 
1940         if (w->windowPos == originalPos)
1941             return;
1942 
1943         newWindowCoords = w->windowPos;
1944         w->windowPos = originalPos;
1945     }
1946 
1947     window_set_position(w, newWindowCoords);
1948 }
1949 
window_can_resize(rct_window * w)1950 int32_t window_can_resize(rct_window* w)
1951 {
1952     return (w->flags & WF_RESIZABLE) && (w->min_width != w->max_width || w->min_height != w->max_height);
1953 }
1954 
1955 /**
1956  *
1957  *  rct2: 0x006EE3C3
1958  */
textinput_cancel()1959 void textinput_cancel()
1960 {
1961     window_close_by_class(WC_TEXTINPUT);
1962 }
1963 
window_start_textbox(rct_window * call_w,rct_widgetindex call_widget,rct_string_id existing_text,char * existing_args,int32_t maxLength)1964 void window_start_textbox(
1965     rct_window* call_w, rct_widgetindex call_widget, rct_string_id existing_text, char* existing_args, int32_t maxLength)
1966 {
1967     if (gUsingWidgetTextBox)
1968         window_cancel_textbox();
1969 
1970     gUsingWidgetTextBox = true;
1971     gCurrentTextBox.window.classification = call_w->classification;
1972     gCurrentTextBox.window.number = call_w->number;
1973     gCurrentTextBox.widget_index = call_widget;
1974     gTextBoxFrameNo = 0;
1975 
1976     gMaxTextBoxInputLength = maxLength;
1977 
1978     window_close_by_class(WC_TEXTINPUT);
1979 
1980     // Clear the text input buffer
1981     std::fill_n(gTextBoxInput, maxLength, 0x00);
1982 
1983     // Enter in the text input buffer any existing
1984     // text.
1985     if (existing_text != STR_NONE)
1986         format_string(gTextBoxInput, TEXT_INPUT_SIZE, existing_text, &existing_args);
1987 
1988     // In order to prevent strings that exceed the maxLength
1989     // from crashing the game.
1990     gTextBoxInput[maxLength - 1] = '\0';
1991 
1992     gTextInput = context_start_text_input(gTextBoxInput, maxLength);
1993 }
1994 
window_cancel_textbox()1995 void window_cancel_textbox()
1996 {
1997     if (gUsingWidgetTextBox)
1998     {
1999         rct_window* w = window_find_by_number(gCurrentTextBox.window.classification, gCurrentTextBox.window.number);
2000         if (w != nullptr)
2001         {
2002             window_event_textinput_call(w, gCurrentTextBox.widget_index, nullptr);
2003         }
2004         gCurrentTextBox.window.classification = WC_NULL;
2005         gCurrentTextBox.window.number = 0;
2006         context_stop_text_input();
2007         gUsingWidgetTextBox = false;
2008         if (w != nullptr)
2009         {
2010             widget_invalidate(w, gCurrentTextBox.widget_index);
2011         }
2012         gCurrentTextBox.widget_index = static_cast<uint16_t>(WindowWidgetType::Last);
2013     }
2014 }
2015 
window_update_textbox_caret()2016 void window_update_textbox_caret()
2017 {
2018     gTextBoxFrameNo++;
2019     if (gTextBoxFrameNo > 30)
2020         gTextBoxFrameNo = 0;
2021 }
2022 
window_update_textbox()2023 void window_update_textbox()
2024 {
2025     if (gUsingWidgetTextBox)
2026     {
2027         gTextBoxFrameNo = 0;
2028         rct_window* w = window_find_by_number(gCurrentTextBox.window.classification, gCurrentTextBox.window.number);
2029         widget_invalidate(w, gCurrentTextBox.widget_index);
2030         window_event_textinput_call(w, gCurrentTextBox.widget_index, gTextBoxInput);
2031     }
2032 }
2033 
window_is_visible(rct_window * w)2034 bool window_is_visible(rct_window* w)
2035 {
2036     // w->visibility is used to prevent repeat calculations within an iteration by caching the result
2037     if (w == nullptr)
2038         return false;
2039 
2040     if (w->visibility == VisibilityCache::Visible)
2041         return true;
2042     if (w->visibility == VisibilityCache::Covered)
2043         return false;
2044 
2045     // only consider viewports, consider the main window always visible
2046     if (w->viewport == nullptr || w->classification == WC_MAIN_WINDOW)
2047     {
2048         // default to previous behaviour
2049         w->visibility = VisibilityCache::Visible;
2050         return true;
2051     }
2052 
2053     // start from the window above the current
2054     auto itPos = window_get_iterator(w);
2055     for (auto it = std::next(itPos); it != g_window_list.end(); it++)
2056     {
2057         auto& w_other = *(*it);
2058 
2059         // if covered by a higher window, no rendering needed
2060         if (w_other.windowPos.x <= w->windowPos.x && w_other.windowPos.y <= w->windowPos.y
2061             && w_other.windowPos.x + w_other.width >= w->windowPos.x + w->width
2062             && w_other.windowPos.y + w_other.height >= w->windowPos.y + w->height)
2063         {
2064             w->visibility = VisibilityCache::Covered;
2065             w->viewport->visibility = VisibilityCache::Covered;
2066             return false;
2067         }
2068     }
2069 
2070     // default to previous behaviour
2071     w->visibility = VisibilityCache::Visible;
2072     w->viewport->visibility = VisibilityCache::Visible;
2073     return true;
2074 }
2075 
2076 /**
2077  *
2078  *  rct2: 0x006E7499
2079  * left (ax)
2080  * top (bx)
2081  * right (dx)
2082  * bottom (bp)
2083  */
window_draw_all(rct_drawpixelinfo * dpi,int32_t left,int32_t top,int32_t right,int32_t bottom)2084 void window_draw_all(rct_drawpixelinfo* dpi, int32_t left, int32_t top, int32_t right, int32_t bottom)
2085 {
2086     auto windowDPI = dpi->Crop({ left, top }, { right - left, bottom - top });
2087     window_visit_each([&windowDPI, left, top, right, bottom](rct_window* w) {
2088         if (w->flags & WF_TRANSPARENT)
2089             return;
2090         if (right <= w->windowPos.x || bottom <= w->windowPos.y)
2091             return;
2092         if (left >= w->windowPos.x + w->width || top >= w->windowPos.y + w->height)
2093             return;
2094         window_draw(&windowDPI, w, left, top, right, bottom);
2095     });
2096 }
2097 
window_get_previous_viewport(rct_viewport * current)2098 rct_viewport* window_get_previous_viewport(rct_viewport* current)
2099 {
2100     bool foundPrevious = (current == nullptr);
2101     for (auto it = g_window_list.rbegin(); it != g_window_list.rend(); it++)
2102     {
2103         auto& w = **it;
2104         if (w.viewport != nullptr)
2105         {
2106             if (foundPrevious)
2107             {
2108                 return w.viewport;
2109             }
2110             if (w.viewport == current)
2111             {
2112                 foundPrevious = true;
2113             }
2114         }
2115     }
2116     return nullptr;
2117 }
2118 
window_reset_visibilities()2119 void window_reset_visibilities()
2120 {
2121     // reset window visibility status to unknown
2122     window_visit_each([](rct_window* w) {
2123         w->visibility = VisibilityCache::Unknown;
2124         if (w->viewport != nullptr)
2125         {
2126             w->viewport->visibility = VisibilityCache::Unknown;
2127         }
2128     });
2129 }
2130 
window_init_all()2131 void window_init_all()
2132 {
2133     window_close_all_except_flags(0);
2134 }
2135 
window_follow_sprite(rct_window * w,size_t spriteIndex)2136 void window_follow_sprite(rct_window* w, size_t spriteIndex)
2137 {
2138     if (spriteIndex < MAX_ENTITIES || spriteIndex == SPRITE_INDEX_NULL)
2139     {
2140         w->viewport_smart_follow_sprite = static_cast<uint16_t>(spriteIndex);
2141     }
2142 }
2143 
window_unfollow_sprite(rct_window * w)2144 void window_unfollow_sprite(rct_window* w)
2145 {
2146     w->viewport_smart_follow_sprite = SPRITE_INDEX_NULL;
2147     w->viewport_target_sprite = SPRITE_INDEX_NULL;
2148 }
2149 
window_get_viewport(rct_window * w)2150 rct_viewport* window_get_viewport(rct_window* w)
2151 {
2152     if (w == nullptr)
2153     {
2154         return nullptr;
2155     }
2156 
2157     return w->viewport;
2158 }
2159 
window_get_listening()2160 rct_window* window_get_listening()
2161 {
2162     for (auto it = g_window_list.rbegin(); it != g_window_list.rend(); it++)
2163     {
2164         auto& w = **it;
2165         auto viewport = w.viewport;
2166         if (viewport != nullptr)
2167         {
2168             if (viewport->flags & VIEWPORT_FLAG_SOUND_ON)
2169             {
2170                 return &w;
2171             }
2172         }
2173     }
2174     return nullptr;
2175 }
2176 
window_get_classification(rct_window * window)2177 rct_windowclass window_get_classification(rct_window* window)
2178 {
2179     return window->classification;
2180 }
2181 
2182 /**
2183  *
2184  *  rct2: 0x006EAF26
2185  */
WidgetScrollUpdateThumbs(rct_window * w,rct_widgetindex widget_index)2186 void WidgetScrollUpdateThumbs(rct_window* w, rct_widgetindex widget_index)
2187 {
2188     const auto& widget = w->widgets[widget_index];
2189     auto& scroll = w->scrolls[window_get_scroll_data_index(w, widget_index)];
2190 
2191     if (scroll.flags & HSCROLLBAR_VISIBLE)
2192     {
2193         int32_t view_size = widget.width() - 21;
2194         if (scroll.flags & VSCROLLBAR_VISIBLE)
2195             view_size -= 11;
2196         int32_t x = scroll.h_left * view_size;
2197         if (scroll.h_right != 0)
2198             x /= scroll.h_right;
2199         scroll.h_thumb_left = x + 11;
2200 
2201         x = widget.width() - 2;
2202         if (scroll.flags & VSCROLLBAR_VISIBLE)
2203             x -= 11;
2204         x += scroll.h_left;
2205         if (scroll.h_right != 0)
2206             x = (x * view_size) / scroll.h_right;
2207         x += 11;
2208         view_size += 10;
2209         scroll.h_thumb_right = std::min(x, view_size);
2210 
2211         if (scroll.h_thumb_right - scroll.h_thumb_left < 20)
2212         {
2213             double barPosition = (scroll.h_thumb_right * 1.0) / view_size;
2214 
2215             scroll.h_thumb_left = static_cast<uint16_t>(std::lround(scroll.h_thumb_left - (20 * barPosition)));
2216             scroll.h_thumb_right = static_cast<uint16_t>(std::lround(scroll.h_thumb_right + (20 * (1 - barPosition))));
2217         }
2218     }
2219 
2220     if (scroll.flags & VSCROLLBAR_VISIBLE)
2221     {
2222         int32_t view_size = widget.height() - 21;
2223         if (scroll.flags & HSCROLLBAR_VISIBLE)
2224             view_size -= 11;
2225         int32_t y = scroll.v_top * view_size;
2226         if (scroll.v_bottom != 0)
2227             y /= scroll.v_bottom;
2228         scroll.v_thumb_top = y + 11;
2229 
2230         y = widget.height() - 2;
2231         if (scroll.flags & HSCROLLBAR_VISIBLE)
2232             y -= 11;
2233         y += scroll.v_top;
2234         if (scroll.v_bottom != 0)
2235             y = (y * view_size) / scroll.v_bottom;
2236         y += 11;
2237         view_size += 10;
2238         scroll.v_thumb_bottom = std::min(y, view_size);
2239 
2240         if (scroll.v_thumb_bottom - scroll.v_thumb_top < 20)
2241         {
2242             double barPosition = (scroll.v_thumb_bottom * 1.0) / view_size;
2243 
2244             scroll.v_thumb_top = static_cast<uint16_t>(std::lround(scroll.v_thumb_top - (20 * barPosition)));
2245             scroll.v_thumb_bottom = static_cast<uint16_t>(std::lround(scroll.v_thumb_bottom + (20 * (1 - barPosition))));
2246         }
2247     }
2248 }
2249