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 <algorithm>
11 #include <cmath>
12 #include <iterator>
13 #include <openrct2-ui/interface/Dropdown.h>
14 #include <openrct2-ui/interface/Viewport.h>
15 #include <openrct2-ui/interface/Widget.h>
16 #include <openrct2-ui/interface/Window.h>
17 #include <openrct2-ui/windows/Window.h>
18 #include <openrct2/Context.h>
19 #include <openrct2/Game.h>
20 #include <openrct2/Input.h>
21 #include <openrct2/OpenRCT2.h>
22 #include <openrct2/audio/audio.h>
23 #include <openrct2/config/Config.h>
24 #include <openrct2/interface/Chat.h>
25 #include <openrct2/interface/Cursors.h>
26 #include <openrct2/interface/InteractiveConsole.h>
27 #include <openrct2/localisation/Localisation.h>
28 #include <openrct2/platform/platform.h>
29 #include <openrct2/ride/RideData.h>
30 #include <openrct2/scenario/Scenario.h>
31 #include <openrct2/world/Banner.h>
32 #include <openrct2/world/Map.h>
33 #include <openrct2/world/Scenery.h>
34 
35 struct RCTMouseData
36 {
37     uint32_t x;
38     uint32_t y;
39     MouseState state;
40 };
41 
42 static RCTMouseData _mouseInputQueue[64];
43 static uint8_t _mouseInputQueueReadIndex = 0;
44 static uint8_t _mouseInputQueueWriteIndex = 0;
45 
46 static uint32_t _ticksSinceDragStart;
47 static widget_ref _dragWidget;
48 static uint8_t _dragScrollIndex;
49 static int32_t _originalWindowWidth;
50 static int32_t _originalWindowHeight;
51 
52 static uint8_t _currentScrollIndex;
53 static uint8_t _currentScrollArea;
54 
55 ScreenCoordsXY gInputDragLast;
56 
57 uint16_t gTooltipTimeout;
58 widget_ref gTooltipWidget;
59 ScreenCoordsXY gTooltipCursor;
60 
61 static int16_t _clickRepeatTicks;
62 
63 static MouseState GameGetNextInput(ScreenCoordsXY& screenCoords);
64 static void InputWidgetOver(const ScreenCoordsXY& screenCoords, rct_window* w, rct_widgetindex widgetIndex);
65 static void InputWidgetOverChangeCheck(rct_windowclass windowClass, rct_windownumber windowNumber, rct_widgetindex widgetIndex);
66 static void InputWidgetOverFlatbuttonInvalidate();
67 void ProcessMouseOver(const ScreenCoordsXY& screenCoords);
68 void ProcessMouseTool(const ScreenCoordsXY& screenCoords);
69 void InvalidateScroll();
70 static RCTMouseData* GetMouseInput();
71 void TileElementRightClick(int32_t type, TileElement* tileElement, const ScreenCoordsXY& screenCoords);
72 static void GameHandleInputMouse(const ScreenCoordsXY& screenCoords, MouseState state);
73 static void InputWidgetLeft(const ScreenCoordsXY& screenCoords, rct_window* w, rct_widgetindex widgetIndex);
74 void InputStateWidgetPressed(
75     const ScreenCoordsXY& screenCoords, MouseState state, rct_widgetindex widgetIndex, rct_window* w, rct_widget* widget);
76 void SetCursor(CursorID cursor_id);
77 static void InputWindowPositionContinue(
78     rct_window* w, const ScreenCoordsXY& lastScreenCoords, const ScreenCoordsXY& newScreenCoords);
79 static void InputWindowPositionEnd(rct_window* w, const ScreenCoordsXY& screenCoords);
80 static void InputWindowResizeBegin(rct_window* w, rct_widgetindex widgetIndex, const ScreenCoordsXY& screenCoords);
81 static void InputWindowResizeContinue(rct_window* w, const ScreenCoordsXY& screenCoords);
82 static void InputWindowResizeEnd();
83 static void InputViewportDragBegin(rct_window* w);
84 static void InputViewportDragContinue();
85 static void InputViewportDragEnd();
86 static void InputScrollBegin(rct_window* w, rct_widgetindex widgetIndex, const ScreenCoordsXY& screenCoords);
87 static void InputScrollContinue(rct_window* w, rct_widgetindex widgetIndex, const ScreenCoordsXY& screenCoords);
88 static void InputScrollEnd();
89 static void InputScrollPartUpdateHThumb(rct_window* w, rct_widgetindex widgetIndex, int32_t x, int32_t scroll_id);
90 static void InputScrollPartUpdateHLeft(rct_window* w, rct_widgetindex widgetIndex, int32_t scroll_id);
91 static void InputScrollPartUpdateHRight(rct_window* w, rct_widgetindex widgetIndex, int32_t scroll_id);
92 static void InputScrollPartUpdateVThumb(rct_window* w, rct_widgetindex widgetIndex, int32_t y, int32_t scroll_id);
93 static void InputScrollPartUpdateVTop(rct_window* w, rct_widgetindex widgetIndex, int32_t scroll_id);
94 static void InputScrollPartUpdateVBottom(rct_window* w, rct_widgetindex widgetIndex, int32_t scroll_id);
95 static void InputUpdateTooltip(rct_window* w, rct_widgetindex widgetIndex, const ScreenCoordsXY& screenCoords);
96 
97 #pragma region Mouse input
98 
99 /**
100  *
101  *  rct2: 0x006EA627
102  */
GameHandleInput()103 void GameHandleInput()
104 {
105     window_visit_each([](rct_window* w) { window_event_periodic_update_call(w); });
106 
107     InvalidateAllWindowsAfterInput();
108 
109     MouseState state;
110     ScreenCoordsXY screenCoords;
111     while ((state = GameGetNextInput(screenCoords)) != MouseState::Released)
112     {
113         GameHandleInputMouse(screenCoords, state);
114     }
115 
116     if (_inputFlags & INPUT_FLAG_5)
117     {
118         GameHandleInputMouse(screenCoords, state);
119     }
120     else
121     {
122         int32_t screenWidth = context_get_width();
123         int32_t screenHeight = context_get_height();
124         screenCoords.x = std::clamp(screenCoords.x, 0, screenWidth - 1);
125         screenCoords.y = std::clamp(screenCoords.y, 0, screenHeight - 1);
126 
127         GameHandleInputMouse(screenCoords, state);
128         ProcessMouseOver(screenCoords);
129         ProcessMouseTool(screenCoords);
130     }
131 
132     window_visit_each([](rct_window* w) { window_event_unknown_08_call(w); });
133 }
134 
135 /**
136  *
137  *  rct2: 0x006E83C7
138  */
GameGetNextInput(ScreenCoordsXY & screenCoords)139 static MouseState GameGetNextInput(ScreenCoordsXY& screenCoords)
140 {
141     RCTMouseData* input = GetMouseInput();
142     if (input == nullptr)
143     {
144         const CursorState* cursorState = context_get_cursor_state();
145         screenCoords = cursorState->position;
146         return MouseState::Released;
147     }
148 
149     screenCoords.x = input->x;
150     screenCoords.y = input->y;
151     return input->state;
152 }
153 
154 /**
155  *
156  *  rct2: 0x00407074
157  */
GetMouseInput()158 static RCTMouseData* GetMouseInput()
159 {
160     // Check if that location has been written to yet
161     if (_mouseInputQueueReadIndex == _mouseInputQueueWriteIndex)
162     {
163         return nullptr;
164     }
165 
166     RCTMouseData* result = &_mouseInputQueue[_mouseInputQueueReadIndex];
167     _mouseInputQueueReadIndex = (_mouseInputQueueReadIndex + 1) % std::size(_mouseInputQueue);
168     return result;
169 }
170 
171 /**
172  *
173  *  rct2: 0x006E957F
174  */
InputScrollDragBegin(const ScreenCoordsXY & screenCoords,rct_window * w,rct_widgetindex widgetIndex)175 static void InputScrollDragBegin(const ScreenCoordsXY& screenCoords, rct_window* w, rct_widgetindex widgetIndex)
176 {
177     _inputState = InputState::ScrollRight;
178     gInputDragLast = screenCoords;
179     _dragWidget.window_classification = w->classification;
180     _dragWidget.window_number = w->number;
181     _dragWidget.widget_index = widgetIndex;
182     _ticksSinceDragStart = 0;
183 
184     _dragScrollIndex = window_get_scroll_data_index(w, widgetIndex);
185     context_hide_cursor();
186 }
187 
188 /**
189  * Based on (heavily changed)
190  *  rct2: 0x006E9E0E,  0x006E9ED0
191  */
InputScrollDragContinue(const ScreenCoordsXY & screenCoords,rct_window * w)192 static void InputScrollDragContinue(const ScreenCoordsXY& screenCoords, rct_window* w)
193 {
194     rct_widgetindex widgetIndex = _dragWidget.widget_index;
195     uint8_t scrollIndex = _dragScrollIndex;
196 
197     const auto& widget = w->widgets[widgetIndex];
198     auto& scroll = w->scrolls[scrollIndex];
199 
200     ScreenCoordsXY differentialCoords = screenCoords - gInputDragLast;
201 
202     if (scroll.flags & HSCROLLBAR_VISIBLE)
203     {
204         int16_t size = widget.width() - 1;
205         if (scroll.flags & VSCROLLBAR_VISIBLE)
206             size -= 11;
207         size = std::max(0, scroll.h_right - size);
208         scroll.h_left = std::min<uint16_t>(std::max(0, scroll.h_left + differentialCoords.x), size);
209     }
210 
211     if (scroll.flags & VSCROLLBAR_VISIBLE)
212     {
213         int16_t size = widget.height() - 1;
214         if (scroll.flags & HSCROLLBAR_VISIBLE)
215             size -= 11;
216         size = std::max(0, scroll.v_bottom - size);
217         scroll.v_top = std::min<uint16_t>(std::max(0, scroll.v_top + differentialCoords.y), size);
218     }
219 
220     WidgetScrollUpdateThumbs(w, widgetIndex);
221     window_invalidate_by_number(w->classification, w->number);
222 
223     ScreenCoordsXY fixedCursorPosition = { static_cast<int32_t>(std::ceil(gInputDragLast.x * gConfigGeneral.window_scale)),
224                                            static_cast<int32_t>(std::ceil(gInputDragLast.y * gConfigGeneral.window_scale)) };
225 
226     context_set_cursor_position(fixedCursorPosition);
227 }
228 
229 /**
230  *
231  *  rct2: 0x006E8ACB
232  */
InputScrollRight(const ScreenCoordsXY & screenCoords,MouseState state)233 static void InputScrollRight(const ScreenCoordsXY& screenCoords, MouseState state)
234 {
235     rct_window* w = window_find_by_number(_dragWidget.window_classification, _dragWidget.window_number);
236     if (w == nullptr)
237     {
238         context_show_cursor();
239         _inputState = InputState::Reset;
240         return;
241     }
242 
243     switch (state)
244     {
245         case MouseState::Released:
246             _ticksSinceDragStart += gCurrentDeltaTime;
247             if (screenCoords.x != 0 || screenCoords.y != 0)
248             {
249                 _ticksSinceDragStart = 1000;
250                 InputScrollDragContinue(screenCoords, w);
251             }
252             break;
253         case MouseState::RightRelease:
254             _inputState = InputState::Reset;
255             context_show_cursor();
256             break;
257         case MouseState::LeftPress:
258         case MouseState::LeftRelease:
259         case MouseState::RightPress:
260             // Function only handles right button, so it's the only one relevant
261             break;
262     }
263 }
264 
265 /**
266  *
267  *  rct2: 0x006E8655
268  */
GameHandleInputMouse(const ScreenCoordsXY & screenCoords,MouseState state)269 static void GameHandleInputMouse(const ScreenCoordsXY& screenCoords, MouseState state)
270 {
271     rct_window* w;
272     rct_widget* widget;
273     rct_widgetindex widgetIndex;
274 
275     // Get window and widget under cursor position
276     w = window_find_from_point(screenCoords);
277     widgetIndex = w == nullptr ? -1 : window_find_widget_from_point(w, screenCoords);
278     widget = widgetIndex == -1 ? nullptr : &w->widgets[widgetIndex];
279 
280     switch (_inputState)
281     {
282         case InputState::Reset:
283             window_tooltip_reset(screenCoords);
284             // fall-through
285         case InputState::Normal:
286             switch (state)
287             {
288                 case MouseState::Released:
289                     InputWidgetOver(screenCoords, w, widgetIndex);
290                     break;
291                 case MouseState::LeftPress:
292                     InputWidgetLeft(screenCoords, w, widgetIndex);
293                     break;
294                 case MouseState::RightPress:
295                     window_close_by_class(WC_TOOLTIP);
296 
297                     if (w != nullptr)
298                     {
299                         w = window_bring_to_front(w);
300                     }
301 
302                     if (widgetIndex != -1)
303                     {
304                         switch (widget->type)
305                         {
306                             case WindowWidgetType::Viewport:
307                                 if (!(gScreenFlags & (SCREEN_FLAGS_TRACK_MANAGER | SCREEN_FLAGS_TITLE_DEMO)))
308                                 {
309                                     InputViewportDragBegin(w);
310                                 }
311                                 break;
312                             case WindowWidgetType::Scroll:
313                                 InputScrollDragBegin(screenCoords, w, widgetIndex);
314                                 break;
315                             default:
316                                 break;
317                         }
318                     }
319                     break;
320                 case MouseState::LeftRelease:
321                 case MouseState::RightRelease:
322                     // In this switch only button presses are relevant
323                     break;
324             }
325             break;
326         case InputState::WidgetPressed:
327             InputStateWidgetPressed(screenCoords, state, widgetIndex, w, widget);
328             break;
329         case InputState::PositioningWindow:
330             w = window_find_by_number(_dragWidget.window_classification, _dragWidget.window_number);
331             if (w == nullptr)
332             {
333                 _inputState = InputState::Reset;
334             }
335             else
336             {
337                 InputWindowPositionContinue(w, gInputDragLast, screenCoords);
338                 if (state == MouseState::LeftRelease)
339                 {
340                     InputWindowPositionEnd(w, screenCoords);
341                 }
342             }
343             break;
344         case InputState::ViewportRight:
345             if (state == MouseState::Released)
346             {
347                 InputViewportDragContinue();
348             }
349             else if (state == MouseState::RightRelease)
350             {
351                 InputViewportDragEnd();
352                 if (_ticksSinceDragStart < 500)
353                 {
354                     // If the user pressed the right mouse button for less than 500 ticks, interpret as right click
355                     ViewportInteractionRightClick(screenCoords);
356                 }
357             }
358             break;
359         case InputState::DropdownActive:
360             InputStateWidgetPressed(screenCoords, state, widgetIndex, w, widget);
361             break;
362         case InputState::ViewportLeft:
363             w = window_find_by_number(_dragWidget.window_classification, _dragWidget.window_number);
364             if (w == nullptr)
365             {
366                 _inputState = InputState::Reset;
367                 break;
368             }
369 
370             switch (state)
371             {
372                 case MouseState::Released:
373                     if (w->viewport == nullptr)
374                     {
375                         _inputState = InputState::Reset;
376                         break;
377                     }
378 
379                     if (w->classification != _dragWidget.window_classification || w->number != _dragWidget.window_number
380                         || !(_inputFlags & INPUT_FLAG_TOOL_ACTIVE))
381                     {
382                         break;
383                     }
384 
385                     w = window_find_by_number(gCurrentToolWidget.window_classification, gCurrentToolWidget.window_number);
386                     if (w == nullptr)
387                     {
388                         break;
389                     }
390 
391                     window_event_tool_drag_call(w, gCurrentToolWidget.widget_index, screenCoords);
392                     break;
393                 case MouseState::LeftRelease:
394                     _inputState = InputState::Reset;
395                     if (_dragWidget.window_number == w->number)
396                     {
397                         if ((_inputFlags & INPUT_FLAG_TOOL_ACTIVE))
398                         {
399                             w = window_find_by_number(
400                                 gCurrentToolWidget.window_classification, gCurrentToolWidget.window_number);
401                             if (w != nullptr)
402                             {
403                                 window_event_tool_up_call(w, gCurrentToolWidget.widget_index, screenCoords);
404                             }
405                         }
406                         else if (!(_inputFlags & INPUT_FLAG_4))
407                         {
408                             ViewportInteractionLeftClick(screenCoords);
409                         }
410                     }
411                     break;
412                 case MouseState::LeftPress:
413                 case MouseState::RightPress:
414                 case MouseState::RightRelease:
415                     // In this switch only left button release is relevant
416                     break;
417             }
418             break;
419         case InputState::ScrollLeft:
420             switch (state)
421             {
422                 case MouseState::Released:
423                     InputScrollContinue(w, widgetIndex, screenCoords);
424                     break;
425                 case MouseState::LeftRelease:
426                     InputScrollEnd();
427                     break;
428                 case MouseState::LeftPress:
429                 case MouseState::RightPress:
430                 case MouseState::RightRelease:
431                     // In this switch only left button release is relevant
432                     break;
433             }
434             break;
435         case InputState::Resizing:
436             w = window_find_by_number(_dragWidget.window_classification, _dragWidget.window_number);
437             if (w == nullptr)
438             {
439                 _inputState = InputState::Reset;
440             }
441             else
442             {
443                 if (state == MouseState::LeftRelease)
444                 {
445                     InputWindowResizeEnd();
446                 }
447                 if (state == MouseState::Released || state == MouseState::LeftRelease)
448                 {
449                     InputWindowResizeContinue(w, screenCoords);
450                 }
451             }
452             break;
453         case InputState::ScrollRight:
454             InputScrollRight(screenCoords, state);
455             break;
456     }
457 }
458 
459 #pragma region Window positioning / resizing
460 
InputWindowPositionBegin(rct_window * w,rct_widgetindex widgetIndex,const ScreenCoordsXY & screenCoords)461 void InputWindowPositionBegin(rct_window* w, rct_widgetindex widgetIndex, const ScreenCoordsXY& screenCoords)
462 {
463     _inputState = InputState::PositioningWindow;
464     gInputDragLast = screenCoords - w->windowPos;
465     _dragWidget.window_classification = w->classification;
466     _dragWidget.window_number = w->number;
467     _dragWidget.widget_index = widgetIndex;
468 }
469 
InputWindowPositionContinue(rct_window * w,const ScreenCoordsXY & lastScreenCoords,const ScreenCoordsXY & newScreenCoords)470 static void InputWindowPositionContinue(
471     rct_window* w, const ScreenCoordsXY& lastScreenCoords, const ScreenCoordsXY& newScreenCoords)
472 {
473     int32_t snapProximity;
474 
475     snapProximity = (w->flags & WF_NO_SNAPPING) ? 0 : gConfigGeneral.window_snap_proximity;
476     window_move_and_snap(w, newScreenCoords - lastScreenCoords, snapProximity);
477 }
478 
InputWindowPositionEnd(rct_window * w,const ScreenCoordsXY & screenCoords)479 static void InputWindowPositionEnd(rct_window* w, const ScreenCoordsXY& screenCoords)
480 {
481     _inputState = InputState::Normal;
482     gTooltipTimeout = 0;
483     gTooltipWidget = _dragWidget;
484     window_event_moved_call(w, screenCoords);
485 }
486 
InputWindowResizeBegin(rct_window * w,rct_widgetindex widgetIndex,const ScreenCoordsXY & screenCoords)487 static void InputWindowResizeBegin(rct_window* w, rct_widgetindex widgetIndex, const ScreenCoordsXY& screenCoords)
488 {
489     _inputState = InputState::Resizing;
490     gInputDragLast = screenCoords;
491     _dragWidget.window_classification = w->classification;
492     _dragWidget.window_number = w->number;
493     _dragWidget.widget_index = widgetIndex;
494     _originalWindowWidth = w->width;
495     _originalWindowHeight = w->height;
496 }
497 
InputWindowResizeContinue(rct_window * w,const ScreenCoordsXY & screenCoords)498 static void InputWindowResizeContinue(rct_window* w, const ScreenCoordsXY& screenCoords)
499 {
500     if (screenCoords.y < static_cast<int32_t>(context_get_height()) - 2)
501     {
502         auto differentialCoords = screenCoords - gInputDragLast;
503         int32_t targetWidth = _originalWindowWidth + differentialCoords.x - w->width;
504         int32_t targetHeight = _originalWindowHeight + differentialCoords.y - w->height;
505 
506         window_resize(w, targetWidth, targetHeight);
507     }
508 }
509 
InputWindowResizeEnd()510 static void InputWindowResizeEnd()
511 {
512     _inputState = InputState::Normal;
513     gTooltipTimeout = 0;
514     gTooltipWidget = _dragWidget;
515 }
516 
517 #pragma endregion
518 
519 #pragma region Viewport dragging
520 
InputViewportDragBegin(rct_window * w)521 static void InputViewportDragBegin(rct_window* w)
522 {
523     w->flags &= ~WF_SCROLLING_TO_LOCATION;
524     _inputState = InputState::ViewportRight;
525     _dragWidget.window_classification = w->classification;
526     _dragWidget.window_number = w->number;
527     _ticksSinceDragStart = 0;
528     auto cursorPosition = context_get_cursor_position();
529     gInputDragLast = cursorPosition;
530     context_hide_cursor();
531 
532     window_unfollow_sprite(w);
533     // gInputFlags |= INPUT_FLAG_5;
534 }
535 
InputViewportDragContinue()536 static void InputViewportDragContinue()
537 {
538     rct_window* w;
539     rct_viewport* viewport;
540 
541     auto newDragCoords = context_get_cursor_position();
542     const CursorState* cursorState = context_get_cursor_state();
543 
544     auto differentialCoords = newDragCoords - gInputDragLast;
545     w = window_find_by_number(_dragWidget.window_classification, _dragWidget.window_number);
546 
547     // #3294: Window can be closed during a drag session, so just finish
548     //        the session if the window no longer exists
549     if (w == nullptr)
550     {
551         InputViewportDragEnd();
552         return;
553     }
554 
555     viewport = w->viewport;
556     _ticksSinceDragStart += gCurrentDeltaTime;
557     if (viewport == nullptr)
558     {
559         context_show_cursor();
560         _inputState = InputState::Reset;
561     }
562     else if (differentialCoords.x != 0 || differentialCoords.y != 0)
563     {
564         if (!(w->flags & WF_NO_SCROLLING))
565         {
566             // User dragged a scrollable viewport
567 
568             // If the drag time is less than 500 the "drag" is usually interpreted as a right click.
569             // As the user moved the mouse, don't interpret it as right click in any case.
570             _ticksSinceDragStart = 1000;
571 
572             differentialCoords.x = differentialCoords.x * (viewport->zoom + 1);
573             differentialCoords.y = differentialCoords.y * (viewport->zoom + 1);
574             if (gConfigGeneral.invert_viewport_drag)
575             {
576                 w->savedViewPos -= differentialCoords;
577             }
578             else
579             {
580                 w->savedViewPos += differentialCoords;
581             }
582         }
583     }
584 
585     if (cursorState->touch)
586     {
587         gInputDragLast = newDragCoords;
588     }
589     else
590     {
591         context_set_cursor_position(gInputDragLast);
592     }
593 }
594 
InputViewportDragEnd()595 static void InputViewportDragEnd()
596 {
597     _inputState = InputState::Reset;
598     context_show_cursor();
599 }
600 
601 #pragma endregion
602 
603 #pragma region Scroll bars
604 
InputScrollBegin(rct_window * w,rct_widgetindex widgetIndex,const ScreenCoordsXY & screenCoords)605 static void InputScrollBegin(rct_window* w, rct_widgetindex widgetIndex, const ScreenCoordsXY& screenCoords)
606 {
607     const auto& widget = w->widgets[widgetIndex];
608 
609     _inputState = InputState::ScrollLeft;
610     gPressedWidget.window_classification = w->classification;
611     gPressedWidget.window_number = w->number;
612     gPressedWidget.widget_index = widgetIndex;
613     gTooltipCursor = screenCoords;
614 
615     int32_t scroll_area, scroll_id;
616     ScreenCoordsXY scrollCoords;
617     scroll_id = 0; // safety
618     WidgetScrollGetPart(w, &widget, screenCoords, scrollCoords, &scroll_area, &scroll_id);
619 
620     _currentScrollArea = scroll_area;
621     _currentScrollIndex = scroll_id;
622     window_event_unknown_15_call(w, scroll_id, scroll_area);
623     if (scroll_area == SCROLL_PART_VIEW)
624     {
625         window_event_scroll_mousedown_call(w, scroll_id, scrollCoords);
626         return;
627     }
628 
629     const auto& widg = w->widgets[widgetIndex];
630     auto& scroll = w->scrolls[scroll_id];
631 
632     int32_t widget_width = widg.width() - 1;
633     if (scroll.flags & VSCROLLBAR_VISIBLE)
634         widget_width -= SCROLLBAR_WIDTH + 1;
635     int32_t widget_content_width = std::max(scroll.h_right - widget_width, 0);
636 
637     int32_t widget_height = widg.bottom - widg.top - 1;
638     if (scroll.flags & HSCROLLBAR_VISIBLE)
639         widget_height -= SCROLLBAR_WIDTH + 1;
640     int32_t widget_content_height = std::max(scroll.v_bottom - widget_height, 0);
641 
642     switch (scroll_area)
643     {
644         case SCROLL_PART_HSCROLLBAR_LEFT:
645             scroll.h_left = std::max(scroll.h_left - 3, 0);
646             break;
647         case SCROLL_PART_HSCROLLBAR_RIGHT:
648             scroll.h_left = std::min(scroll.h_left + 3, widget_content_width);
649             break;
650         case SCROLL_PART_HSCROLLBAR_LEFT_TROUGH:
651             scroll.h_left = std::max(scroll.h_left - widget_width, 0);
652             break;
653         case SCROLL_PART_HSCROLLBAR_RIGHT_TROUGH:
654             scroll.h_left = std::min(scroll.h_left + widget_width, widget_content_width);
655             break;
656         case SCROLL_PART_VSCROLLBAR_TOP:
657             scroll.v_top = std::max(scroll.v_top - 3, 0);
658             break;
659         case SCROLL_PART_VSCROLLBAR_BOTTOM:
660             scroll.v_top = std::min(scroll.v_top + 3, widget_content_height);
661             break;
662         case SCROLL_PART_VSCROLLBAR_TOP_TROUGH:
663             scroll.v_top = std::max(scroll.v_top - widget_height, 0);
664             break;
665         case SCROLL_PART_VSCROLLBAR_BOTTOM_TROUGH:
666             scroll.v_top = std::min(scroll.v_top + widget_height, widget_content_height);
667             break;
668         default:
669             break;
670     }
671     WidgetScrollUpdateThumbs(w, widgetIndex);
672     window_invalidate_by_number(widgetIndex, w->classification);
673 }
674 
InputScrollContinue(rct_window * w,rct_widgetindex widgetIndex,const ScreenCoordsXY & screenCoords)675 static void InputScrollContinue(rct_window* w, rct_widgetindex widgetIndex, const ScreenCoordsXY& screenCoords)
676 {
677     int32_t scroll_part, scroll_id;
678 
679     assert(w != nullptr);
680 
681     const auto& widget = w->widgets[widgetIndex];
682     if (w->classification != gPressedWidget.window_classification || w->number != gPressedWidget.window_number
683         || widgetIndex != gPressedWidget.widget_index)
684     {
685         InvalidateScroll();
686         return;
687     }
688 
689     ScreenCoordsXY newScreenCoords;
690     WidgetScrollGetPart(w, &widget, screenCoords, newScreenCoords, &scroll_part, &scroll_id);
691 
692     if (_currentScrollArea == SCROLL_PART_HSCROLLBAR_THUMB)
693     {
694         int32_t originalTooltipCursorX = gTooltipCursor.x;
695         gTooltipCursor.x = screenCoords.x;
696         InputScrollPartUpdateHThumb(w, widgetIndex, screenCoords.x - originalTooltipCursorX, scroll_id);
697         return;
698     }
699 
700     if (_currentScrollArea == SCROLL_PART_VSCROLLBAR_THUMB)
701     {
702         int32_t originalTooltipCursorY = gTooltipCursor.y;
703         gTooltipCursor.y = screenCoords.y;
704         InputScrollPartUpdateVThumb(w, widgetIndex, screenCoords.y - originalTooltipCursorY, scroll_id);
705         return;
706     }
707 
708     if (scroll_part != _currentScrollArea)
709     {
710         InvalidateScroll();
711         return;
712     }
713 
714     switch (scroll_part)
715     {
716         case SCROLL_PART_VIEW:
717             window_event_scroll_mousedrag_call(w, scroll_id, newScreenCoords);
718             break;
719         case SCROLL_PART_HSCROLLBAR_LEFT:
720             InputScrollPartUpdateHLeft(w, widgetIndex, scroll_id);
721             break;
722         case SCROLL_PART_HSCROLLBAR_RIGHT:
723             InputScrollPartUpdateHRight(w, widgetIndex, scroll_id);
724             break;
725         case SCROLL_PART_VSCROLLBAR_TOP:
726             InputScrollPartUpdateVTop(w, widgetIndex, scroll_id);
727             break;
728         case SCROLL_PART_VSCROLLBAR_BOTTOM:
729             InputScrollPartUpdateVBottom(w, widgetIndex, scroll_id);
730             break;
731     }
732 }
733 
InputScrollEnd()734 static void InputScrollEnd()
735 {
736     _inputState = InputState::Reset;
737     InvalidateScroll();
738 }
739 
740 /**
741  *
742  *  rct2: 0x006E98F2
743  */
InputScrollPartUpdateHThumb(rct_window * w,rct_widgetindex widgetIndex,int32_t x,int32_t scroll_id)744 static void InputScrollPartUpdateHThumb(rct_window* w, rct_widgetindex widgetIndex, int32_t x, int32_t scroll_id)
745 {
746     const auto& widget = w->widgets[widgetIndex];
747     auto& scroll = w->scrolls[scroll_id];
748 
749     if (window_find_by_number(w->classification, w->number) != nullptr)
750     {
751         int32_t newLeft;
752         newLeft = scroll.h_right;
753         newLeft *= x;
754         x = widget.width() - 21;
755         if (scroll.flags & VSCROLLBAR_VISIBLE)
756             x -= SCROLLBAR_WIDTH + 1;
757         newLeft /= x;
758         x = newLeft;
759         scroll.flags |= HSCROLLBAR_THUMB_PRESSED;
760         newLeft = scroll.h_left;
761         newLeft += x;
762         if (newLeft < 0)
763             newLeft = 0;
764         x = widget.width() - 1;
765         if (scroll.flags & VSCROLLBAR_VISIBLE)
766             x -= SCROLLBAR_WIDTH + 1;
767         x *= -1;
768         x += scroll.h_right;
769         if (x < 0)
770             x = 0;
771         if (newLeft > x)
772             newLeft = x;
773         scroll.h_left = newLeft;
774         WidgetScrollUpdateThumbs(w, widgetIndex);
775         widget_invalidate_by_number(w->classification, w->number, widgetIndex);
776     }
777 }
778 
779 /**
780  *
781  *  rct2: 0x006E99A9
782  */
InputScrollPartUpdateVThumb(rct_window * w,rct_widgetindex widgetIndex,int32_t y,int32_t scroll_id)783 static void InputScrollPartUpdateVThumb(rct_window* w, rct_widgetindex widgetIndex, int32_t y, int32_t scroll_id)
784 {
785     assert(w != nullptr);
786     const auto& widget = w->widgets[widgetIndex];
787     auto& scroll = w->scrolls[scroll_id];
788 
789     if (window_find_by_number(w->classification, w->number) != nullptr)
790     {
791         int32_t newTop;
792         newTop = scroll.v_bottom;
793         newTop *= y;
794         y = widget.height() - 21;
795         if (scroll.flags & HSCROLLBAR_VISIBLE)
796             y -= SCROLLBAR_WIDTH + 1;
797         newTop /= y;
798         y = newTop;
799         scroll.flags |= VSCROLLBAR_THUMB_PRESSED;
800         newTop = scroll.v_top;
801         newTop += y;
802         if (newTop < 0)
803             newTop = 0;
804         y = widget.height() - 1;
805         if (scroll.flags & HSCROLLBAR_VISIBLE)
806             y -= SCROLLBAR_WIDTH + 1;
807         y *= -1;
808         y += scroll.v_bottom;
809         if (y < 0)
810             y = 0;
811         if (newTop > y)
812             newTop = y;
813         scroll.v_top = newTop;
814         WidgetScrollUpdateThumbs(w, widgetIndex);
815         widget_invalidate_by_number(w->classification, w->number, widgetIndex);
816     }
817 }
818 
819 /**
820  *
821  *  rct2: 0x006E9A60
822  */
InputScrollPartUpdateHLeft(rct_window * w,rct_widgetindex widgetIndex,int32_t scroll_id)823 static void InputScrollPartUpdateHLeft(rct_window* w, rct_widgetindex widgetIndex, int32_t scroll_id)
824 {
825     assert(w != nullptr);
826     if (window_find_by_number(w->classification, w->number) != nullptr)
827     {
828         auto& scroll = w->scrolls[scroll_id];
829         scroll.flags |= HSCROLLBAR_LEFT_PRESSED;
830         if (scroll.h_left >= 3)
831             scroll.h_left -= 3;
832         WidgetScrollUpdateThumbs(w, widgetIndex);
833         widget_invalidate_by_number(w->classification, w->number, widgetIndex);
834     }
835 }
836 
837 /**
838  *
839  *  rct2: 0x006E9ABF
840  */
InputScrollPartUpdateHRight(rct_window * w,rct_widgetindex widgetIndex,int32_t scroll_id)841 static void InputScrollPartUpdateHRight(rct_window* w, rct_widgetindex widgetIndex, int32_t scroll_id)
842 {
843     assert(w != nullptr);
844     const auto& widget = w->widgets[widgetIndex];
845     if (window_find_by_number(w->classification, w->number) != nullptr)
846     {
847         auto& scroll = w->scrolls[scroll_id];
848         scroll.flags |= HSCROLLBAR_RIGHT_PRESSED;
849         scroll.h_left += 3;
850         int32_t newLeft = widget.width() - 1;
851         if (scroll.flags & VSCROLLBAR_VISIBLE)
852             newLeft -= SCROLLBAR_WIDTH + 1;
853         newLeft *= -1;
854         newLeft += scroll.h_right;
855         if (newLeft < 0)
856             newLeft = 0;
857         if (scroll.h_left > newLeft)
858             scroll.h_left = newLeft;
859         WidgetScrollUpdateThumbs(w, widgetIndex);
860         widget_invalidate_by_number(w->classification, w->number, widgetIndex);
861     }
862 }
863 
864 /**
865  *
866  *  rct2: 0x006E9C37
867  */
InputScrollPartUpdateVTop(rct_window * w,rct_widgetindex widgetIndex,int32_t scroll_id)868 static void InputScrollPartUpdateVTop(rct_window* w, rct_widgetindex widgetIndex, int32_t scroll_id)
869 {
870     assert(w != nullptr);
871     if (window_find_by_number(w->classification, w->number) != nullptr)
872     {
873         auto& scroll = w->scrolls[scroll_id];
874         scroll.flags |= VSCROLLBAR_UP_PRESSED;
875         if (scroll.v_top >= 3)
876             scroll.v_top -= 3;
877         WidgetScrollUpdateThumbs(w, widgetIndex);
878         widget_invalidate_by_number(w->classification, w->number, widgetIndex);
879     }
880 }
881 
882 /**
883  *
884  *  rct2: 0x006E9C96
885  */
InputScrollPartUpdateVBottom(rct_window * w,rct_widgetindex widgetIndex,int32_t scroll_id)886 static void InputScrollPartUpdateVBottom(rct_window* w, rct_widgetindex widgetIndex, int32_t scroll_id)
887 {
888     assert(w != nullptr);
889     const auto& widget = w->widgets[widgetIndex];
890     if (window_find_by_number(w->classification, w->number) != nullptr)
891     {
892         auto& scroll = w->scrolls[scroll_id];
893         scroll.flags |= VSCROLLBAR_DOWN_PRESSED;
894         scroll.v_top += 3;
895         int32_t newTop = widget.height() - 1;
896         if (scroll.flags & HSCROLLBAR_VISIBLE)
897             newTop -= SCROLLBAR_WIDTH + 1;
898         newTop *= -1;
899         newTop += scroll.v_bottom;
900         if (newTop < 0)
901             newTop = 0;
902         if (scroll.v_top > newTop)
903             scroll.v_top = newTop;
904         WidgetScrollUpdateThumbs(w, widgetIndex);
905         widget_invalidate_by_number(w->classification, w->number, widgetIndex);
906     }
907 }
908 
909 #pragma endregion
910 
911 #pragma region Widgets
912 
913 /**
914  *
915  *  rct2: 0x006E9253
916  */
InputWidgetOver(const ScreenCoordsXY & screenCoords,rct_window * w,rct_widgetindex widgetIndex)917 static void InputWidgetOver(const ScreenCoordsXY& screenCoords, rct_window* w, rct_widgetindex widgetIndex)
918 {
919     rct_windowclass windowClass = WC_NULL;
920     rct_windownumber windowNumber = 0;
921     rct_widget* widget = nullptr;
922 
923     if (w != nullptr)
924     {
925         windowClass = w->classification;
926         windowNumber = w->number;
927         widget = &w->widgets[widgetIndex];
928     }
929 
930     InputWidgetOverChangeCheck(windowClass, windowNumber, widgetIndex);
931 
932     if (w != nullptr && widgetIndex != -1 && widget->type == WindowWidgetType::Scroll)
933     {
934         int32_t scroll_part, scrollId;
935         ScreenCoordsXY newScreenCoords;
936         WidgetScrollGetPart(w, widget, screenCoords, newScreenCoords, &scroll_part, &scrollId);
937 
938         if (scroll_part != SCROLL_PART_VIEW)
939             window_tooltip_close();
940         else
941         {
942             window_event_scroll_mouseover_call(w, scrollId, newScreenCoords);
943             InputUpdateTooltip(w, widgetIndex, screenCoords);
944         }
945     }
946     else
947     {
948         InputUpdateTooltip(w, widgetIndex, screenCoords);
949     }
950 }
951 
952 /**
953  *
954  *  rct2: 0x006E9269
955  */
InputWidgetOverChangeCheck(rct_windowclass windowClass,rct_windownumber windowNumber,rct_widgetindex widgetIndex)956 static void InputWidgetOverChangeCheck(rct_windowclass windowClass, rct_windownumber windowNumber, rct_widgetindex widgetIndex)
957 {
958     // Prevents invalid widgets being clicked source of bug is elsewhere
959     if (widgetIndex == -1)
960         return;
961 
962     // Check if the widget that the cursor was over, has changed
963     if (windowClass != gHoverWidget.window_classification || windowNumber != gHoverWidget.window_number
964         || widgetIndex != gHoverWidget.widget_index)
965     {
966         // Invalidate last widget cursor was on if widget is a flat button
967         InputWidgetOverFlatbuttonInvalidate();
968 
969         // Set new cursor over widget
970         gHoverWidget.window_classification = windowClass;
971         gHoverWidget.window_number = windowNumber;
972         gHoverWidget.widget_index = widgetIndex;
973 
974         // Invalidate new widget cursor is on if widget is a flat button
975         if (windowClass != 255)
976             InputWidgetOverFlatbuttonInvalidate();
977     }
978 }
979 
980 /**
981  * Used to invalidate flat button widgets when the mouse leaves and enters them. This should be generalised so that all widgets
982  * can use this in the future.
983  */
InputWidgetOverFlatbuttonInvalidate()984 static void InputWidgetOverFlatbuttonInvalidate()
985 {
986     rct_window* w = window_find_by_number(gHoverWidget.window_classification, gHoverWidget.window_number);
987     if (w != nullptr)
988     {
989         window_event_invalidate_call(w);
990         if (w->widgets[gHoverWidget.widget_index].type == WindowWidgetType::FlatBtn)
991         {
992             widget_invalidate_by_number(
993                 gHoverWidget.window_classification, gHoverWidget.window_number, gHoverWidget.widget_index);
994         }
995     }
996 }
997 
998 /**
999  *
1000  *  rct2: 0x006E95F9
1001  */
InputWidgetLeft(const ScreenCoordsXY & screenCoords,rct_window * w,rct_widgetindex widgetIndex)1002 static void InputWidgetLeft(const ScreenCoordsXY& screenCoords, rct_window* w, rct_widgetindex widgetIndex)
1003 {
1004     rct_windowclass windowClass = WC_NULL;
1005     rct_windownumber windowNumber = 0;
1006 
1007     if (w != nullptr)
1008     {
1009         windowClass = w->classification;
1010         windowNumber = w->number;
1011     }
1012 
1013     window_close_by_class(WC_ERROR);
1014     window_close_by_class(WC_TOOLTIP);
1015 
1016     // Window might have changed position in the list, therefore find it again
1017     w = window_find_by_number(windowClass, windowNumber);
1018     if (w == nullptr)
1019         return;
1020 
1021     w = window_bring_to_front(w);
1022     if (widgetIndex == -1)
1023         return;
1024 
1025     if (windowClass != gCurrentTextBox.window.classification || windowNumber != gCurrentTextBox.window.number
1026         || widgetIndex != gCurrentTextBox.widget_index)
1027     {
1028         window_cancel_textbox();
1029     }
1030 
1031     const auto& widget = w->widgets[widgetIndex];
1032 
1033     switch (widget.type)
1034     {
1035         case WindowWidgetType::Frame:
1036         case WindowWidgetType::Resize:
1037             if (window_can_resize(w)
1038                 && (screenCoords.x >= w->windowPos.x + w->width - 19 && screenCoords.y >= w->windowPos.y + w->height - 19))
1039                 InputWindowResizeBegin(w, widgetIndex, screenCoords);
1040             break;
1041         case WindowWidgetType::Viewport:
1042             _inputState = InputState::ViewportLeft;
1043             gInputDragLast = screenCoords;
1044             _dragWidget.window_classification = windowClass;
1045             _dragWidget.window_number = windowNumber;
1046             if (_inputFlags & INPUT_FLAG_TOOL_ACTIVE)
1047             {
1048                 w = window_find_by_number(gCurrentToolWidget.window_classification, gCurrentToolWidget.window_number);
1049                 if (w != nullptr)
1050                 {
1051                     window_event_tool_down_call(w, gCurrentToolWidget.widget_index, screenCoords);
1052                     _inputFlags |= INPUT_FLAG_4;
1053                 }
1054             }
1055             break;
1056         case WindowWidgetType::Caption:
1057             InputWindowPositionBegin(w, widgetIndex, screenCoords);
1058             break;
1059         case WindowWidgetType::Scroll:
1060             InputScrollBegin(w, widgetIndex, screenCoords);
1061             break;
1062         default:
1063             if (WidgetIsEnabled(w, widgetIndex) && !WidgetIsDisabled(w, widgetIndex))
1064             {
1065                 OpenRCT2::Audio::Play(OpenRCT2::Audio::SoundId::Click1, 0, w->windowPos.x + widget.midX());
1066 
1067                 // Set new cursor down widget
1068                 gPressedWidget.window_classification = windowClass;
1069                 gPressedWidget.window_number = windowNumber;
1070                 gPressedWidget.widget_index = widgetIndex;
1071                 _inputFlags |= INPUT_FLAG_WIDGET_PRESSED;
1072                 _inputState = InputState::WidgetPressed;
1073                 _clickRepeatTicks = 1;
1074 
1075                 widget_invalidate_by_number(windowClass, windowNumber, widgetIndex);
1076                 window_event_mouse_down_call(w, widgetIndex);
1077             }
1078             break;
1079     }
1080 }
1081 
1082 #pragma endregion
1083 
1084 /**
1085  *
1086  *  rct2: 0x006ED833
1087  */
ProcessMouseOver(const ScreenCoordsXY & screenCoords)1088 void ProcessMouseOver(const ScreenCoordsXY& screenCoords)
1089 {
1090     rct_window* window;
1091 
1092     CursorID cursorId = CursorID::Arrow;
1093     auto ft = Formatter();
1094     ft.Add<rct_string_id>(STR_NONE);
1095     SetMapTooltip(ft);
1096     window = window_find_from_point(screenCoords);
1097 
1098     if (window != nullptr)
1099     {
1100         rct_widgetindex widgetId = window_find_widget_from_point(window, screenCoords);
1101         if (widgetId != -1)
1102         {
1103             switch (window->widgets[widgetId].type)
1104             {
1105                 case WindowWidgetType::Viewport:
1106                     if (!(_inputFlags & INPUT_FLAG_TOOL_ACTIVE))
1107                     {
1108                         if (ViewportInteractionLeftOver(screenCoords))
1109                         {
1110                             SetCursor(CursorID::HandPoint);
1111                             return;
1112                         }
1113                         break;
1114                     }
1115                     cursorId = static_cast<CursorID>(gCurrentToolId);
1116                     break;
1117 
1118                 case WindowWidgetType::Frame:
1119                 case WindowWidgetType::Resize:
1120                     if (!(window->flags & WF_RESIZABLE))
1121                         break;
1122 
1123                     if (window->min_width == window->max_width && window->min_height == window->max_height)
1124                         break;
1125 
1126                     if (screenCoords.x < window->windowPos.x + window->width - 0x13)
1127                         break;
1128 
1129                     if (screenCoords.y < window->windowPos.y + window->height - 0x13)
1130                         break;
1131 
1132                     cursorId = CursorID::DiagonalArrows;
1133                     break;
1134 
1135                 case WindowWidgetType::Scroll:
1136                 {
1137                     int32_t output_scroll_area, scroll_id;
1138                     ScreenCoordsXY scrollCoords;
1139                     WidgetScrollGetPart(
1140                         window, &window->widgets[widgetId], screenCoords, scrollCoords, &output_scroll_area, &scroll_id);
1141                     if (output_scroll_area != SCROLL_PART_VIEW)
1142                     {
1143                         cursorId = CursorID::Arrow;
1144                         break;
1145                     }
1146                     // Same as default but with scroll_x/y
1147                     cursorId = window_event_cursor_call(window, widgetId, scrollCoords);
1148                     if (cursorId == CursorID::Undefined)
1149                         cursorId = CursorID::Arrow;
1150                     break;
1151                 }
1152                 default:
1153                     cursorId = window_event_cursor_call(window, widgetId, screenCoords);
1154                     if (cursorId == CursorID::Undefined)
1155                         cursorId = CursorID::Arrow;
1156                     break;
1157             }
1158         }
1159     }
1160 
1161     ViewportInteractionRightOver(screenCoords);
1162     SetCursor(cursorId);
1163 }
1164 
1165 /**
1166  *
1167  *  rct2: 0x006ED801
1168  */
ProcessMouseTool(const ScreenCoordsXY & screenCoords)1169 void ProcessMouseTool(const ScreenCoordsXY& screenCoords)
1170 {
1171     if (_inputFlags & INPUT_FLAG_TOOL_ACTIVE)
1172     {
1173         rct_window* w = window_find_by_number(gCurrentToolWidget.window_classification, gCurrentToolWidget.window_number);
1174 
1175         if (w == nullptr)
1176             tool_cancel();
1177         else
1178             window_event_tool_update_call(w, gCurrentToolWidget.widget_index, screenCoords);
1179     }
1180 }
1181 
1182 /**
1183  *
1184  *  rct2: 0x006E8DA7
1185  */
InputStateWidgetPressed(const ScreenCoordsXY & screenCoords,MouseState state,rct_widgetindex widgetIndex,rct_window * w,rct_widget * widget)1186 void InputStateWidgetPressed(
1187     const ScreenCoordsXY& screenCoords, MouseState state, rct_widgetindex widgetIndex, rct_window* w, rct_widget* widget)
1188 {
1189     rct_windowclass cursor_w_class;
1190     rct_windownumber cursor_w_number;
1191     cursor_w_class = gPressedWidget.window_classification;
1192     cursor_w_number = gPressedWidget.window_number;
1193     rct_widgetindex cursor_widgetIndex = gPressedWidget.widget_index;
1194 
1195     rct_window* cursor_w = window_find_by_number(cursor_w_class, cursor_w_number);
1196     if (cursor_w == nullptr)
1197     {
1198         _inputState = InputState::Reset;
1199         return;
1200     }
1201 
1202     switch (state)
1203     {
1204         case MouseState::Released:
1205             if (w == nullptr || cursor_w_class != w->classification || cursor_w_number != w->number
1206                 || widgetIndex != cursor_widgetIndex)
1207                 break;
1208 
1209             if (w->disabled_widgets & (1ULL << widgetIndex))
1210                 break;
1211 
1212             if (_clickRepeatTicks != 0)
1213             {
1214                 _clickRepeatTicks++;
1215 
1216                 // Handle click repeat
1217                 if (_clickRepeatTicks >= 16 && (_clickRepeatTicks & 3) == 0)
1218                 {
1219                     if (w->hold_down_widgets & (1ULL << widgetIndex))
1220                     {
1221                         window_event_mouse_down_call(w, widgetIndex);
1222                     }
1223                 }
1224             }
1225 
1226             if (_inputFlags & INPUT_FLAG_WIDGET_PRESSED)
1227             {
1228                 if (_inputState == InputState::DropdownActive)
1229                 {
1230                     gDropdownHighlightedIndex = gDropdownDefaultIndex;
1231                     window_invalidate_by_class(WC_DROPDOWN);
1232                 }
1233                 return;
1234             }
1235 
1236             _inputFlags |= INPUT_FLAG_WIDGET_PRESSED;
1237             widget_invalidate_by_number(cursor_w_class, cursor_w_number, widgetIndex);
1238             return;
1239         case MouseState::LeftRelease:
1240         case MouseState::RightPress:
1241             if (_inputState == InputState::DropdownActive)
1242             {
1243                 if (w != nullptr)
1244                 {
1245                     auto wClass = w->classification;
1246                     auto wNumber = w->number;
1247                     int32_t dropdown_index = 0;
1248                     bool dropdownCleanup = false;
1249 
1250                     if (w->classification == WC_DROPDOWN)
1251                     {
1252                         dropdown_index = DropdownIndexFromPoint(screenCoords, w);
1253                         dropdownCleanup = dropdown_index == -1
1254                             || (dropdown_index < Dropdown::ItemsMaxSize && Dropdown::IsDisabled(dropdown_index))
1255                             || gDropdownItemsFormat[dropdown_index] == Dropdown::SeparatorString;
1256                         w = nullptr; // To be closed right next
1257                     }
1258                     else
1259                     {
1260                         if (cursor_w_class != w->classification || cursor_w_number != w->number
1261                             || widgetIndex != cursor_widgetIndex)
1262                         {
1263                             dropdownCleanup = true;
1264                         }
1265                         else
1266                         {
1267                             dropdown_index = -1;
1268                             if (_inputFlags & INPUT_FLAG_DROPDOWN_STAY_OPEN)
1269                             {
1270                                 if (!(_inputFlags & INPUT_FLAG_DROPDOWN_MOUSE_UP))
1271                                 {
1272                                     _inputFlags |= INPUT_FLAG_DROPDOWN_MOUSE_UP;
1273                                     return;
1274                                 }
1275                             }
1276                         }
1277                     }
1278 
1279                     window_close_by_class(WC_DROPDOWN);
1280 
1281                     if (dropdownCleanup)
1282                     {
1283                         // Update w as it will be invalid after closing the dropdown window
1284                         w = window_find_by_number(wClass, wNumber);
1285                     }
1286                     else
1287                     {
1288                         cursor_w = window_find_by_number(cursor_w_class, cursor_w_number);
1289                         if (_inputFlags & INPUT_FLAG_WIDGET_PRESSED)
1290                         {
1291                             _inputFlags &= ~INPUT_FLAG_WIDGET_PRESSED;
1292                             widget_invalidate_by_number(cursor_w_class, cursor_w_number, cursor_widgetIndex);
1293                         }
1294 
1295                         _inputState = InputState::Normal;
1296                         gTooltipTimeout = 0;
1297                         gTooltipWidget.widget_index = cursor_widgetIndex;
1298                         gTooltipWidget.window_classification = cursor_w_class;
1299                         gTooltipWidget.window_number = cursor_w_number;
1300 
1301                         if (dropdown_index == -1)
1302                         {
1303                             if (!Dropdown::IsDisabled(gDropdownDefaultIndex))
1304                             {
1305                                 dropdown_index = gDropdownDefaultIndex;
1306                             }
1307                         }
1308                         window_event_dropdown_call(cursor_w, cursor_widgetIndex, dropdown_index);
1309                     }
1310                 }
1311             }
1312 
1313             _inputState = InputState::Normal;
1314 
1315             if (state == MouseState::RightPress)
1316             {
1317                 return;
1318             }
1319 
1320             gTooltipTimeout = 0;
1321             gTooltipWidget.widget_index = cursor_widgetIndex;
1322 
1323             if (w == nullptr)
1324                 break;
1325 
1326             if (widget == nullptr)
1327                 break;
1328 
1329             {
1330                 int32_t mid_point_x = widget->midX() + w->windowPos.x;
1331                 OpenRCT2::Audio::Play(OpenRCT2::Audio::SoundId::Click2, 0, mid_point_x);
1332             }
1333             if (cursor_w_class != w->classification || cursor_w_number != w->number || widgetIndex != cursor_widgetIndex)
1334                 break;
1335 
1336             if (w->disabled_widgets & (1ULL << widgetIndex))
1337                 break;
1338 
1339             widget_invalidate_by_number(cursor_w_class, cursor_w_number, widgetIndex);
1340             window_event_mouse_up_call(w, widgetIndex);
1341             return;
1342 
1343         default:
1344             return;
1345     }
1346 
1347     _clickRepeatTicks = 0;
1348     if (_inputState != InputState::DropdownActive)
1349     {
1350         // Hold down widget and drag outside of area??
1351         if (_inputFlags & INPUT_FLAG_WIDGET_PRESSED)
1352         {
1353             _inputFlags &= ~INPUT_FLAG_WIDGET_PRESSED;
1354             widget_invalidate_by_number(cursor_w_class, cursor_w_number, cursor_widgetIndex);
1355         }
1356         return;
1357     }
1358 
1359     gDropdownHighlightedIndex = -1;
1360     window_invalidate_by_class(WC_DROPDOWN);
1361     if (w == nullptr)
1362     {
1363         return;
1364     }
1365 
1366     if (w->classification == WC_DROPDOWN)
1367     {
1368         int32_t dropdown_index = DropdownIndexFromPoint(screenCoords, w);
1369         if (dropdown_index == -1)
1370         {
1371             return;
1372         }
1373 
1374         if (gDropdownIsColour && gDropdownLastColourHover != dropdown_index)
1375         {
1376             gDropdownLastColourHover = dropdown_index;
1377             window_tooltip_close();
1378 
1379             static constexpr const rct_string_id colourTooltips[] = {
1380                 STR_COLOUR_BLACK_TIP,
1381                 STR_COLOUR_GREY_TIP,
1382                 STR_COLOUR_WHITE_TIP,
1383                 STR_COLOUR_DARK_PURPLE_TIP,
1384                 STR_COLOUR_LIGHT_PURPLE_TIP,
1385                 STR_COLOUR_BRIGHT_PURPLE_TIP,
1386                 STR_COLOUR_DARK_BLUE_TIP,
1387                 STR_COLOUR_LIGHT_BLUE_TIP,
1388                 STR_COLOUR_ICY_BLUE_TIP,
1389                 STR_COLOUR_TEAL_TIP,
1390                 STR_COLOUR_AQUAMARINE_TIP,
1391                 STR_COLOUR_SATURATED_GREEN_TIP,
1392                 STR_COLOUR_DARK_GREEN_TIP,
1393                 STR_COLOUR_MOSS_GREEN_TIP,
1394                 STR_COLOUR_BRIGHT_GREEN_TIP,
1395                 STR_COLOUR_OLIVE_GREEN_TIP,
1396                 STR_COLOUR_DARK_OLIVE_GREEN_TIP,
1397                 STR_COLOUR_BRIGHT_YELLOW_TIP,
1398                 STR_COLOUR_YELLOW_TIP,
1399                 STR_COLOUR_DARK_YELLOW_TIP,
1400                 STR_COLOUR_LIGHT_ORANGE_TIP,
1401                 STR_COLOUR_DARK_ORANGE_TIP,
1402                 STR_COLOUR_LIGHT_BROWN_TIP,
1403                 STR_COLOUR_SATURATED_BROWN_TIP,
1404                 STR_COLOUR_DARK_BROWN_TIP,
1405                 STR_COLOUR_SALMON_PINK_TIP,
1406                 STR_COLOUR_BORDEAUX_RED_TIP,
1407                 STR_COLOUR_SATURATED_RED_TIP,
1408                 STR_COLOUR_BRIGHT_RED_TIP,
1409                 STR_COLOUR_DARK_PINK_TIP,
1410                 STR_COLOUR_BRIGHT_PINK_TIP,
1411                 STR_COLOUR_LIGHT_PINK_TIP,
1412             };
1413             window_tooltip_show(OpenRCT2String{ colourTooltips[dropdown_index], {} }, screenCoords);
1414         }
1415 
1416         if (dropdown_index < Dropdown::ItemsMaxSize && Dropdown::IsDisabled(dropdown_index))
1417         {
1418             return;
1419         }
1420 
1421         if (gDropdownItemsFormat[dropdown_index] == Dropdown::SeparatorString)
1422         {
1423             return;
1424         }
1425 
1426         gDropdownHighlightedIndex = dropdown_index;
1427         window_invalidate_by_class(WC_DROPDOWN);
1428     }
1429     else
1430     {
1431         gDropdownLastColourHover = -1;
1432         window_tooltip_close();
1433     }
1434 }
1435 
InputUpdateTooltip(rct_window * w,rct_widgetindex widgetIndex,const ScreenCoordsXY & screenCoords)1436 static void InputUpdateTooltip(rct_window* w, rct_widgetindex widgetIndex, const ScreenCoordsXY& screenCoords)
1437 {
1438     if (gTooltipWidget.window_classification == 255)
1439     {
1440         if (gTooltipCursor == screenCoords)
1441         {
1442             _tooltipNotShownTicks++;
1443             if (_tooltipNotShownTicks > 50 && w != nullptr && WidgetIsVisible(w, widgetIndex))
1444             {
1445                 gTooltipTimeout = 0;
1446                 window_tooltip_open(w, widgetIndex, screenCoords);
1447             }
1448         }
1449 
1450         gTooltipTimeout = 0;
1451         gTooltipCursor = screenCoords;
1452     }
1453     else
1454     {
1455         reset_tooltip_not_shown();
1456 
1457         if (w == nullptr || gTooltipWidget.window_classification != w->classification
1458             || gTooltipWidget.window_number != w->number || gTooltipWidget.widget_index != widgetIndex
1459             || !WidgetIsVisible(w, widgetIndex))
1460         {
1461             window_tooltip_close();
1462         }
1463 
1464         gTooltipTimeout += gCurrentDeltaTime;
1465         if (gTooltipTimeout >= 8000)
1466         {
1467             window_close_by_class(WC_TOOLTIP);
1468         }
1469     }
1470 }
1471 
1472 #pragma endregion
1473 
1474 #pragma region Keyboard input
1475 
1476 /**
1477  *
1478  *  rct2: 0x00406CD2
1479  */
GetNextKey()1480 int32_t GetNextKey()
1481 {
1482     uint8_t* keysPressed = const_cast<uint8_t*>(context_get_keys_pressed());
1483     for (int32_t i = 0; i < 221; i++)
1484     {
1485         if (keysPressed[i])
1486         {
1487             keysPressed[i] = 0;
1488             return i;
1489         }
1490     }
1491 
1492     return 0;
1493 }
1494 
1495 #pragma endregion
1496 
1497 /**
1498  *
1499  *  rct2: 0x006ED990
1500  */
SetCursor(CursorID cursor_id)1501 void SetCursor(CursorID cursor_id)
1502 {
1503     assert(cursor_id != CursorID::Undefined);
1504     if (_inputState == InputState::Resizing)
1505     {
1506         cursor_id = CursorID::DiagonalArrows;
1507     }
1508     context_setcurrentcursor(cursor_id);
1509 }
1510 
1511 /**
1512  *
1513  *  rct2: 0x006E876D
1514  */
InvalidateScroll()1515 void InvalidateScroll()
1516 {
1517     rct_window* w = window_find_by_number(gPressedWidget.window_classification, gPressedWidget.window_number);
1518     if (w != nullptr)
1519     {
1520         // Reset to basic scroll
1521         w->scrolls[_currentScrollIndex].flags &= 0xFF11;
1522         window_invalidate_by_number(gPressedWidget.window_classification, gPressedWidget.window_number);
1523     }
1524 }
1525 
1526 /**
1527  * rct2: 0x00406C96
1528  */
StoreMouseInput(MouseState state,const ScreenCoordsXY & screenCoords)1529 void StoreMouseInput(MouseState state, const ScreenCoordsXY& screenCoords)
1530 {
1531     uint32_t writeIndex = _mouseInputQueueWriteIndex;
1532     uint32_t nextWriteIndex = (writeIndex + 1) % std::size(_mouseInputQueue);
1533 
1534     // Check if the queue is full
1535     if (nextWriteIndex != _mouseInputQueueReadIndex)
1536     {
1537         RCTMouseData* item = &_mouseInputQueue[writeIndex];
1538         item->x = screenCoords.x;
1539         item->y = screenCoords.y;
1540         item->state = state;
1541 
1542         _mouseInputQueueWriteIndex = nextWriteIndex;
1543     }
1544 }
1545 
GameHandleEdgeScroll()1546 void GameHandleEdgeScroll()
1547 {
1548     rct_window* mainWindow;
1549     int32_t scrollX, scrollY;
1550 
1551     mainWindow = window_get_main();
1552     if (mainWindow == nullptr)
1553         return;
1554     if ((mainWindow->flags & WF_NO_SCROLLING) || (gScreenFlags & (SCREEN_FLAGS_TRACK_MANAGER | SCREEN_FLAGS_TITLE_DEMO)))
1555         return;
1556     if (mainWindow->viewport == nullptr)
1557         return;
1558     if (!context_has_focus())
1559         return;
1560 
1561     scrollX = 0;
1562     scrollY = 0;
1563 
1564     // Scroll left / right
1565     const CursorState* cursorState = context_get_cursor_state();
1566     if (cursorState->position.x == 0)
1567         scrollX = -1;
1568     else if (cursorState->position.x >= context_get_width() - 1)
1569         scrollX = 1;
1570 
1571     // Scroll up / down
1572     if (cursorState->position.y == 0)
1573         scrollY = -1;
1574     else if (cursorState->position.y >= context_get_height() - 1)
1575         scrollY = 1;
1576 
1577     InputScrollViewport(ScreenCoordsXY(scrollX, scrollY));
1578 }
1579 
InputTestPlaceObjectModifier(PLACE_OBJECT_MODIFIER modifier)1580 bool InputTestPlaceObjectModifier(PLACE_OBJECT_MODIFIER modifier)
1581 {
1582     return gInputPlaceObjectModifier & modifier;
1583 }
1584 
InputScrollViewport(const ScreenCoordsXY & scrollScreenCoords)1585 void InputScrollViewport(const ScreenCoordsXY& scrollScreenCoords)
1586 {
1587     rct_window* mainWindow = window_get_main();
1588     rct_viewport* viewport = mainWindow->viewport;
1589 
1590     const int32_t speed = gConfigGeneral.edge_scrolling_speed;
1591 
1592     int32_t multiplier = speed * viewport->zoom;
1593     int32_t dx = scrollScreenCoords.x * multiplier;
1594     int32_t dy = scrollScreenCoords.y * multiplier;
1595 
1596     if (scrollScreenCoords.x != 0)
1597     {
1598         // Speed up scrolling horizontally when at the edge of the map
1599         // so that the speed is consistent with vertical edge scrolling.
1600         int32_t x = mainWindow->savedViewPos.x + viewport->view_width / 2 + dx;
1601         int32_t y = mainWindow->savedViewPos.y + viewport->view_height / 2;
1602         int32_t y_dy = mainWindow->savedViewPos.y + viewport->view_height / 2 + dy;
1603 
1604         auto mapCoord = viewport_coord_to_map_coord({ x, y }, 0);
1605         auto mapCoord_dy = viewport_coord_to_map_coord({ x, y_dy }, 0);
1606 
1607         // Check if we're crossing the boundary
1608         // Clamp to the map minimum value
1609         int32_t at_map_edge = 0;
1610         int32_t at_map_edge_dy = 0;
1611         if (mapCoord.x < MAP_MINIMUM_X_Y || mapCoord.y < MAP_MINIMUM_X_Y)
1612         {
1613             at_map_edge = 1;
1614         }
1615         if (mapCoord_dy.x < MAP_MINIMUM_X_Y || mapCoord_dy.y < MAP_MINIMUM_X_Y)
1616         {
1617             at_map_edge_dy = 1;
1618         }
1619 
1620         // Clamp to the map maximum value (scenario specific)
1621         if (mapCoord.x > GetMapSizeMinus2() || mapCoord.y > GetMapSizeMinus2())
1622         {
1623             at_map_edge = 1;
1624         }
1625         if (mapCoord_dy.x > GetMapSizeMinus2() || mapCoord_dy.y > GetMapSizeMinus2())
1626         {
1627             at_map_edge_dy = 1;
1628         }
1629 
1630         // If we crossed the boundary, multiply the distance by 2
1631         if (at_map_edge && at_map_edge_dy)
1632         {
1633             dx *= 2;
1634         }
1635 
1636         mainWindow->savedViewPos.x += dx;
1637         _inputFlags |= INPUT_FLAG_VIEWPORT_SCROLLING;
1638     }
1639     if (scrollScreenCoords.y != 0)
1640     {
1641         mainWindow->savedViewPos.y += dy;
1642         _inputFlags |= INPUT_FLAG_VIEWPORT_SCROLLING;
1643     }
1644 }
1645