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