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 = ©
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