From e1fa0b539df9138a257286496dbbe637ac7473f3 Mon Sep 17 00:00:00 2001 From: Aaron van Geffen Date: Sat, 18 Jan 2025 14:29:41 +0100 Subject: [PATCH] Move Widget, Window, Intent, MouseInput into OpenRCT2 namespace (#23642) --- src/openrct2-ui/input/MouseInput.cpp | 3007 +++++++-------- src/openrct2-ui/input/MouseInput.h | 29 +- src/openrct2-ui/interface/LandTool.h | 1 + src/openrct2-ui/interface/Window.cpp | 1300 +++---- src/openrct2-ui/interface/Window.h | 63 +- src/openrct2-ui/scripting/CustomListView.cpp | 2 +- src/openrct2-ui/windows/Window.h | 1 + src/openrct2/Context.h | 19 +- src/openrct2/Game.h | 8 +- src/openrct2/Input.cpp | 103 +- src/openrct2/Input.h | 95 +- src/openrct2/interface/Viewport.cpp | 3530 +++++++++--------- src/openrct2/interface/Viewport.h | 191 +- src/openrct2/interface/Widget.h | 193 +- src/openrct2/interface/Window.cpp | 2422 ++++++------ src/openrct2/interface/Window.h | 551 ++- src/openrct2/interface/Window_internal.cpp | 51 +- src/openrct2/interface/Window_internal.h | 293 +- src/openrct2/network/NetworkPlayer.cpp | 4 +- src/openrct2/paint/Paint.Entity.cpp | 1 + src/openrct2/windows/Intent.h | 215 +- 21 files changed, 6070 insertions(+), 6009 deletions(-) diff --git a/src/openrct2-ui/input/MouseInput.cpp b/src/openrct2-ui/input/MouseInput.cpp index 5b75d883c8..2821567c0b 100644 --- a/src/openrct2-ui/input/MouseInput.cpp +++ b/src/openrct2-ui/input/MouseInput.cpp @@ -41,1700 +41,1705 @@ #include #include -using namespace OpenRCT2; -using namespace OpenRCT2::Ui; -using namespace OpenRCT2::Ui::Windows; - -struct RCTMouseData +namespace OpenRCT2 { - uint32_t x; - uint32_t y; - MouseState state; -}; + using namespace OpenRCT2::Ui; + using namespace OpenRCT2::Ui::Windows; -static RCTMouseData _mouseInputQueue[64]; -static uint8_t _mouseInputQueueReadIndex = 0; -static uint8_t _mouseInputQueueWriteIndex = 0; + struct RCTMouseData + { + uint32_t x; + uint32_t y; + MouseState state; + }; -static std::optional _ticksSinceDragStart; -static WidgetRef _dragWidget; -static uint8_t _dragScrollIndex; -static int32_t _originalWindowWidth; -static int32_t _originalWindowHeight; + static RCTMouseData _mouseInputQueue[64]; + static uint8_t _mouseInputQueueReadIndex = 0; + static uint8_t _mouseInputQueueWriteIndex = 0; -static uint8_t _currentScrollIndex; -static uint8_t _currentScrollArea; + static std::optional _ticksSinceDragStart; + static WidgetRef _dragWidget; + static uint8_t _dragScrollIndex; + static int32_t _originalWindowWidth; + static int32_t _originalWindowHeight; -ScreenCoordsXY gInputDragLast; + static uint8_t _currentScrollIndex; + static uint8_t _currentScrollArea; -uint32_t gTooltipCloseTimeout; -WidgetRef gTooltipWidget; -ScreenCoordsXY gTooltipCursor; + ScreenCoordsXY gInputDragLast; -static std::optional _clickRepeatTicks; + uint32_t gTooltipCloseTimeout; + WidgetRef gTooltipWidget; + ScreenCoordsXY gTooltipCursor; -static MouseState GameGetNextInput(ScreenCoordsXY& screenCoords); -static void InputWidgetOver(const ScreenCoordsXY& screenCoords, WindowBase* w, WidgetIndex widgetIndex); -static void InputWidgetOverChangeCheck(WindowClass windowClass, rct_windownumber windowNumber, WidgetIndex widgetIndex); -static void InputWidgetOverFlatbuttonInvalidate(); -void ProcessMouseOver(const ScreenCoordsXY& screenCoords); -void ProcessMouseTool(const ScreenCoordsXY& screenCoords); -void InvalidateScroll(); -static RCTMouseData* GetMouseInput(); -void TileElementRightClick(int32_t type, TileElement* tileElement, const ScreenCoordsXY& screenCoords); -static void GameHandleInputMouse(const ScreenCoordsXY& screenCoords, MouseState state); -static void InputWidgetLeft(const ScreenCoordsXY& screenCoords, WindowBase* w, WidgetIndex widgetIndex); -void InputStateWidgetPressed( - const ScreenCoordsXY& screenCoords, MouseState state, WidgetIndex widgetIndex, WindowBase* w, Widget* widget); -void SetCursor(CursorID cursor_id); -static void InputWindowPositionContinue( - WindowBase& w, const ScreenCoordsXY& lastScreenCoords, const ScreenCoordsXY& newScreenCoords); -static void InputWindowPositionEnd(WindowBase& w, const ScreenCoordsXY& screenCoords); -static void InputWindowResizeBegin(WindowBase& w, WidgetIndex widgetIndex, const ScreenCoordsXY& screenCoords); -static void InputWindowResizeContinue(WindowBase& w, const ScreenCoordsXY& screenCoords); -static void InputWindowResizeEnd(); -static void InputViewportDragBegin(WindowBase& w); -static void InputViewportDragContinue(); -static void InputViewportDragEnd(); -static void InputScrollBegin(WindowBase& w, WidgetIndex widgetIndex, const ScreenCoordsXY& screenCoords); -static void InputScrollContinue(WindowBase& w, WidgetIndex widgetIndex, const ScreenCoordsXY& screenCoords); -static void InputScrollEnd(); -static void InputScrollPartUpdateHThumb(WindowBase& w, WidgetIndex widgetIndex, int32_t x, int32_t scroll_id); -static void InputScrollPartUpdateHLeft(WindowBase& w, WidgetIndex widgetIndex, int32_t scroll_id); -static void InputScrollPartUpdateHRight(WindowBase& w, WidgetIndex widgetIndex, int32_t scroll_id); -static void InputScrollPartUpdateVThumb(WindowBase& w, WidgetIndex widgetIndex, int32_t y, int32_t scroll_id); -static void InputScrollPartUpdateVTop(WindowBase& w, WidgetIndex widgetIndex, int32_t scroll_id); -static void InputScrollPartUpdateVBottom(WindowBase& w, WidgetIndex widgetIndex, int32_t scroll_id); -static void InputUpdateTooltip(WindowBase* w, WidgetIndex widgetIndex, const ScreenCoordsXY& screenCoords); + static std::optional _clickRepeatTicks; + + static MouseState GameGetNextInput(ScreenCoordsXY& screenCoords); + static void InputWidgetOver(const ScreenCoordsXY& screenCoords, WindowBase* w, WidgetIndex widgetIndex); + static void InputWidgetOverChangeCheck(WindowClass windowClass, rct_windownumber windowNumber, WidgetIndex widgetIndex); + static void InputWidgetOverFlatbuttonInvalidate(); + void ProcessMouseOver(const ScreenCoordsXY& screenCoords); + void ProcessMouseTool(const ScreenCoordsXY& screenCoords); + void InvalidateScroll(); + static RCTMouseData* GetMouseInput(); + void TileElementRightClick(int32_t type, TileElement* tileElement, const ScreenCoordsXY& screenCoords); + static void GameHandleInputMouse(const ScreenCoordsXY& screenCoords, MouseState state); + static void InputWidgetLeft(const ScreenCoordsXY& screenCoords, WindowBase* w, WidgetIndex widgetIndex); + void InputStateWidgetPressed( + const ScreenCoordsXY& screenCoords, MouseState state, WidgetIndex widgetIndex, WindowBase* w, Widget* widget); + void SetCursor(CursorID cursor_id); + static void InputWindowPositionContinue( + WindowBase& w, const ScreenCoordsXY& lastScreenCoords, const ScreenCoordsXY& newScreenCoords); + static void InputWindowPositionEnd(WindowBase& w, const ScreenCoordsXY& screenCoords); + static void InputWindowResizeBegin(WindowBase& w, WidgetIndex widgetIndex, const ScreenCoordsXY& screenCoords); + static void InputWindowResizeContinue(WindowBase& w, const ScreenCoordsXY& screenCoords); + static void InputWindowResizeEnd(); + static void InputViewportDragBegin(WindowBase& w); + static void InputViewportDragContinue(); + static void InputViewportDragEnd(); + static void InputScrollBegin(WindowBase& w, WidgetIndex widgetIndex, const ScreenCoordsXY& screenCoords); + static void InputScrollContinue(WindowBase& w, WidgetIndex widgetIndex, const ScreenCoordsXY& screenCoords); + static void InputScrollEnd(); + static void InputScrollPartUpdateHThumb(WindowBase& w, WidgetIndex widgetIndex, int32_t x, int32_t scroll_id); + static void InputScrollPartUpdateHLeft(WindowBase& w, WidgetIndex widgetIndex, int32_t scroll_id); + static void InputScrollPartUpdateHRight(WindowBase& w, WidgetIndex widgetIndex, int32_t scroll_id); + static void InputScrollPartUpdateVThumb(WindowBase& w, WidgetIndex widgetIndex, int32_t y, int32_t scroll_id); + static void InputScrollPartUpdateVTop(WindowBase& w, WidgetIndex widgetIndex, int32_t scroll_id); + static void InputScrollPartUpdateVBottom(WindowBase& w, WidgetIndex widgetIndex, int32_t scroll_id); + static void InputUpdateTooltip(WindowBase* w, WidgetIndex widgetIndex, const ScreenCoordsXY& screenCoords); #pragma region Mouse input -/** - * - * rct2: 0x006EA627 - */ -void GameHandleInput() -{ - InvalidateAllWindowsAfterInput(); - - MouseState state; - ScreenCoordsXY screenCoords; - while ((state = GameGetNextInput(screenCoords)) != MouseState::Released) + /** + * + * rct2: 0x006EA627 + */ + void GameHandleInput() { - GameHandleInputMouse(screenCoords, state); + InvalidateAllWindowsAfterInput(); + + MouseState state; + ScreenCoordsXY screenCoords; + while ((state = GameGetNextInput(screenCoords)) != MouseState::Released) + { + GameHandleInputMouse(screenCoords, state); + } + + if (_inputFlags & INPUT_FLAG_5) + { + GameHandleInputMouse(screenCoords, state); + } + else + { + int32_t screenWidth = ContextGetWidth(); + int32_t screenHeight = ContextGetHeight(); + screenCoords.x = std::clamp(screenCoords.x, 0, screenWidth - 1); + screenCoords.y = std::clamp(screenCoords.y, 0, screenHeight - 1); + + GameHandleInputMouse(screenCoords, state); + ProcessMouseOver(screenCoords); + ProcessMouseTool(screenCoords); + } } - if (_inputFlags & INPUT_FLAG_5) + /** + * + * rct2: 0x006E83C7 + */ + static MouseState GameGetNextInput(ScreenCoordsXY& screenCoords) { - GameHandleInputMouse(screenCoords, state); - } - else - { - int32_t screenWidth = ContextGetWidth(); - int32_t screenHeight = ContextGetHeight(); - screenCoords.x = std::clamp(screenCoords.x, 0, screenWidth - 1); - screenCoords.y = std::clamp(screenCoords.y, 0, screenHeight - 1); + RCTMouseData* input = GetMouseInput(); + if (input == nullptr) + { + const CursorState* cursorState = ContextGetCursorState(); + screenCoords = cursorState->position; + return MouseState::Released; + } - GameHandleInputMouse(screenCoords, state); - ProcessMouseOver(screenCoords); - ProcessMouseTool(screenCoords); - } -} - -/** - * - * rct2: 0x006E83C7 - */ -static MouseState GameGetNextInput(ScreenCoordsXY& screenCoords) -{ - RCTMouseData* input = GetMouseInput(); - if (input == nullptr) - { - const CursorState* cursorState = ContextGetCursorState(); - screenCoords = cursorState->position; - return MouseState::Released; + screenCoords.x = input->x; + screenCoords.y = input->y; + return input->state; } - screenCoords.x = input->x; - screenCoords.y = input->y; - return input->state; -} - -/** - * - * rct2: 0x00407074 - */ -static RCTMouseData* GetMouseInput() -{ - // Check if that location has been written to yet - if (_mouseInputQueueReadIndex == _mouseInputQueueWriteIndex) + /** + * + * rct2: 0x00407074 + */ + static RCTMouseData* GetMouseInput() { - return nullptr; + // Check if that location has been written to yet + if (_mouseInputQueueReadIndex == _mouseInputQueueWriteIndex) + { + return nullptr; + } + + RCTMouseData* result = &_mouseInputQueue[_mouseInputQueueReadIndex]; + _mouseInputQueueReadIndex = (_mouseInputQueueReadIndex + 1) % std::size(_mouseInputQueue); + return result; } - RCTMouseData* result = &_mouseInputQueue[_mouseInputQueueReadIndex]; - _mouseInputQueueReadIndex = (_mouseInputQueueReadIndex + 1) % std::size(_mouseInputQueue); - return result; -} - -/** - * - * rct2: 0x006E957F - */ -static void InputScrollDragBegin(const ScreenCoordsXY& screenCoords, WindowBase* w, WidgetIndex widgetIndex) -{ - _inputState = InputState::ScrollRight; - gInputDragLast = screenCoords; - _dragWidget.window_classification = w->classification; - _dragWidget.window_number = w->number; - _dragWidget.widget_index = widgetIndex; - _ticksSinceDragStart = gCurrentRealTimeTicks; - - _dragScrollIndex = WindowGetScrollDataIndex(*w, widgetIndex); - ContextHideCursor(); -} - -/** - * Based on (heavily changed) - * rct2: 0x006E9E0E, 0x006E9ED0 - */ -static void InputScrollDragContinue(const ScreenCoordsXY& screenCoords, WindowBase* w) -{ - WidgetIndex widgetIndex = _dragWidget.widget_index; - uint8_t scrollIndex = _dragScrollIndex; - - const auto& widget = w->widgets[widgetIndex]; - auto& scroll = w->scrolls[scrollIndex]; - - ScreenCoordsXY differentialCoords = screenCoords - gInputDragLast; - if (differentialCoords.x == 0 && differentialCoords.y == 0) - return; - - if (scroll.flags & HSCROLLBAR_VISIBLE) + /** + * + * rct2: 0x006E957F + */ + static void InputScrollDragBegin(const ScreenCoordsXY& screenCoords, WindowBase* w, WidgetIndex widgetIndex) { - int16_t size = widget.width() - 1; - if (scroll.flags & VSCROLLBAR_VISIBLE) - size -= 11; - size = std::max(0, scroll.contentWidth - size); - scroll.contentOffsetX = std::min(std::max(0, scroll.contentOffsetX + differentialCoords.x), size); + _inputState = InputState::ScrollRight; + gInputDragLast = screenCoords; + _dragWidget.window_classification = w->classification; + _dragWidget.window_number = w->number; + _dragWidget.widget_index = widgetIndex; + _ticksSinceDragStart = gCurrentRealTimeTicks; + + _dragScrollIndex = WindowGetScrollDataIndex(*w, widgetIndex); + ContextHideCursor(); } - if (scroll.flags & VSCROLLBAR_VISIBLE) + /** + * Based on (heavily changed) + * rct2: 0x006E9E0E, 0x006E9ED0 + */ + static void InputScrollDragContinue(const ScreenCoordsXY& screenCoords, WindowBase* w) { - int16_t size = widget.height() - 1; + WidgetIndex widgetIndex = _dragWidget.widget_index; + uint8_t scrollIndex = _dragScrollIndex; + + const auto& widget = w->widgets[widgetIndex]; + auto& scroll = w->scrolls[scrollIndex]; + + ScreenCoordsXY differentialCoords = screenCoords - gInputDragLast; + if (differentialCoords.x == 0 && differentialCoords.y == 0) + return; + if (scroll.flags & HSCROLLBAR_VISIBLE) - size -= 11; - size = std::max(0, scroll.contentHeight - size); - scroll.contentOffsetY = std::min(std::max(0, scroll.contentOffsetY + differentialCoords.y), size); + { + int16_t size = widget.width() - 1; + if (scroll.flags & VSCROLLBAR_VISIBLE) + size -= 11; + size = std::max(0, scroll.contentWidth - size); + scroll.contentOffsetX = std::min(std::max(0, scroll.contentOffsetX + differentialCoords.x), size); + } + + if (scroll.flags & VSCROLLBAR_VISIBLE) + { + int16_t size = widget.height() - 1; + if (scroll.flags & HSCROLLBAR_VISIBLE) + size -= 11; + size = std::max(0, scroll.contentHeight - size); + scroll.contentOffsetY = std::min(std::max(0, scroll.contentOffsetY + differentialCoords.y), size); + } + + WidgetScrollUpdateThumbs(*w, widgetIndex); + WindowInvalidateByNumber(w->classification, w->number); + + ScreenCoordsXY fixedCursorPosition = { + static_cast(std::ceil(gInputDragLast.x * Config::Get().general.WindowScale)), + static_cast(std::ceil(gInputDragLast.y * Config::Get().general.WindowScale)) + }; + + ContextSetCursorPosition(fixedCursorPosition); } - WidgetScrollUpdateThumbs(*w, widgetIndex); - WindowInvalidateByNumber(w->classification, w->number); - - ScreenCoordsXY fixedCursorPosition = { - static_cast(std::ceil(gInputDragLast.x * Config::Get().general.WindowScale)), - static_cast(std::ceil(gInputDragLast.y * Config::Get().general.WindowScale)) - }; - - ContextSetCursorPosition(fixedCursorPosition); -} - -/** - * - * rct2: 0x006E8ACB - */ -static void InputScrollRight(const ScreenCoordsXY& screenCoords, MouseState state) -{ - auto* windowMgr = GetContext()->GetUiContext()->GetWindowManager(); - WindowBase* w = windowMgr->FindByNumber(_dragWidget.window_classification, _dragWidget.window_number); - if (w == nullptr) + /** + * + * rct2: 0x006E8ACB + */ + static void InputScrollRight(const ScreenCoordsXY& screenCoords, MouseState state) { - ContextShowCursor(); - _inputState = InputState::Reset; - return; - } - - switch (state) - { - case MouseState::Released: - if (screenCoords.x != 0 || screenCoords.y != 0) - { - _ticksSinceDragStart = std::nullopt; - InputScrollDragContinue(screenCoords, w); - } - break; - case MouseState::RightRelease: - _inputState = InputState::Reset; + auto* windowMgr = GetContext()->GetUiContext()->GetWindowManager(); + WindowBase* w = windowMgr->FindByNumber(_dragWidget.window_classification, _dragWidget.window_number); + if (w == nullptr) + { ContextShowCursor(); - break; - case MouseState::LeftPress: - case MouseState::LeftRelease: - case MouseState::RightPress: - // Function only handles right button, so it's the only one relevant - break; - } -} + _inputState = InputState::Reset; + return; + } -/** - * - * rct2: 0x006E8655 - */ -static void GameHandleInputMouse(const ScreenCoordsXY& screenCoords, MouseState state) -{ - WindowBase* w; - Widget* widget; - WidgetIndex widgetIndex; - - auto* windowMgr = GetContext()->GetUiContext()->GetWindowManager(); - - // Get window and widget under cursor position - w = windowMgr->FindFromPoint(screenCoords); - widgetIndex = w == nullptr ? -1 : windowMgr->FindWidgetFromPoint(*w, screenCoords); - widget = widgetIndex == kWidgetIndexNull ? nullptr : &w->widgets[widgetIndex]; - - switch (_inputState) - { - case InputState::Reset: - WindowTooltipReset(screenCoords); - // fall-through - case InputState::Normal: - switch (state) - { - case MouseState::Released: - InputWidgetOver(screenCoords, w, widgetIndex); - break; - case MouseState::LeftPress: - InputWidgetLeft(screenCoords, w, widgetIndex); - break; - case MouseState::RightPress: - WindowCloseByClass(WindowClass::Tooltip); - - if (w != nullptr) - { - w = WindowBringToFront(*w); - } - - if (widgetIndex != kWidgetIndexNull) - { - switch (widget->type) - { - case WindowWidgetType::Viewport: - if (!(gScreenFlags & (SCREEN_FLAGS_TRACK_MANAGER | SCREEN_FLAGS_TITLE_DEMO))) - { - InputViewportDragBegin(*w); - } - break; - case WindowWidgetType::Scroll: - InputScrollDragBegin(screenCoords, w, widgetIndex); - break; - default: - break; - } - } - break; - case MouseState::LeftRelease: - case MouseState::RightRelease: - // In this switch only button presses are relevant - break; - } - break; - case InputState::WidgetPressed: - InputStateWidgetPressed(screenCoords, state, widgetIndex, w, widget); - break; - case InputState::PositioningWindow: - w = windowMgr->FindByNumber(_dragWidget.window_classification, _dragWidget.window_number); - if (w == nullptr) - { - _inputState = InputState::Reset; - } - else - { - InputWindowPositionContinue(*w, gInputDragLast, screenCoords); - if (state == MouseState::LeftRelease) + switch (state) + { + case MouseState::Released: + if (screenCoords.x != 0 || screenCoords.y != 0) { - InputWindowPositionEnd(*w, screenCoords); + _ticksSinceDragStart = std::nullopt; + InputScrollDragContinue(screenCoords, w); } - } - break; - case InputState::ViewportRight: - if (state == MouseState::Released) - { - InputViewportDragContinue(); - } - else if (state == MouseState::RightRelease) - { - InputViewportDragEnd(); - if (_ticksSinceDragStart.has_value() && gCurrentRealTimeTicks - _ticksSinceDragStart.value() < 500) - { - // If the user pressed the right mouse button for less than 500 ticks, interpret as right click - ViewportInteractionRightClick(screenCoords); - } - } - break; - case InputState::DropdownActive: - InputStateWidgetPressed(screenCoords, state, widgetIndex, w, widget); - break; - case InputState::ViewportLeft: - w = windowMgr->FindByNumber(_dragWidget.window_classification, _dragWidget.window_number); - if (w == nullptr) - { - _inputState = InputState::Reset; break; - } + case MouseState::RightRelease: + _inputState = InputState::Reset; + ContextShowCursor(); + break; + case MouseState::LeftPress: + case MouseState::LeftRelease: + case MouseState::RightPress: + // Function only handles right button, so it's the only one relevant + break; + } + } - switch (state) - { - case MouseState::Released: - if (w->viewport == nullptr) - { - _inputState = InputState::Reset; + /** + * + * rct2: 0x006E8655 + */ + static void GameHandleInputMouse(const ScreenCoordsXY& screenCoords, MouseState state) + { + WindowBase* w; + Widget* widget; + WidgetIndex widgetIndex; + + auto* windowMgr = GetContext()->GetUiContext()->GetWindowManager(); + + // Get window and widget under cursor position + w = windowMgr->FindFromPoint(screenCoords); + widgetIndex = w == nullptr ? -1 : windowMgr->FindWidgetFromPoint(*w, screenCoords); + widget = widgetIndex == kWidgetIndexNull ? nullptr : &w->widgets[widgetIndex]; + + switch (_inputState) + { + case InputState::Reset: + WindowTooltipReset(screenCoords); + // fall-through + case InputState::Normal: + switch (state) + { + case MouseState::Released: + InputWidgetOver(screenCoords, w, widgetIndex); break; - } - - if (!InputTestFlag(INPUT_FLAG_4)) + case MouseState::LeftPress: + InputWidgetLeft(screenCoords, w, widgetIndex); break; + case MouseState::RightPress: + WindowCloseByClass(WindowClass::Tooltip); - if (w->classification != _dragWidget.window_classification || w->number != _dragWidget.window_number - || !(_inputFlags & INPUT_FLAG_TOOL_ACTIVE)) - { - break; - } - - w = windowMgr->FindByNumber(gCurrentToolWidget.window_classification, gCurrentToolWidget.window_number); - if (w == nullptr) - { - break; - } - - w->OnToolDrag(gCurrentToolWidget.widget_index, screenCoords); - break; - case MouseState::LeftRelease: - _inputState = InputState::Reset; - if (_dragWidget.window_number == w->number) - { - if ((_inputFlags & INPUT_FLAG_TOOL_ACTIVE)) + if (w != nullptr) { - w = windowMgr->FindByNumber( - gCurrentToolWidget.window_classification, gCurrentToolWidget.window_number); - if (w != nullptr) + w = WindowBringToFront(*w); + } + + if (widgetIndex != kWidgetIndexNull) + { + switch (widget->type) { - w->OnToolUp(gCurrentToolWidget.widget_index, screenCoords); + case WindowWidgetType::Viewport: + if (!(gScreenFlags & (SCREEN_FLAGS_TRACK_MANAGER | SCREEN_FLAGS_TITLE_DEMO))) + { + InputViewportDragBegin(*w); + } + break; + case WindowWidgetType::Scroll: + InputScrollDragBegin(screenCoords, w, widgetIndex); + break; + default: + break; } } - else if (!(_inputFlags & INPUT_FLAG_4)) - { - ViewportInteractionLeftClick(screenCoords); - } + break; + case MouseState::LeftRelease: + case MouseState::RightRelease: + // In this switch only button presses are relevant + break; + } + break; + case InputState::WidgetPressed: + InputStateWidgetPressed(screenCoords, state, widgetIndex, w, widget); + break; + case InputState::PositioningWindow: + w = windowMgr->FindByNumber(_dragWidget.window_classification, _dragWidget.window_number); + if (w == nullptr) + { + _inputState = InputState::Reset; + } + else + { + InputWindowPositionContinue(*w, gInputDragLast, screenCoords); + if (state == MouseState::LeftRelease) + { + InputWindowPositionEnd(*w, screenCoords); } - break; - case MouseState::LeftPress: - case MouseState::RightPress: - case MouseState::RightRelease: - // In this switch only left button release is relevant - break; - } - break; - case InputState::ScrollLeft: - switch (state) - { - case MouseState::Released: - InputScrollContinue(*w, widgetIndex, screenCoords); - break; - case MouseState::LeftRelease: - InputScrollEnd(); - break; - case MouseState::LeftPress: - case MouseState::RightPress: - case MouseState::RightRelease: - // In this switch only left button release is relevant - break; - } - break; - case InputState::Resizing: - w = windowMgr->FindByNumber(_dragWidget.window_classification, _dragWidget.window_number); - if (w == nullptr) - { - _inputState = InputState::Reset; - } - else - { - if (state == MouseState::LeftRelease) - { - InputWindowResizeEnd(); } - if (state == MouseState::Released || state == MouseState::LeftRelease) + break; + case InputState::ViewportRight: + if (state == MouseState::Released) { - InputWindowResizeContinue(*w, screenCoords); + InputViewportDragContinue(); } - } - break; - case InputState::ScrollRight: - InputScrollRight(screenCoords, state); - break; + else if (state == MouseState::RightRelease) + { + InputViewportDragEnd(); + if (_ticksSinceDragStart.has_value() && gCurrentRealTimeTicks - _ticksSinceDragStart.value() < 500) + { + // If the user pressed the right mouse button for less than 500 ticks, interpret as right click + ViewportInteractionRightClick(screenCoords); + } + } + break; + case InputState::DropdownActive: + InputStateWidgetPressed(screenCoords, state, widgetIndex, w, widget); + break; + case InputState::ViewportLeft: + w = windowMgr->FindByNumber(_dragWidget.window_classification, _dragWidget.window_number); + if (w == nullptr) + { + _inputState = InputState::Reset; + break; + } + + switch (state) + { + case MouseState::Released: + if (w->viewport == nullptr) + { + _inputState = InputState::Reset; + break; + } + + if (!InputTestFlag(INPUT_FLAG_4)) + break; + + if (w->classification != _dragWidget.window_classification || w->number != _dragWidget.window_number + || !(_inputFlags & INPUT_FLAG_TOOL_ACTIVE)) + { + break; + } + + w = windowMgr->FindByNumber(gCurrentToolWidget.window_classification, gCurrentToolWidget.window_number); + if (w == nullptr) + { + break; + } + + w->OnToolDrag(gCurrentToolWidget.widget_index, screenCoords); + break; + case MouseState::LeftRelease: + _inputState = InputState::Reset; + if (_dragWidget.window_number == w->number) + { + if ((_inputFlags & INPUT_FLAG_TOOL_ACTIVE)) + { + w = windowMgr->FindByNumber( + gCurrentToolWidget.window_classification, gCurrentToolWidget.window_number); + if (w != nullptr) + { + w->OnToolUp(gCurrentToolWidget.widget_index, screenCoords); + } + } + else if (!(_inputFlags & INPUT_FLAG_4)) + { + ViewportInteractionLeftClick(screenCoords); + } + } + break; + case MouseState::LeftPress: + case MouseState::RightPress: + case MouseState::RightRelease: + // In this switch only left button release is relevant + break; + } + break; + case InputState::ScrollLeft: + switch (state) + { + case MouseState::Released: + InputScrollContinue(*w, widgetIndex, screenCoords); + break; + case MouseState::LeftRelease: + InputScrollEnd(); + break; + case MouseState::LeftPress: + case MouseState::RightPress: + case MouseState::RightRelease: + // In this switch only left button release is relevant + break; + } + break; + case InputState::Resizing: + w = windowMgr->FindByNumber(_dragWidget.window_classification, _dragWidget.window_number); + if (w == nullptr) + { + _inputState = InputState::Reset; + } + else + { + if (state == MouseState::LeftRelease) + { + InputWindowResizeEnd(); + } + if (state == MouseState::Released || state == MouseState::LeftRelease) + { + InputWindowResizeContinue(*w, screenCoords); + } + } + break; + case InputState::ScrollRight: + InputScrollRight(screenCoords, state); + break; + } } -} #pragma region Window positioning / resizing -void InputWindowPositionBegin(WindowBase& w, WidgetIndex widgetIndex, const ScreenCoordsXY& screenCoords) -{ - _inputState = InputState::PositioningWindow; - gInputDragLast = screenCoords - w.windowPos; - _dragWidget.window_classification = w.classification; - _dragWidget.window_number = w.number; - _dragWidget.widget_index = widgetIndex; -} - -static void InputWindowPositionContinue( - WindowBase& w, const ScreenCoordsXY& lastScreenCoords, const ScreenCoordsXY& newScreenCoords) -{ - int32_t snapProximity; - - snapProximity = (w.flags & WF_NO_SNAPPING) ? 0 : Config::Get().general.WindowSnapProximity; - WindowMoveAndSnap(w, newScreenCoords - lastScreenCoords, snapProximity); -} - -static void InputWindowPositionEnd(WindowBase& w, const ScreenCoordsXY& screenCoords) -{ - _inputState = InputState::Normal; - gTooltipCloseTimeout = 0; - gTooltipWidget = _dragWidget; - w.OnMoved(screenCoords); -} - -static void InputWindowResizeBegin(WindowBase& w, WidgetIndex widgetIndex, const ScreenCoordsXY& screenCoords) -{ - _inputState = InputState::Resizing; - gInputDragLast = screenCoords; - _dragWidget.window_classification = w.classification; - _dragWidget.window_number = w.number; - _dragWidget.widget_index = widgetIndex; - _originalWindowWidth = w.width; - _originalWindowHeight = w.height; -} - -static void InputWindowResizeContinue(WindowBase& w, const ScreenCoordsXY& screenCoords) -{ - if (screenCoords.y < static_cast(ContextGetHeight()) - 2) + void InputWindowPositionBegin(WindowBase& w, WidgetIndex widgetIndex, const ScreenCoordsXY& screenCoords) { - auto differentialCoords = screenCoords - gInputDragLast; - int32_t targetWidth = _originalWindowWidth + differentialCoords.x - w.width; - int32_t targetHeight = _originalWindowHeight + differentialCoords.y - w.height; - - WindowResize(w, targetWidth, targetHeight); + _inputState = InputState::PositioningWindow; + gInputDragLast = screenCoords - w.windowPos; + _dragWidget.window_classification = w.classification; + _dragWidget.window_number = w.number; + _dragWidget.widget_index = widgetIndex; } -} -static void InputWindowResizeEnd() -{ - _inputState = InputState::Normal; - gTooltipCloseTimeout = 0; - gTooltipWidget = _dragWidget; -} + static void InputWindowPositionContinue( + WindowBase& w, const ScreenCoordsXY& lastScreenCoords, const ScreenCoordsXY& newScreenCoords) + { + int32_t snapProximity; + + snapProximity = (w.flags & WF_NO_SNAPPING) ? 0 : Config::Get().general.WindowSnapProximity; + WindowMoveAndSnap(w, newScreenCoords - lastScreenCoords, snapProximity); + } + + static void InputWindowPositionEnd(WindowBase& w, const ScreenCoordsXY& screenCoords) + { + _inputState = InputState::Normal; + gTooltipCloseTimeout = 0; + gTooltipWidget = _dragWidget; + w.OnMoved(screenCoords); + } + + static void InputWindowResizeBegin(WindowBase& w, WidgetIndex widgetIndex, const ScreenCoordsXY& screenCoords) + { + _inputState = InputState::Resizing; + gInputDragLast = screenCoords; + _dragWidget.window_classification = w.classification; + _dragWidget.window_number = w.number; + _dragWidget.widget_index = widgetIndex; + _originalWindowWidth = w.width; + _originalWindowHeight = w.height; + } + + static void InputWindowResizeContinue(WindowBase& w, const ScreenCoordsXY& screenCoords) + { + if (screenCoords.y < static_cast(ContextGetHeight()) - 2) + { + auto differentialCoords = screenCoords - gInputDragLast; + int32_t targetWidth = _originalWindowWidth + differentialCoords.x - w.width; + int32_t targetHeight = _originalWindowHeight + differentialCoords.y - w.height; + + WindowResize(w, targetWidth, targetHeight); + } + } + + static void InputWindowResizeEnd() + { + _inputState = InputState::Normal; + gTooltipCloseTimeout = 0; + gTooltipWidget = _dragWidget; + } #pragma endregion #pragma region Viewport dragging -static void InputViewportDragBegin(WindowBase& w) -{ - w.flags &= ~WF_SCROLLING_TO_LOCATION; - _inputState = InputState::ViewportRight; - _dragWidget.window_classification = w.classification; - _dragWidget.window_number = w.number; - _ticksSinceDragStart = gCurrentRealTimeTicks; - auto cursorPosition = ContextGetCursorPosition(); - gInputDragLast = cursorPosition; - if (!Config::Get().general.InvertViewportDrag) + static void InputViewportDragBegin(WindowBase& w) { - ContextHideCursor(); - } - - WindowUnfollowSprite(w); - // gInputFlags |= INPUT_FLAG_5; -} - -static void InputViewportDragContinue() -{ - WindowBase* w; - Viewport* viewport; - - auto newDragCoords = ContextGetCursorPosition(); - - auto differentialCoords = newDragCoords - gInputDragLast; - if (differentialCoords.x == 0 && differentialCoords.y == 0) - return; - - auto* windowMgr = GetContext()->GetUiContext()->GetWindowManager(); - w = windowMgr->FindByNumber(_dragWidget.window_classification, _dragWidget.window_number); - - // #3294: Window can be closed during a drag session, so just finish - // the session if the window no longer exists - if (w == nullptr) - { - InputViewportDragEnd(); - return; - } - - viewport = w->viewport; - if (viewport == nullptr) - { - ContextShowCursor(); - _inputState = InputState::Reset; - } - else if (differentialCoords.x != 0 || differentialCoords.y != 0) - { - if (!(w->flags & WF_NO_SCROLLING)) + w.flags &= ~WF_SCROLLING_TO_LOCATION; + _inputState = InputState::ViewportRight; + _dragWidget.window_classification = w.classification; + _dragWidget.window_number = w.number; + _ticksSinceDragStart = gCurrentRealTimeTicks; + auto cursorPosition = ContextGetCursorPosition(); + gInputDragLast = cursorPosition; + if (!Config::Get().general.InvertViewportDrag) { - // User dragged a scrollable viewport + ContextHideCursor(); + } - // If the drag time is less than 500 the "drag" is usually interpreted as a right click. - // As the user moved the mouse, don't interpret it as right click in any case. - _ticksSinceDragStart = std::nullopt; + WindowUnfollowSprite(w); + // gInputFlags |= INPUT_FLAG_5; + } - // applying the zoom only with negative values avoids a "deadzone" effect where small positive value round to zero. - const bool posX = differentialCoords.x > 0; - const bool posY = differentialCoords.y > 0; - differentialCoords.x = (viewport->zoom + 1).ApplyTo(-std::abs(differentialCoords.x)); - differentialCoords.y = (viewport->zoom + 1).ApplyTo(-std::abs(differentialCoords.y)); - differentialCoords.x = posX ? -differentialCoords.x : differentialCoords.x; - differentialCoords.y = posY ? -differentialCoords.y : differentialCoords.y; + static void InputViewportDragContinue() + { + WindowBase* w; + Viewport* viewport; - if (Config::Get().general.InvertViewportDrag) + auto newDragCoords = ContextGetCursorPosition(); + + auto differentialCoords = newDragCoords - gInputDragLast; + if (differentialCoords.x == 0 && differentialCoords.y == 0) + return; + + auto* windowMgr = GetContext()->GetUiContext()->GetWindowManager(); + w = windowMgr->FindByNumber(_dragWidget.window_classification, _dragWidget.window_number); + + // #3294: Window can be closed during a drag session, so just finish + // the session if the window no longer exists + if (w == nullptr) + { + InputViewportDragEnd(); + return; + } + + viewport = w->viewport; + if (viewport == nullptr) + { + ContextShowCursor(); + _inputState = InputState::Reset; + } + else if (differentialCoords.x != 0 || differentialCoords.y != 0) + { + if (!(w->flags & WF_NO_SCROLLING)) { - w->savedViewPos -= differentialCoords; - } - else - { - w->savedViewPos += differentialCoords; + // User dragged a scrollable viewport + + // If the drag time is less than 500 the "drag" is usually interpreted as a right click. + // As the user moved the mouse, don't interpret it as right click in any case. + _ticksSinceDragStart = std::nullopt; + + // applying the zoom only with negative values avoids a "deadzone" effect where small positive value round to + // zero. + const bool posX = differentialCoords.x > 0; + const bool posY = differentialCoords.y > 0; + differentialCoords.x = (viewport->zoom + 1).ApplyTo(-std::abs(differentialCoords.x)); + differentialCoords.y = (viewport->zoom + 1).ApplyTo(-std::abs(differentialCoords.y)); + differentialCoords.x = posX ? -differentialCoords.x : differentialCoords.x; + differentialCoords.y = posY ? -differentialCoords.y : differentialCoords.y; + + if (Config::Get().general.InvertViewportDrag) + { + w->savedViewPos -= differentialCoords; + } + else + { + w->savedViewPos += differentialCoords; + } } } + + const CursorState* cursorState = ContextGetCursorState(); + if (cursorState->touch || Config::Get().general.InvertViewportDrag) + { + gInputDragLast = newDragCoords; + } + else + { + ContextSetCursorPosition(gInputDragLast); + } } - const CursorState* cursorState = ContextGetCursorState(); - if (cursorState->touch || Config::Get().general.InvertViewportDrag) + static void InputViewportDragEnd() { - gInputDragLast = newDragCoords; + _inputState = InputState::Reset; + ContextShowCursor(); } - else - { - ContextSetCursorPosition(gInputDragLast); - } -} - -static void InputViewportDragEnd() -{ - _inputState = InputState::Reset; - ContextShowCursor(); -} #pragma endregion #pragma region Scroll bars -static void InputScrollBegin(WindowBase& w, WidgetIndex widgetIndex, const ScreenCoordsXY& screenCoords) -{ - const auto& widget = w.widgets[widgetIndex]; - - _inputState = InputState::ScrollLeft; - gPressedWidget.window_classification = w.classification; - gPressedWidget.window_number = w.number; - gPressedWidget.widget_index = widgetIndex; - gTooltipCursor = screenCoords; - - int32_t scroll_area, scroll_id; - ScreenCoordsXY scrollCoords; - scroll_id = 0; // safety - WidgetScrollGetPart(w, &widget, screenCoords, scrollCoords, &scroll_area, &scroll_id); - - _currentScrollArea = scroll_area; - _currentScrollIndex = scroll_id; - w.OnScrollSelect(scroll_id, scroll_area); - if (scroll_area == SCROLL_PART_VIEW) + static void InputScrollBegin(WindowBase& w, WidgetIndex widgetIndex, const ScreenCoordsXY& screenCoords) { - w.OnScrollMouseDown(scroll_id, scrollCoords); - return; - } + const auto& widget = w.widgets[widgetIndex]; - const auto& widg = w.widgets[widgetIndex]; - auto& scroll = w.scrolls[scroll_id]; + _inputState = InputState::ScrollLeft; + gPressedWidget.window_classification = w.classification; + gPressedWidget.window_number = w.number; + gPressedWidget.widget_index = widgetIndex; + gTooltipCursor = screenCoords; - int32_t widget_width = widg.width() - 1; - if (scroll.flags & VSCROLLBAR_VISIBLE) - widget_width -= kScrollBarWidth + 1; - int32_t widget_content_width = std::max(scroll.contentWidth - widget_width, 0); + int32_t scroll_area, scroll_id; + ScreenCoordsXY scrollCoords; + scroll_id = 0; // safety + WidgetScrollGetPart(w, &widget, screenCoords, scrollCoords, &scroll_area, &scroll_id); - int32_t widget_height = widg.bottom - widg.top - 1; - if (scroll.flags & HSCROLLBAR_VISIBLE) - widget_height -= kScrollBarWidth + 1; - int32_t widget_content_height = std::max(scroll.contentHeight - widget_height, 0); + _currentScrollArea = scroll_area; + _currentScrollIndex = scroll_id; + w.OnScrollSelect(scroll_id, scroll_area); + if (scroll_area == SCROLL_PART_VIEW) + { + w.OnScrollMouseDown(scroll_id, scrollCoords); + return; + } - switch (scroll_area) - { - case SCROLL_PART_HSCROLLBAR_LEFT: - scroll.contentOffsetX = std::max(scroll.contentOffsetX - 3, 0); - break; - case SCROLL_PART_HSCROLLBAR_RIGHT: - scroll.contentOffsetX = std::min(scroll.contentOffsetX + 3, widget_content_width); - break; - case SCROLL_PART_HSCROLLBAR_LEFT_TROUGH: - scroll.contentOffsetX = std::max(scroll.contentOffsetX - widget_width, 0); - break; - case SCROLL_PART_HSCROLLBAR_RIGHT_TROUGH: - scroll.contentOffsetX = std::min(scroll.contentOffsetX + widget_width, widget_content_width); - break; - case SCROLL_PART_VSCROLLBAR_TOP: - scroll.contentOffsetY = std::max(scroll.contentOffsetY - 3, 0); - break; - case SCROLL_PART_VSCROLLBAR_BOTTOM: - scroll.contentOffsetY = std::min(scroll.contentOffsetY + 3, widget_content_height); - break; - case SCROLL_PART_VSCROLLBAR_TOP_TROUGH: - scroll.contentOffsetY = std::max(scroll.contentOffsetY - widget_height, 0); - break; - case SCROLL_PART_VSCROLLBAR_BOTTOM_TROUGH: - scroll.contentOffsetY = std::min(scroll.contentOffsetY + widget_height, widget_content_height); - break; - default: - break; - } - WidgetScrollUpdateThumbs(w, widgetIndex); - WindowInvalidateByNumber(w.classification, w.number); -} - -static void InputScrollContinue(WindowBase& w, WidgetIndex widgetIndex, const ScreenCoordsXY& screenCoords) -{ - int32_t scroll_part, scroll_id; - - const auto& widget = w.widgets[widgetIndex]; - if (w.classification != gPressedWidget.window_classification || w.number != gPressedWidget.window_number - || widgetIndex != gPressedWidget.widget_index) - { - InvalidateScroll(); - return; - } - - ScreenCoordsXY newScreenCoords; - WidgetScrollGetPart(w, &widget, screenCoords, newScreenCoords, &scroll_part, &scroll_id); - - if (_currentScrollArea == SCROLL_PART_HSCROLLBAR_THUMB) - { - int32_t originalTooltipCursorX = gTooltipCursor.x; - gTooltipCursor.x = screenCoords.x; - InputScrollPartUpdateHThumb(w, widgetIndex, screenCoords.x - originalTooltipCursorX, scroll_id); - return; - } - - if (_currentScrollArea == SCROLL_PART_VSCROLLBAR_THUMB) - { - int32_t originalTooltipCursorY = gTooltipCursor.y; - gTooltipCursor.y = screenCoords.y; - InputScrollPartUpdateVThumb(w, widgetIndex, screenCoords.y - originalTooltipCursorY, scroll_id); - return; - } - - if (scroll_part != _currentScrollArea) - { - InvalidateScroll(); - return; - } - - switch (scroll_part) - { - case SCROLL_PART_VIEW: - w.OnScrollMouseDrag(scroll_id, newScreenCoords); - break; - case SCROLL_PART_HSCROLLBAR_LEFT: - InputScrollPartUpdateHLeft(w, widgetIndex, scroll_id); - break; - case SCROLL_PART_HSCROLLBAR_RIGHT: - InputScrollPartUpdateHRight(w, widgetIndex, scroll_id); - break; - case SCROLL_PART_VSCROLLBAR_TOP: - InputScrollPartUpdateVTop(w, widgetIndex, scroll_id); - break; - case SCROLL_PART_VSCROLLBAR_BOTTOM: - InputScrollPartUpdateVBottom(w, widgetIndex, scroll_id); - break; - } -} - -static void InputScrollEnd() -{ - _inputState = InputState::Reset; - InvalidateScroll(); -} - -/** - * - * rct2: 0x006E98F2 - */ -static void InputScrollPartUpdateHThumb(WindowBase& w, WidgetIndex widgetIndex, int32_t x, int32_t scroll_id) -{ - const auto& widget = w.widgets[widgetIndex]; - auto& scroll = w.scrolls[scroll_id]; - - auto* windowMgr = GetContext()->GetUiContext()->GetWindowManager(); - if (windowMgr->FindByNumber(w.classification, w.number) != nullptr) - { - int32_t newLeft; - newLeft = scroll.contentWidth; - newLeft *= x; - x = widget.width() - 21; - if (scroll.flags & VSCROLLBAR_VISIBLE) - x -= kScrollBarWidth + 1; - newLeft /= x; - x = newLeft; - scroll.flags |= HSCROLLBAR_THUMB_PRESSED; - newLeft = scroll.contentOffsetX; - newLeft += x; - if (newLeft < 0) - newLeft = 0; - x = widget.width() - 1; - if (scroll.flags & VSCROLLBAR_VISIBLE) - x -= kScrollBarWidth + 1; - x *= -1; - x += scroll.contentWidth; - if (x < 0) - x = 0; - if (newLeft > x) - newLeft = x; - scroll.contentOffsetX = newLeft; - WidgetScrollUpdateThumbs(w, widgetIndex); - WidgetInvalidateByNumber(w.classification, w.number, widgetIndex); - } -} - -/** - * - * rct2: 0x006E99A9 - */ -static void InputScrollPartUpdateVThumb(WindowBase& w, WidgetIndex widgetIndex, int32_t y, int32_t scroll_id) -{ - const auto& widget = w.widgets[widgetIndex]; - auto& scroll = w.scrolls[scroll_id]; - - auto* windowMgr = GetContext()->GetUiContext()->GetWindowManager(); - if (windowMgr->FindByNumber(w.classification, w.number) != nullptr) - { - int32_t newTop; - newTop = scroll.contentHeight; - newTop *= y; - y = widget.height() - 21; - if (scroll.flags & HSCROLLBAR_VISIBLE) - y -= kScrollBarWidth + 1; - newTop /= y; - y = newTop; - scroll.flags |= VSCROLLBAR_THUMB_PRESSED; - newTop = scroll.contentOffsetY; - newTop += y; - if (newTop < 0) - newTop = 0; - y = widget.height() - 1; - if (scroll.flags & HSCROLLBAR_VISIBLE) - y -= kScrollBarWidth + 1; - y *= -1; - y += scroll.contentHeight; - if (y < 0) - y = 0; - if (newTop > y) - newTop = y; - scroll.contentOffsetY = newTop; - WidgetScrollUpdateThumbs(w, widgetIndex); - WidgetInvalidateByNumber(w.classification, w.number, widgetIndex); - } -} - -/** - * - * rct2: 0x006E9A60 - */ -static void InputScrollPartUpdateHLeft(WindowBase& w, WidgetIndex widgetIndex, int32_t scroll_id) -{ - auto* windowMgr = GetContext()->GetUiContext()->GetWindowManager(); - if (windowMgr->FindByNumber(w.classification, w.number) != nullptr) - { + const auto& widg = w.widgets[widgetIndex]; auto& scroll = w.scrolls[scroll_id]; - scroll.flags |= HSCROLLBAR_LEFT_PRESSED; - if (scroll.contentOffsetX >= 3) - scroll.contentOffsetX -= 3; - WidgetScrollUpdateThumbs(w, widgetIndex); - WidgetInvalidateByNumber(w.classification, w.number, widgetIndex); - } -} -/** - * - * rct2: 0x006E9ABF - */ -static void InputScrollPartUpdateHRight(WindowBase& w, WidgetIndex widgetIndex, int32_t scroll_id) -{ - const auto& widget = w.widgets[widgetIndex]; - - auto* windowMgr = GetContext()->GetUiContext()->GetWindowManager(); - if (windowMgr->FindByNumber(w.classification, w.number) != nullptr) - { - auto& scroll = w.scrolls[scroll_id]; - scroll.flags |= HSCROLLBAR_RIGHT_PRESSED; - scroll.contentOffsetX += 3; - int32_t newLeft = widget.width() - 1; + int32_t widget_width = widg.width() - 1; if (scroll.flags & VSCROLLBAR_VISIBLE) - newLeft -= kScrollBarWidth + 1; - newLeft *= -1; - newLeft += scroll.contentWidth; - if (newLeft < 0) - newLeft = 0; - if (scroll.contentOffsetX > newLeft) + widget_width -= kScrollBarWidth + 1; + int32_t widget_content_width = std::max(scroll.contentWidth - widget_width, 0); + + int32_t widget_height = widg.bottom - widg.top - 1; + if (scroll.flags & HSCROLLBAR_VISIBLE) + widget_height -= kScrollBarWidth + 1; + int32_t widget_content_height = std::max(scroll.contentHeight - widget_height, 0); + + switch (scroll_area) + { + case SCROLL_PART_HSCROLLBAR_LEFT: + scroll.contentOffsetX = std::max(scroll.contentOffsetX - 3, 0); + break; + case SCROLL_PART_HSCROLLBAR_RIGHT: + scroll.contentOffsetX = std::min(scroll.contentOffsetX + 3, widget_content_width); + break; + case SCROLL_PART_HSCROLLBAR_LEFT_TROUGH: + scroll.contentOffsetX = std::max(scroll.contentOffsetX - widget_width, 0); + break; + case SCROLL_PART_HSCROLLBAR_RIGHT_TROUGH: + scroll.contentOffsetX = std::min(scroll.contentOffsetX + widget_width, widget_content_width); + break; + case SCROLL_PART_VSCROLLBAR_TOP: + scroll.contentOffsetY = std::max(scroll.contentOffsetY - 3, 0); + break; + case SCROLL_PART_VSCROLLBAR_BOTTOM: + scroll.contentOffsetY = std::min(scroll.contentOffsetY + 3, widget_content_height); + break; + case SCROLL_PART_VSCROLLBAR_TOP_TROUGH: + scroll.contentOffsetY = std::max(scroll.contentOffsetY - widget_height, 0); + break; + case SCROLL_PART_VSCROLLBAR_BOTTOM_TROUGH: + scroll.contentOffsetY = std::min(scroll.contentOffsetY + widget_height, widget_content_height); + break; + default: + break; + } + WidgetScrollUpdateThumbs(w, widgetIndex); + WindowInvalidateByNumber(w.classification, w.number); + } + + static void InputScrollContinue(WindowBase& w, WidgetIndex widgetIndex, const ScreenCoordsXY& screenCoords) + { + int32_t scroll_part, scroll_id; + + const auto& widget = w.widgets[widgetIndex]; + if (w.classification != gPressedWidget.window_classification || w.number != gPressedWidget.window_number + || widgetIndex != gPressedWidget.widget_index) + { + InvalidateScroll(); + return; + } + + ScreenCoordsXY newScreenCoords; + WidgetScrollGetPart(w, &widget, screenCoords, newScreenCoords, &scroll_part, &scroll_id); + + if (_currentScrollArea == SCROLL_PART_HSCROLLBAR_THUMB) + { + int32_t originalTooltipCursorX = gTooltipCursor.x; + gTooltipCursor.x = screenCoords.x; + InputScrollPartUpdateHThumb(w, widgetIndex, screenCoords.x - originalTooltipCursorX, scroll_id); + return; + } + + if (_currentScrollArea == SCROLL_PART_VSCROLLBAR_THUMB) + { + int32_t originalTooltipCursorY = gTooltipCursor.y; + gTooltipCursor.y = screenCoords.y; + InputScrollPartUpdateVThumb(w, widgetIndex, screenCoords.y - originalTooltipCursorY, scroll_id); + return; + } + + if (scroll_part != _currentScrollArea) + { + InvalidateScroll(); + return; + } + + switch (scroll_part) + { + case SCROLL_PART_VIEW: + w.OnScrollMouseDrag(scroll_id, newScreenCoords); + break; + case SCROLL_PART_HSCROLLBAR_LEFT: + InputScrollPartUpdateHLeft(w, widgetIndex, scroll_id); + break; + case SCROLL_PART_HSCROLLBAR_RIGHT: + InputScrollPartUpdateHRight(w, widgetIndex, scroll_id); + break; + case SCROLL_PART_VSCROLLBAR_TOP: + InputScrollPartUpdateVTop(w, widgetIndex, scroll_id); + break; + case SCROLL_PART_VSCROLLBAR_BOTTOM: + InputScrollPartUpdateVBottom(w, widgetIndex, scroll_id); + break; + } + } + + static void InputScrollEnd() + { + _inputState = InputState::Reset; + InvalidateScroll(); + } + + /** + * + * rct2: 0x006E98F2 + */ + static void InputScrollPartUpdateHThumb(WindowBase& w, WidgetIndex widgetIndex, int32_t x, int32_t scroll_id) + { + const auto& widget = w.widgets[widgetIndex]; + auto& scroll = w.scrolls[scroll_id]; + + auto* windowMgr = GetContext()->GetUiContext()->GetWindowManager(); + if (windowMgr->FindByNumber(w.classification, w.number) != nullptr) + { + int32_t newLeft; + newLeft = scroll.contentWidth; + newLeft *= x; + x = widget.width() - 21; + if (scroll.flags & VSCROLLBAR_VISIBLE) + x -= kScrollBarWidth + 1; + newLeft /= x; + x = newLeft; + scroll.flags |= HSCROLLBAR_THUMB_PRESSED; + newLeft = scroll.contentOffsetX; + newLeft += x; + if (newLeft < 0) + newLeft = 0; + x = widget.width() - 1; + if (scroll.flags & VSCROLLBAR_VISIBLE) + x -= kScrollBarWidth + 1; + x *= -1; + x += scroll.contentWidth; + if (x < 0) + x = 0; + if (newLeft > x) + newLeft = x; scroll.contentOffsetX = newLeft; - WidgetScrollUpdateThumbs(w, widgetIndex); - WidgetInvalidateByNumber(w.classification, w.number, widgetIndex); + WidgetScrollUpdateThumbs(w, widgetIndex); + WidgetInvalidateByNumber(w.classification, w.number, widgetIndex); + } } -} -/** - * - * rct2: 0x006E9C37 - */ -static void InputScrollPartUpdateVTop(WindowBase& w, WidgetIndex widgetIndex, int32_t scroll_id) -{ - auto* windowMgr = GetContext()->GetUiContext()->GetWindowManager(); - if (windowMgr->FindByNumber(w.classification, w.number) != nullptr) + /** + * + * rct2: 0x006E99A9 + */ + static void InputScrollPartUpdateVThumb(WindowBase& w, WidgetIndex widgetIndex, int32_t y, int32_t scroll_id) { + const auto& widget = w.widgets[widgetIndex]; auto& scroll = w.scrolls[scroll_id]; - scroll.flags |= VSCROLLBAR_UP_PRESSED; - if (scroll.contentOffsetY >= 3) - scroll.contentOffsetY -= 3; - WidgetScrollUpdateThumbs(w, widgetIndex); - WidgetInvalidateByNumber(w.classification, w.number, widgetIndex); - } -} -/** - * - * rct2: 0x006E9C96 - */ -static void InputScrollPartUpdateVBottom(WindowBase& w, WidgetIndex widgetIndex, int32_t scroll_id) -{ - const auto& widget = w.widgets[widgetIndex]; - - auto* windowMgr = GetContext()->GetUiContext()->GetWindowManager(); - if (windowMgr->FindByNumber(w.classification, w.number) != nullptr) - { - auto& scroll = w.scrolls[scroll_id]; - scroll.flags |= VSCROLLBAR_DOWN_PRESSED; - scroll.contentOffsetY += 3; - int32_t newTop = widget.height() - 1; - if (scroll.flags & HSCROLLBAR_VISIBLE) - newTop -= kScrollBarWidth + 1; - newTop *= -1; - newTop += scroll.contentHeight; - if (newTop < 0) - newTop = 0; - if (scroll.contentOffsetY > newTop) + auto* windowMgr = GetContext()->GetUiContext()->GetWindowManager(); + if (windowMgr->FindByNumber(w.classification, w.number) != nullptr) + { + int32_t newTop; + newTop = scroll.contentHeight; + newTop *= y; + y = widget.height() - 21; + if (scroll.flags & HSCROLLBAR_VISIBLE) + y -= kScrollBarWidth + 1; + newTop /= y; + y = newTop; + scroll.flags |= VSCROLLBAR_THUMB_PRESSED; + newTop = scroll.contentOffsetY; + newTop += y; + if (newTop < 0) + newTop = 0; + y = widget.height() - 1; + if (scroll.flags & HSCROLLBAR_VISIBLE) + y -= kScrollBarWidth + 1; + y *= -1; + y += scroll.contentHeight; + if (y < 0) + y = 0; + if (newTop > y) + newTop = y; scroll.contentOffsetY = newTop; - WidgetScrollUpdateThumbs(w, widgetIndex); - WidgetInvalidateByNumber(w.classification, w.number, widgetIndex); + WidgetScrollUpdateThumbs(w, widgetIndex); + WidgetInvalidateByNumber(w.classification, w.number, widgetIndex); + } + } + + /** + * + * rct2: 0x006E9A60 + */ + static void InputScrollPartUpdateHLeft(WindowBase& w, WidgetIndex widgetIndex, int32_t scroll_id) + { + auto* windowMgr = GetContext()->GetUiContext()->GetWindowManager(); + if (windowMgr->FindByNumber(w.classification, w.number) != nullptr) + { + auto& scroll = w.scrolls[scroll_id]; + scroll.flags |= HSCROLLBAR_LEFT_PRESSED; + if (scroll.contentOffsetX >= 3) + scroll.contentOffsetX -= 3; + WidgetScrollUpdateThumbs(w, widgetIndex); + WidgetInvalidateByNumber(w.classification, w.number, widgetIndex); + } + } + + /** + * + * rct2: 0x006E9ABF + */ + static void InputScrollPartUpdateHRight(WindowBase& w, WidgetIndex widgetIndex, int32_t scroll_id) + { + const auto& widget = w.widgets[widgetIndex]; + + auto* windowMgr = GetContext()->GetUiContext()->GetWindowManager(); + if (windowMgr->FindByNumber(w.classification, w.number) != nullptr) + { + auto& scroll = w.scrolls[scroll_id]; + scroll.flags |= HSCROLLBAR_RIGHT_PRESSED; + scroll.contentOffsetX += 3; + int32_t newLeft = widget.width() - 1; + if (scroll.flags & VSCROLLBAR_VISIBLE) + newLeft -= kScrollBarWidth + 1; + newLeft *= -1; + newLeft += scroll.contentWidth; + if (newLeft < 0) + newLeft = 0; + if (scroll.contentOffsetX > newLeft) + scroll.contentOffsetX = newLeft; + WidgetScrollUpdateThumbs(w, widgetIndex); + WidgetInvalidateByNumber(w.classification, w.number, widgetIndex); + } + } + + /** + * + * rct2: 0x006E9C37 + */ + static void InputScrollPartUpdateVTop(WindowBase& w, WidgetIndex widgetIndex, int32_t scroll_id) + { + auto* windowMgr = GetContext()->GetUiContext()->GetWindowManager(); + if (windowMgr->FindByNumber(w.classification, w.number) != nullptr) + { + auto& scroll = w.scrolls[scroll_id]; + scroll.flags |= VSCROLLBAR_UP_PRESSED; + if (scroll.contentOffsetY >= 3) + scroll.contentOffsetY -= 3; + WidgetScrollUpdateThumbs(w, widgetIndex); + WidgetInvalidateByNumber(w.classification, w.number, widgetIndex); + } + } + + /** + * + * rct2: 0x006E9C96 + */ + static void InputScrollPartUpdateVBottom(WindowBase& w, WidgetIndex widgetIndex, int32_t scroll_id) + { + const auto& widget = w.widgets[widgetIndex]; + + auto* windowMgr = GetContext()->GetUiContext()->GetWindowManager(); + if (windowMgr->FindByNumber(w.classification, w.number) != nullptr) + { + auto& scroll = w.scrolls[scroll_id]; + scroll.flags |= VSCROLLBAR_DOWN_PRESSED; + scroll.contentOffsetY += 3; + int32_t newTop = widget.height() - 1; + if (scroll.flags & HSCROLLBAR_VISIBLE) + newTop -= kScrollBarWidth + 1; + newTop *= -1; + newTop += scroll.contentHeight; + if (newTop < 0) + newTop = 0; + if (scroll.contentOffsetY > newTop) + scroll.contentOffsetY = newTop; + WidgetScrollUpdateThumbs(w, widgetIndex); + WidgetInvalidateByNumber(w.classification, w.number, widgetIndex); + } } -} #pragma endregion #pragma region Widgets -/** - * - * rct2: 0x006E9253 - */ -static void InputWidgetOver(const ScreenCoordsXY& screenCoords, WindowBase* w, WidgetIndex widgetIndex) -{ - WindowClass windowClass = WindowClass::Null; - rct_windownumber windowNumber = 0; - Widget* widget = nullptr; - - if (w != nullptr) + /** + * + * rct2: 0x006E9253 + */ + static void InputWidgetOver(const ScreenCoordsXY& screenCoords, WindowBase* w, WidgetIndex widgetIndex) { - windowClass = w->classification; - windowNumber = w->number; - widget = &w->widgets[widgetIndex]; - } + WindowClass windowClass = WindowClass::Null; + rct_windownumber windowNumber = 0; + Widget* widget = nullptr; - InputWidgetOverChangeCheck(windowClass, windowNumber, widgetIndex); + if (w != nullptr) + { + windowClass = w->classification; + windowNumber = w->number; + widget = &w->widgets[widgetIndex]; + } - if (w != nullptr && widgetIndex != kWidgetIndexNull && widget->type == WindowWidgetType::Scroll) - { - int32_t scroll_part, scrollId; - ScreenCoordsXY newScreenCoords; - WidgetScrollGetPart(*w, widget, screenCoords, newScreenCoords, &scroll_part, &scrollId); + InputWidgetOverChangeCheck(windowClass, windowNumber, widgetIndex); - if (scroll_part != SCROLL_PART_VIEW) - WindowTooltipClose(); + if (w != nullptr && widgetIndex != kWidgetIndexNull && widget->type == WindowWidgetType::Scroll) + { + int32_t scroll_part, scrollId; + ScreenCoordsXY newScreenCoords; + WidgetScrollGetPart(*w, widget, screenCoords, newScreenCoords, &scroll_part, &scrollId); + + if (scroll_part != SCROLL_PART_VIEW) + WindowTooltipClose(); + else + { + w->OnScrollMouseOver(scrollId, newScreenCoords); + InputUpdateTooltip(w, widgetIndex, screenCoords); + } + } else { - w->OnScrollMouseOver(scrollId, newScreenCoords); InputUpdateTooltip(w, widgetIndex, screenCoords); } } - else + + /** + * + * rct2: 0x006E9269 + */ + static void InputWidgetOverChangeCheck(WindowClass windowClass, rct_windownumber windowNumber, WidgetIndex widgetIndex) { - InputUpdateTooltip(w, widgetIndex, screenCoords); - } -} + // Prevents invalid widgets being clicked source of bug is elsewhere + if (widgetIndex == kWidgetIndexNull) + return; -/** - * - * rct2: 0x006E9269 - */ -static void InputWidgetOverChangeCheck(WindowClass windowClass, rct_windownumber windowNumber, WidgetIndex widgetIndex) -{ - // Prevents invalid widgets being clicked source of bug is elsewhere - if (widgetIndex == kWidgetIndexNull) - return; - - // Check if the widget that the cursor was over, has changed - if (windowClass != gHoverWidget.window_classification || windowNumber != gHoverWidget.window_number - || widgetIndex != gHoverWidget.widget_index) - { - // Invalidate last widget cursor was on if widget is a flat button - InputWidgetOverFlatbuttonInvalidate(); - - // Set new cursor over widget - gHoverWidget.window_classification = windowClass; - gHoverWidget.window_number = windowNumber; - gHoverWidget.widget_index = widgetIndex; - - // Invalidate new widget cursor is on if widget is a flat button - if (windowClass != WindowClass::Null) + // Check if the widget that the cursor was over, has changed + if (windowClass != gHoverWidget.window_classification || windowNumber != gHoverWidget.window_number + || widgetIndex != gHoverWidget.widget_index) + { + // Invalidate last widget cursor was on if widget is a flat button InputWidgetOverFlatbuttonInvalidate(); - } -} -/** - * Used to invalidate flat button widgets when the mouse leaves and enters them. This should be generalised so that all widgets - * can use this in the future. - */ -static void InputWidgetOverFlatbuttonInvalidate() -{ - auto* windowMgr = GetContext()->GetUiContext()->GetWindowManager(); - WindowBase* w = windowMgr->FindByNumber(gHoverWidget.window_classification, gHoverWidget.window_number); - if (w != nullptr) - { - w->OnPrepareDraw(); - if (w->widgets[gHoverWidget.widget_index].type == WindowWidgetType::FlatBtn) - { - WidgetInvalidateByNumber(gHoverWidget.window_classification, gHoverWidget.window_number, gHoverWidget.widget_index); - } - } -} + // Set new cursor over widget + gHoverWidget.window_classification = windowClass; + gHoverWidget.window_number = windowNumber; + gHoverWidget.widget_index = widgetIndex; -/** - * - * rct2: 0x006E95F9 - */ -static void InputWidgetLeft(const ScreenCoordsXY& screenCoords, WindowBase* w, WidgetIndex widgetIndex) -{ - WindowClass windowClass = WindowClass::Null; - rct_windownumber windowNumber = 0; - - if (w != nullptr) - { - windowClass = w->classification; - windowNumber = w->number; - } - - WindowCloseByClass(WindowClass::Error); - WindowCloseByClass(WindowClass::Tooltip); - - // Window might have changed position in the list, therefore find it again - auto* windowMgr = GetContext()->GetUiContext()->GetWindowManager(); - w = windowMgr->FindByNumber(windowClass, windowNumber); - if (w == nullptr) - return; - - w = WindowBringToFront(*w); - if (widgetIndex == kWidgetIndexNull) - return; - - if (windowClass != GetCurrentTextBox().window.classification || windowNumber != GetCurrentTextBox().window.number - || widgetIndex != GetCurrentTextBox().widget_index) - { - WindowCancelTextbox(); - } - - const auto& widget = w->widgets[widgetIndex]; - - switch (widget.type) - { - case WindowWidgetType::Frame: - case WindowWidgetType::Resize: - if (WindowCanResize(*w) - && (screenCoords.x >= w->windowPos.x + w->width - 19 && screenCoords.y >= w->windowPos.y + w->height - 19)) - InputWindowResizeBegin(*w, widgetIndex, screenCoords); - break; - case WindowWidgetType::Viewport: - _inputState = InputState::ViewportLeft; - gInputDragLast = screenCoords; - _dragWidget.window_classification = windowClass; - _dragWidget.window_number = windowNumber; - if (_inputFlags & INPUT_FLAG_TOOL_ACTIVE) - { - w = windowMgr->FindByNumber(gCurrentToolWidget.window_classification, gCurrentToolWidget.window_number); - if (w != nullptr) - { - InputSetFlag(INPUT_FLAG_4, true); - w->OnToolDown(gCurrentToolWidget.widget_index, screenCoords); - } - } - break; - case WindowWidgetType::Caption: - InputWindowPositionBegin(*w, widgetIndex, screenCoords); - break; - case WindowWidgetType::Scroll: - InputScrollBegin(*w, widgetIndex, screenCoords); - break; - case WindowWidgetType::Empty: - case WindowWidgetType::LabelCentred: - case WindowWidgetType::Label: - case WindowWidgetType::Groupbox: - case WindowWidgetType::ProgressBar: - case WindowWidgetType::Placeholder: - // Non-interactive widget type - break; - case WindowWidgetType::ImgBtn: - case WindowWidgetType::ColourBtn: - case WindowWidgetType::TrnBtn: - case WindowWidgetType::Tab: - case WindowWidgetType::FlatBtn: - case WindowWidgetType::Button: - case WindowWidgetType::TableHeader: - case WindowWidgetType::Spinner: - case WindowWidgetType::DropdownMenu: - case WindowWidgetType::CloseBox: - case WindowWidgetType::Checkbox: - case WindowWidgetType::TextBox: - case WindowWidgetType::Custom: - if (!WidgetIsDisabled(*w, widgetIndex)) - { - OpenRCT2::Audio::Play(OpenRCT2::Audio::SoundId::Click1, 0, w->windowPos.x + widget.midX()); - - // Set new cursor down widget - gPressedWidget.window_classification = windowClass; - gPressedWidget.window_number = windowNumber; - gPressedWidget.widget_index = widgetIndex; - _inputFlags |= INPUT_FLAG_WIDGET_PRESSED; - _inputState = InputState::WidgetPressed; - _clickRepeatTicks = gCurrentRealTimeTicks; - - WidgetInvalidateByNumber(windowClass, windowNumber, widgetIndex); - w->OnMouseDown(widgetIndex); - } - break; - } -} - -/** - * - * rct2: 0x006ED833 - */ -void ProcessMouseOver(const ScreenCoordsXY& screenCoords) -{ - CursorID cursorId = CursorID::Arrow; - auto ft = Formatter(); - ft.Add(STR_NONE); - SetMapTooltip(ft); - - auto* windowMgr = GetContext()->GetUiContext()->GetWindowManager(); - WindowBase* window = windowMgr->FindFromPoint(screenCoords); - - if (window != nullptr) - { - WidgetIndex widgetId = windowMgr->FindWidgetFromPoint(*window, screenCoords); - if (widgetId != kWidgetIndexNull) - { - switch (window->widgets[widgetId].type) - { - case WindowWidgetType::Viewport: - if (!(_inputFlags & INPUT_FLAG_TOOL_ACTIVE)) - { - if (ViewportInteractionLeftOver(screenCoords)) - { - SetCursor(CursorID::HandPoint); - return; - } - break; - } - cursorId = static_cast(gCurrentToolId); - break; - - case WindowWidgetType::Frame: - case WindowWidgetType::Resize: - if (!(window->flags & WF_RESIZABLE)) - break; - - if (window->min_width == window->max_width && window->min_height == window->max_height) - break; - - if (screenCoords.x < window->windowPos.x + window->width - 0x13) - break; - - if (screenCoords.y < window->windowPos.y + window->height - 0x13) - break; - - cursorId = CursorID::DiagonalArrows; - break; - - case WindowWidgetType::Scroll: - { - int32_t output_scroll_area, scroll_id; - ScreenCoordsXY scrollCoords; - WidgetScrollGetPart( - *window, &window->widgets[widgetId], screenCoords, scrollCoords, &output_scroll_area, &scroll_id); - if (output_scroll_area != SCROLL_PART_VIEW) - { - cursorId = CursorID::Arrow; - break; - } - // Same as default but with scroll_x/y - cursorId = window->OnCursor(widgetId, scrollCoords, CursorID::Arrow); - if (cursorId == CursorID::Undefined) - cursorId = CursorID::Arrow; - break; - } - default: - cursorId = window->OnCursor(widgetId, screenCoords, CursorID::Arrow); - if (cursorId == CursorID::Undefined) - cursorId = CursorID::Arrow; - break; - } + // Invalidate new widget cursor is on if widget is a flat button + if (windowClass != WindowClass::Null) + InputWidgetOverFlatbuttonInvalidate(); } } - ViewportInteractionRightOver(screenCoords); - SetCursor(cursorId); -} - -/** - * - * rct2: 0x006ED801 - */ -void ProcessMouseTool(const ScreenCoordsXY& screenCoords) -{ - if (_inputFlags & INPUT_FLAG_TOOL_ACTIVE) + /** + * Used to invalidate flat button widgets when the mouse leaves and enters them. This should be generalised so that all + * widgets can use this in the future. + */ + static void InputWidgetOverFlatbuttonInvalidate() { auto* windowMgr = GetContext()->GetUiContext()->GetWindowManager(); - WindowBase* w = windowMgr->FindByNumber(gCurrentToolWidget.window_classification, gCurrentToolWidget.window_number); - - if (w == nullptr) - ToolCancel(); - else if (InputGetState() != InputState::ViewportRight) - w->OnToolUpdate(gCurrentToolWidget.widget_index, screenCoords); - } -} - -const std::map kColourToTip = { - { COLOUR_BLACK, STR_COLOUR_BLACK_TIP }, - { COLOUR_GREY, STR_COLOUR_GREY_TIP }, - { COLOUR_WHITE, STR_COLOUR_WHITE_TIP }, - { COLOUR_DARK_PURPLE, STR_COLOUR_DARK_PURPLE_TIP }, - { COLOUR_LIGHT_PURPLE, STR_COLOUR_LIGHT_PURPLE_TIP }, - { COLOUR_BRIGHT_PURPLE, STR_COLOUR_BRIGHT_PURPLE_TIP }, - { COLOUR_DARK_BLUE, STR_COLOUR_DARK_BLUE_TIP }, - { COLOUR_LIGHT_BLUE, STR_COLOUR_LIGHT_BLUE_TIP }, - { COLOUR_ICY_BLUE, STR_COLOUR_ICY_BLUE_TIP }, - { COLOUR_TEAL, STR_COLOUR_TEAL_TIP }, - { COLOUR_AQUAMARINE, STR_COLOUR_AQUAMARINE_TIP }, - { COLOUR_SATURATED_GREEN, STR_COLOUR_SATURATED_GREEN_TIP }, - { COLOUR_DARK_GREEN, STR_COLOUR_DARK_GREEN_TIP }, - { COLOUR_MOSS_GREEN, STR_COLOUR_MOSS_GREEN_TIP }, - { COLOUR_BRIGHT_GREEN, STR_COLOUR_BRIGHT_GREEN_TIP }, - { COLOUR_OLIVE_GREEN, STR_COLOUR_OLIVE_GREEN_TIP }, - { COLOUR_DARK_OLIVE_GREEN, STR_COLOUR_DARK_OLIVE_GREEN_TIP }, - { COLOUR_BRIGHT_YELLOW, STR_COLOUR_BRIGHT_YELLOW_TIP }, - { COLOUR_YELLOW, STR_COLOUR_YELLOW_TIP }, - { COLOUR_DARK_YELLOW, STR_COLOUR_DARK_YELLOW_TIP }, - { COLOUR_LIGHT_ORANGE, STR_COLOUR_LIGHT_ORANGE_TIP }, - { COLOUR_DARK_ORANGE, STR_COLOUR_DARK_ORANGE_TIP }, - { COLOUR_LIGHT_BROWN, STR_COLOUR_LIGHT_BROWN_TIP }, - { COLOUR_SATURATED_BROWN, STR_COLOUR_SATURATED_BROWN_TIP }, - { COLOUR_DARK_BROWN, STR_COLOUR_DARK_BROWN_TIP }, - { COLOUR_SALMON_PINK, STR_COLOUR_SALMON_PINK_TIP }, - { COLOUR_BORDEAUX_RED, STR_COLOUR_BORDEAUX_RED_TIP }, - { COLOUR_SATURATED_RED, STR_COLOUR_SATURATED_RED_TIP }, - { COLOUR_BRIGHT_RED, STR_COLOUR_BRIGHT_RED_TIP }, - { COLOUR_DARK_PINK, STR_COLOUR_DARK_PINK_TIP }, - { COLOUR_BRIGHT_PINK, STR_COLOUR_BRIGHT_PINK_TIP }, - { COLOUR_LIGHT_PINK, STR_COLOUR_LIGHT_PINK_TIP }, - { COLOUR_DARK_OLIVE_DARK, STR_COLOUR_DARK_OLIVE_DARK_TIP }, - { COLOUR_DARK_OLIVE_LIGHT, STR_COLOUR_DARK_OLIVE_LIGHT_TIP }, - { COLOUR_SATURATED_BROWN_LIGHT, STR_COLOUR_SATURATED_BROWN_LIGHT_TIP }, - { COLOUR_BORDEAUX_RED_DARK, STR_COLOUR_BORDEAUX_RED_DARK_TIP }, - { COLOUR_BORDEAUX_RED_LIGHT, STR_COLOUR_BORDEAUX_RED_LIGHT_TIP }, - { COLOUR_GRASS_GREEN_DARK, STR_COLOUR_GRASS_GREEN_DARK_TIP }, - { COLOUR_GRASS_GREEN_LIGHT, STR_COLOUR_GRASS_GREEN_LIGHT_TIP }, - { COLOUR_OLIVE_DARK, STR_COLOUR_OLIVE_DARK_TIP }, - { COLOUR_OLIVE_LIGHT, STR_COLOUR_OLIVE_LIGHT_TIP }, - { COLOUR_SATURATED_GREEN_LIGHT, STR_COLOUR_SATURATED_GREEN_LIGHT_TIP }, - { COLOUR_TAN_DARK, STR_COLOUR_TAN_DARK_TIP }, - { COLOUR_TAN_LIGHT, STR_COLOUR_TAN_LIGHT_TIP }, - { COLOUR_DULL_PURPLE_LIGHT, STR_COLOUR_DULL_PURPLE_LIGHT_TIP }, - { COLOUR_DULL_GREEN_DARK, STR_COLOUR_DULL_GREEN_DARK_TIP }, - { COLOUR_DULL_GREEN_LIGHT, STR_COLOUR_DULL_GREEN_LIGHT_TIP }, - { COLOUR_SATURATED_PURPLE_DARK, STR_COLOUR_SATURATED_PURPLE_DARK_TIP }, - { COLOUR_SATURATED_PURPLE_LIGHT, STR_COLOUR_SATURATED_PURPLE_LIGHT_TIP }, - { COLOUR_ORANGE_LIGHT, STR_COLOUR_ORANGE_LIGHT_TIP }, - { COLOUR_AQUA_DARK, STR_COLOUR_AQUA_DARK_TIP }, - { COLOUR_MAGENTA_LIGHT, STR_COLOUR_MAGENTA_LIGHT_TIP }, - { COLOUR_DULL_BROWN_DARK, STR_COLOUR_DULL_BROWN_DARK_TIP }, - { COLOUR_DULL_BROWN_LIGHT, STR_COLOUR_DULL_BROWN_LIGHT_TIP }, - { COLOUR_INVISIBLE, STR_COLOUR_INVISIBLE_TIP }, - { COLOUR_VOID, STR_COLOUR_VOID_TIP }, -}; - -/** - * - * rct2: 0x006E8DA7 - */ -void InputStateWidgetPressed( - const ScreenCoordsXY& screenCoords, MouseState state, WidgetIndex widgetIndex, WindowBase* w, Widget* widget) -{ - WindowClass cursor_w_class; - rct_windownumber cursor_w_number; - cursor_w_class = gPressedWidget.window_classification; - cursor_w_number = gPressedWidget.window_number; - WidgetIndex cursor_widgetIndex = gPressedWidget.widget_index; - - auto* windowMgr = GetContext()->GetUiContext()->GetWindowManager(); - WindowBase* cursor_w = windowMgr->FindByNumber(cursor_w_class, cursor_w_number); - if (cursor_w == nullptr) - { - _inputState = InputState::Reset; - return; - } - - if (w != nullptr && state == MouseState::LeftRelease) - { - if (w->widgets[widgetIndex].type == WindowWidgetType::CloseBox && cursor_w_class == w->classification - && cursor_w_number == w->number && widgetIndex == cursor_widgetIndex) + WindowBase* w = windowMgr->FindByNumber(gHoverWidget.window_classification, gHoverWidget.window_number); + if (w != nullptr) { - auto& im = GetInputManager(); - if (im.IsModifierKeyPressed(ModifierKey::shift)) + w->OnPrepareDraw(); + if (w->widgets[gHoverWidget.widget_index].type == WindowWidgetType::FlatBtn) { - gLastCloseModifier.window.number = w->number; - gLastCloseModifier.window.classification = w->classification; - gLastCloseModifier.modifier = CloseWindowModifier::Shift; - } - else if (im.IsModifierKeyPressed(ModifierKey::ctrl)) - { - gLastCloseModifier.window.number = w->number; - gLastCloseModifier.window.classification = w->classification; - gLastCloseModifier.modifier = CloseWindowModifier::Control; + WidgetInvalidateByNumber( + gHoverWidget.window_classification, gHoverWidget.window_number, gHoverWidget.widget_index); } } } - switch (state) + /** + * + * rct2: 0x006E95F9 + */ + static void InputWidgetLeft(const ScreenCoordsXY& screenCoords, WindowBase* w, WidgetIndex widgetIndex) { - case MouseState::Released: - if (w == nullptr || cursor_w_class != w->classification || cursor_w_number != w->number - || widgetIndex != cursor_widgetIndex) + WindowClass windowClass = WindowClass::Null; + rct_windownumber windowNumber = 0; + + if (w != nullptr) + { + windowClass = w->classification; + windowNumber = w->number; + } + + WindowCloseByClass(WindowClass::Error); + WindowCloseByClass(WindowClass::Tooltip); + + // Window might have changed position in the list, therefore find it again + auto* windowMgr = GetContext()->GetUiContext()->GetWindowManager(); + w = windowMgr->FindByNumber(windowClass, windowNumber); + if (w == nullptr) + return; + + w = WindowBringToFront(*w); + if (widgetIndex == kWidgetIndexNull) + return; + + if (windowClass != GetCurrentTextBox().window.classification || windowNumber != GetCurrentTextBox().window.number + || widgetIndex != GetCurrentTextBox().widget_index) + { + WindowCancelTextbox(); + } + + const auto& widget = w->widgets[widgetIndex]; + + switch (widget.type) + { + case WindowWidgetType::Frame: + case WindowWidgetType::Resize: + if (WindowCanResize(*w) + && (screenCoords.x >= w->windowPos.x + w->width - 19 && screenCoords.y >= w->windowPos.y + w->height - 19)) + InputWindowResizeBegin(*w, widgetIndex, screenCoords); break; - - if (WidgetIsDisabled(*w, widgetIndex)) - break; - - // If this variable is non-zero then its the last tick the mouse down event was fired. - if (_clickRepeatTicks.has_value()) - { - // The initial amount of time in ticks to wait until the first click repeat. - constexpr auto ticksUntilRepeats = 16U; - - // The amount of ticks between each click repeat. - constexpr auto eventDelayInTicks = 3U; - - // The amount of ticks since the last click repeat. - const auto clickRepeatsDelta = gCurrentRealTimeTicks - _clickRepeatTicks.value(); - - // Handle click repeat, only start this when at least 16 ticks elapsed. - if (clickRepeatsDelta >= ticksUntilRepeats && (clickRepeatsDelta & eventDelayInTicks) == 0) + case WindowWidgetType::Viewport: + _inputState = InputState::ViewportLeft; + gInputDragLast = screenCoords; + _dragWidget.window_classification = windowClass; + _dragWidget.window_number = windowNumber; + if (_inputFlags & INPUT_FLAG_TOOL_ACTIVE) { - if (WidgetIsHoldable(*w, widgetIndex)) + w = windowMgr->FindByNumber(gCurrentToolWidget.window_classification, gCurrentToolWidget.window_number); + if (w != nullptr) { - w->OnMouseDown(widgetIndex); + InputSetFlag(INPUT_FLAG_4, true); + w->OnToolDown(gCurrentToolWidget.widget_index, screenCoords); } + } + break; + case WindowWidgetType::Caption: + InputWindowPositionBegin(*w, widgetIndex, screenCoords); + break; + case WindowWidgetType::Scroll: + InputScrollBegin(*w, widgetIndex, screenCoords); + break; + case WindowWidgetType::Empty: + case WindowWidgetType::LabelCentred: + case WindowWidgetType::Label: + case WindowWidgetType::Groupbox: + case WindowWidgetType::ProgressBar: + case WindowWidgetType::Placeholder: + // Non-interactive widget type + break; + case WindowWidgetType::ImgBtn: + case WindowWidgetType::ColourBtn: + case WindowWidgetType::TrnBtn: + case WindowWidgetType::Tab: + case WindowWidgetType::FlatBtn: + case WindowWidgetType::Button: + case WindowWidgetType::TableHeader: + case WindowWidgetType::Spinner: + case WindowWidgetType::DropdownMenu: + case WindowWidgetType::CloseBox: + case WindowWidgetType::Checkbox: + case WindowWidgetType::TextBox: + case WindowWidgetType::Custom: + if (!WidgetIsDisabled(*w, widgetIndex)) + { + OpenRCT2::Audio::Play(OpenRCT2::Audio::SoundId::Click1, 0, w->windowPos.x + widget.midX()); - // Subtract initial delay from here on we want the event each third tick. - _clickRepeatTicks = gCurrentRealTimeTicks - ticksUntilRepeats; + // Set new cursor down widget + gPressedWidget.window_classification = windowClass; + gPressedWidget.window_number = windowNumber; + gPressedWidget.widget_index = widgetIndex; + _inputFlags |= INPUT_FLAG_WIDGET_PRESSED; + _inputState = InputState::WidgetPressed; + _clickRepeatTicks = gCurrentRealTimeTicks; + + WidgetInvalidateByNumber(windowClass, windowNumber, widgetIndex); + w->OnMouseDown(widgetIndex); + } + break; + } + } + + /** + * + * rct2: 0x006ED833 + */ + void ProcessMouseOver(const ScreenCoordsXY& screenCoords) + { + CursorID cursorId = CursorID::Arrow; + auto ft = Formatter(); + ft.Add(STR_NONE); + SetMapTooltip(ft); + + auto* windowMgr = GetContext()->GetUiContext()->GetWindowManager(); + WindowBase* window = windowMgr->FindFromPoint(screenCoords); + + if (window != nullptr) + { + WidgetIndex widgetId = windowMgr->FindWidgetFromPoint(*window, screenCoords); + if (widgetId != kWidgetIndexNull) + { + switch (window->widgets[widgetId].type) + { + case WindowWidgetType::Viewport: + if (!(_inputFlags & INPUT_FLAG_TOOL_ACTIVE)) + { + if (ViewportInteractionLeftOver(screenCoords)) + { + SetCursor(CursorID::HandPoint); + return; + } + break; + } + cursorId = static_cast(gCurrentToolId); + break; + + case WindowWidgetType::Frame: + case WindowWidgetType::Resize: + if (!(window->flags & WF_RESIZABLE)) + break; + + if (window->min_width == window->max_width && window->min_height == window->max_height) + break; + + if (screenCoords.x < window->windowPos.x + window->width - 0x13) + break; + + if (screenCoords.y < window->windowPos.y + window->height - 0x13) + break; + + cursorId = CursorID::DiagonalArrows; + break; + + case WindowWidgetType::Scroll: + { + int32_t output_scroll_area, scroll_id; + ScreenCoordsXY scrollCoords; + WidgetScrollGetPart( + *window, &window->widgets[widgetId], screenCoords, scrollCoords, &output_scroll_area, &scroll_id); + if (output_scroll_area != SCROLL_PART_VIEW) + { + cursorId = CursorID::Arrow; + break; + } + // Same as default but with scroll_x/y + cursorId = window->OnCursor(widgetId, scrollCoords, CursorID::Arrow); + if (cursorId == CursorID::Undefined) + cursorId = CursorID::Arrow; + break; + } + default: + cursorId = window->OnCursor(widgetId, screenCoords, CursorID::Arrow); + if (cursorId == CursorID::Undefined) + cursorId = CursorID::Arrow; + break; } } + } - if (_inputFlags & INPUT_FLAG_WIDGET_PRESSED) + ViewportInteractionRightOver(screenCoords); + SetCursor(cursorId); + } + + /** + * + * rct2: 0x006ED801 + */ + void ProcessMouseTool(const ScreenCoordsXY& screenCoords) + { + if (_inputFlags & INPUT_FLAG_TOOL_ACTIVE) + { + auto* windowMgr = GetContext()->GetUiContext()->GetWindowManager(); + WindowBase* w = windowMgr->FindByNumber(gCurrentToolWidget.window_classification, gCurrentToolWidget.window_number); + + if (w == nullptr) + ToolCancel(); + else if (InputGetState() != InputState::ViewportRight) + w->OnToolUpdate(gCurrentToolWidget.widget_index, screenCoords); + } + } + + const std::map kColourToTip = { + { COLOUR_BLACK, STR_COLOUR_BLACK_TIP }, + { COLOUR_GREY, STR_COLOUR_GREY_TIP }, + { COLOUR_WHITE, STR_COLOUR_WHITE_TIP }, + { COLOUR_DARK_PURPLE, STR_COLOUR_DARK_PURPLE_TIP }, + { COLOUR_LIGHT_PURPLE, STR_COLOUR_LIGHT_PURPLE_TIP }, + { COLOUR_BRIGHT_PURPLE, STR_COLOUR_BRIGHT_PURPLE_TIP }, + { COLOUR_DARK_BLUE, STR_COLOUR_DARK_BLUE_TIP }, + { COLOUR_LIGHT_BLUE, STR_COLOUR_LIGHT_BLUE_TIP }, + { COLOUR_ICY_BLUE, STR_COLOUR_ICY_BLUE_TIP }, + { COLOUR_TEAL, STR_COLOUR_TEAL_TIP }, + { COLOUR_AQUAMARINE, STR_COLOUR_AQUAMARINE_TIP }, + { COLOUR_SATURATED_GREEN, STR_COLOUR_SATURATED_GREEN_TIP }, + { COLOUR_DARK_GREEN, STR_COLOUR_DARK_GREEN_TIP }, + { COLOUR_MOSS_GREEN, STR_COLOUR_MOSS_GREEN_TIP }, + { COLOUR_BRIGHT_GREEN, STR_COLOUR_BRIGHT_GREEN_TIP }, + { COLOUR_OLIVE_GREEN, STR_COLOUR_OLIVE_GREEN_TIP }, + { COLOUR_DARK_OLIVE_GREEN, STR_COLOUR_DARK_OLIVE_GREEN_TIP }, + { COLOUR_BRIGHT_YELLOW, STR_COLOUR_BRIGHT_YELLOW_TIP }, + { COLOUR_YELLOW, STR_COLOUR_YELLOW_TIP }, + { COLOUR_DARK_YELLOW, STR_COLOUR_DARK_YELLOW_TIP }, + { COLOUR_LIGHT_ORANGE, STR_COLOUR_LIGHT_ORANGE_TIP }, + { COLOUR_DARK_ORANGE, STR_COLOUR_DARK_ORANGE_TIP }, + { COLOUR_LIGHT_BROWN, STR_COLOUR_LIGHT_BROWN_TIP }, + { COLOUR_SATURATED_BROWN, STR_COLOUR_SATURATED_BROWN_TIP }, + { COLOUR_DARK_BROWN, STR_COLOUR_DARK_BROWN_TIP }, + { COLOUR_SALMON_PINK, STR_COLOUR_SALMON_PINK_TIP }, + { COLOUR_BORDEAUX_RED, STR_COLOUR_BORDEAUX_RED_TIP }, + { COLOUR_SATURATED_RED, STR_COLOUR_SATURATED_RED_TIP }, + { COLOUR_BRIGHT_RED, STR_COLOUR_BRIGHT_RED_TIP }, + { COLOUR_DARK_PINK, STR_COLOUR_DARK_PINK_TIP }, + { COLOUR_BRIGHT_PINK, STR_COLOUR_BRIGHT_PINK_TIP }, + { COLOUR_LIGHT_PINK, STR_COLOUR_LIGHT_PINK_TIP }, + { COLOUR_DARK_OLIVE_DARK, STR_COLOUR_DARK_OLIVE_DARK_TIP }, + { COLOUR_DARK_OLIVE_LIGHT, STR_COLOUR_DARK_OLIVE_LIGHT_TIP }, + { COLOUR_SATURATED_BROWN_LIGHT, STR_COLOUR_SATURATED_BROWN_LIGHT_TIP }, + { COLOUR_BORDEAUX_RED_DARK, STR_COLOUR_BORDEAUX_RED_DARK_TIP }, + { COLOUR_BORDEAUX_RED_LIGHT, STR_COLOUR_BORDEAUX_RED_LIGHT_TIP }, + { COLOUR_GRASS_GREEN_DARK, STR_COLOUR_GRASS_GREEN_DARK_TIP }, + { COLOUR_GRASS_GREEN_LIGHT, STR_COLOUR_GRASS_GREEN_LIGHT_TIP }, + { COLOUR_OLIVE_DARK, STR_COLOUR_OLIVE_DARK_TIP }, + { COLOUR_OLIVE_LIGHT, STR_COLOUR_OLIVE_LIGHT_TIP }, + { COLOUR_SATURATED_GREEN_LIGHT, STR_COLOUR_SATURATED_GREEN_LIGHT_TIP }, + { COLOUR_TAN_DARK, STR_COLOUR_TAN_DARK_TIP }, + { COLOUR_TAN_LIGHT, STR_COLOUR_TAN_LIGHT_TIP }, + { COLOUR_DULL_PURPLE_LIGHT, STR_COLOUR_DULL_PURPLE_LIGHT_TIP }, + { COLOUR_DULL_GREEN_DARK, STR_COLOUR_DULL_GREEN_DARK_TIP }, + { COLOUR_DULL_GREEN_LIGHT, STR_COLOUR_DULL_GREEN_LIGHT_TIP }, + { COLOUR_SATURATED_PURPLE_DARK, STR_COLOUR_SATURATED_PURPLE_DARK_TIP }, + { COLOUR_SATURATED_PURPLE_LIGHT, STR_COLOUR_SATURATED_PURPLE_LIGHT_TIP }, + { COLOUR_ORANGE_LIGHT, STR_COLOUR_ORANGE_LIGHT_TIP }, + { COLOUR_AQUA_DARK, STR_COLOUR_AQUA_DARK_TIP }, + { COLOUR_MAGENTA_LIGHT, STR_COLOUR_MAGENTA_LIGHT_TIP }, + { COLOUR_DULL_BROWN_DARK, STR_COLOUR_DULL_BROWN_DARK_TIP }, + { COLOUR_DULL_BROWN_LIGHT, STR_COLOUR_DULL_BROWN_LIGHT_TIP }, + { COLOUR_INVISIBLE, STR_COLOUR_INVISIBLE_TIP }, + { COLOUR_VOID, STR_COLOUR_VOID_TIP }, + }; + + /** + * + * rct2: 0x006E8DA7 + */ + void InputStateWidgetPressed( + const ScreenCoordsXY& screenCoords, MouseState state, WidgetIndex widgetIndex, WindowBase* w, Widget* widget) + { + WindowClass cursor_w_class; + rct_windownumber cursor_w_number; + cursor_w_class = gPressedWidget.window_classification; + cursor_w_number = gPressedWidget.window_number; + WidgetIndex cursor_widgetIndex = gPressedWidget.widget_index; + + auto* windowMgr = GetContext()->GetUiContext()->GetWindowManager(); + WindowBase* cursor_w = windowMgr->FindByNumber(cursor_w_class, cursor_w_number); + if (cursor_w == nullptr) + { + _inputState = InputState::Reset; + return; + } + + if (w != nullptr && state == MouseState::LeftRelease) + { + if (w->widgets[widgetIndex].type == WindowWidgetType::CloseBox && cursor_w_class == w->classification + && cursor_w_number == w->number && widgetIndex == cursor_widgetIndex) { + auto& im = GetInputManager(); + if (im.IsModifierKeyPressed(ModifierKey::shift)) + { + gLastCloseModifier.window.number = w->number; + gLastCloseModifier.window.classification = w->classification; + gLastCloseModifier.modifier = CloseWindowModifier::Shift; + } + else if (im.IsModifierKeyPressed(ModifierKey::ctrl)) + { + gLastCloseModifier.window.number = w->number; + gLastCloseModifier.window.classification = w->classification; + gLastCloseModifier.modifier = CloseWindowModifier::Control; + } + } + } + + switch (state) + { + case MouseState::Released: + if (w == nullptr || cursor_w_class != w->classification || cursor_w_number != w->number + || widgetIndex != cursor_widgetIndex) + break; + + if (WidgetIsDisabled(*w, widgetIndex)) + break; + + // If this variable is non-zero then its the last tick the mouse down event was fired. + if (_clickRepeatTicks.has_value()) + { + // The initial amount of time in ticks to wait until the first click repeat. + constexpr auto ticksUntilRepeats = 16U; + + // The amount of ticks between each click repeat. + constexpr auto eventDelayInTicks = 3U; + + // The amount of ticks since the last click repeat. + const auto clickRepeatsDelta = gCurrentRealTimeTicks - _clickRepeatTicks.value(); + + // Handle click repeat, only start this when at least 16 ticks elapsed. + if (clickRepeatsDelta >= ticksUntilRepeats && (clickRepeatsDelta & eventDelayInTicks) == 0) + { + if (WidgetIsHoldable(*w, widgetIndex)) + { + w->OnMouseDown(widgetIndex); + } + + // Subtract initial delay from here on we want the event each third tick. + _clickRepeatTicks = gCurrentRealTimeTicks - ticksUntilRepeats; + } + } + + if (_inputFlags & INPUT_FLAG_WIDGET_PRESSED) + { + if (_inputState == InputState::DropdownActive) + { + gDropdownHighlightedIndex = gDropdownDefaultIndex; + WindowInvalidateByClass(WindowClass::Dropdown); + } + return; + } + + _inputFlags |= INPUT_FLAG_WIDGET_PRESSED; + WidgetInvalidateByNumber(cursor_w_class, cursor_w_number, widgetIndex); + return; + case MouseState::LeftRelease: + case MouseState::RightPress: if (_inputState == InputState::DropdownActive) { - gDropdownHighlightedIndex = gDropdownDefaultIndex; - WindowInvalidateByClass(WindowClass::Dropdown); - } - return; - } - - _inputFlags |= INPUT_FLAG_WIDGET_PRESSED; - WidgetInvalidateByNumber(cursor_w_class, cursor_w_number, widgetIndex); - return; - case MouseState::LeftRelease: - case MouseState::RightPress: - if (_inputState == InputState::DropdownActive) - { - if (w != nullptr) - { - auto wClass = w->classification; - auto wNumber = w->number; - int32_t dropdown_index = 0; - bool dropdownCleanup = false; - - if (w->classification == WindowClass::Dropdown) + if (w != nullptr) { - dropdown_index = DropdownIndexFromPoint(screenCoords, w); - dropdownCleanup = dropdown_index == -1 - || (dropdown_index < Dropdown::ItemsMaxSize && Dropdown::IsDisabled(dropdown_index)) - || gDropdownItems[dropdown_index].IsSeparator(); - w = nullptr; // To be closed right next - } - else - { - if (cursor_w_class != w->classification || cursor_w_number != w->number - || widgetIndex != cursor_widgetIndex) + auto wClass = w->classification; + auto wNumber = w->number; + int32_t dropdown_index = 0; + bool dropdownCleanup = false; + + if (w->classification == WindowClass::Dropdown) { - dropdownCleanup = true; + dropdown_index = DropdownIndexFromPoint(screenCoords, w); + dropdownCleanup = dropdown_index == -1 + || (dropdown_index < Dropdown::ItemsMaxSize && Dropdown::IsDisabled(dropdown_index)) + || gDropdownItems[dropdown_index].IsSeparator(); + w = nullptr; // To be closed right next } else { - dropdown_index = -1; - if (_inputFlags & INPUT_FLAG_DROPDOWN_STAY_OPEN) + if (cursor_w_class != w->classification || cursor_w_number != w->number + || widgetIndex != cursor_widgetIndex) { - if (!(_inputFlags & INPUT_FLAG_DROPDOWN_MOUSE_UP)) + dropdownCleanup = true; + } + else + { + dropdown_index = -1; + if (_inputFlags & INPUT_FLAG_DROPDOWN_STAY_OPEN) { - _inputFlags |= INPUT_FLAG_DROPDOWN_MOUSE_UP; - return; + if (!(_inputFlags & INPUT_FLAG_DROPDOWN_MOUSE_UP)) + { + _inputFlags |= INPUT_FLAG_DROPDOWN_MOUSE_UP; + return; + } } } } - } - WindowCloseByClass(WindowClass::Dropdown); + WindowCloseByClass(WindowClass::Dropdown); - if (dropdownCleanup) - { - // Update w as it will be invalid after closing the dropdown window - w = windowMgr->FindByNumber(wClass, wNumber); - } - else - { - cursor_w = windowMgr->FindByNumber(cursor_w_class, cursor_w_number); - if (_inputFlags & INPUT_FLAG_WIDGET_PRESSED) + if (dropdownCleanup) { - _inputFlags &= ~INPUT_FLAG_WIDGET_PRESSED; - WidgetInvalidateByNumber(cursor_w_class, cursor_w_number, cursor_widgetIndex); + // Update w as it will be invalid after closing the dropdown window + w = windowMgr->FindByNumber(wClass, wNumber); } - - _inputState = InputState::Normal; - gTooltipCloseTimeout = 0; - gTooltipWidget.widget_index = cursor_widgetIndex; - gTooltipWidget.window_classification = cursor_w_class; - gTooltipWidget.window_number = cursor_w_number; - - if (dropdown_index == -1) + else { - if (!Dropdown::IsDisabled(gDropdownDefaultIndex)) + cursor_w = windowMgr->FindByNumber(cursor_w_class, cursor_w_number); + if (_inputFlags & INPUT_FLAG_WIDGET_PRESSED) { - dropdown_index = gDropdownDefaultIndex; + _inputFlags &= ~INPUT_FLAG_WIDGET_PRESSED; + WidgetInvalidateByNumber(cursor_w_class, cursor_w_number, cursor_widgetIndex); } + + _inputState = InputState::Normal; + gTooltipCloseTimeout = 0; + gTooltipWidget.widget_index = cursor_widgetIndex; + gTooltipWidget.window_classification = cursor_w_class; + gTooltipWidget.window_number = cursor_w_number; + + if (dropdown_index == -1) + { + if (!Dropdown::IsDisabled(gDropdownDefaultIndex)) + { + dropdown_index = gDropdownDefaultIndex; + } + } + cursor_w->OnDropdown(cursor_widgetIndex, dropdown_index); } - cursor_w->OnDropdown(cursor_widgetIndex, dropdown_index); } } + + _inputState = InputState::Normal; + + if (state == MouseState::RightPress) + { + return; + } + + gTooltipCloseTimeout = 0; + gTooltipWidget.widget_index = cursor_widgetIndex; + + if (w == nullptr) + break; + + if (widget == nullptr) + break; + + { + int32_t mid_point_x = widget->midX() + w->windowPos.x; + OpenRCT2::Audio::Play(OpenRCT2::Audio::SoundId::Click2, 0, mid_point_x); + } + if (cursor_w_class != w->classification || cursor_w_number != w->number || widgetIndex != cursor_widgetIndex) + break; + + if (WidgetIsDisabled(*w, widgetIndex)) + break; + + WidgetInvalidateByNumber(cursor_w_class, cursor_w_number, widgetIndex); + w->OnMouseUp(widgetIndex); + return; + + default: + return; + } + + _clickRepeatTicks = std::nullopt; + if (_inputState != InputState::DropdownActive) + { + // Hold down widget and drag outside of area?? + if (_inputFlags & INPUT_FLAG_WIDGET_PRESSED) + { + _inputFlags &= ~INPUT_FLAG_WIDGET_PRESSED; + WidgetInvalidateByNumber(cursor_w_class, cursor_w_number, cursor_widgetIndex); } + return; + } + else if (gDropdownIsColour) + { + // This is ordinarily covered in InputWidgetOver but the dropdown with colours is a special case. + InputUpdateTooltip(w, widgetIndex, screenCoords); + } - _inputState = InputState::Normal; + gDropdownHighlightedIndex = -1; + WindowInvalidateByClass(WindowClass::Dropdown); + if (w == nullptr) + { + return; + } - if (state == MouseState::RightPress) + if (w->classification == WindowClass::Dropdown) + { + int32_t dropdown_index = DropdownIndexFromPoint(screenCoords, w); + if (dropdown_index == -1) { return; } - gTooltipCloseTimeout = 0; - gTooltipWidget.widget_index = cursor_widgetIndex; - - if (w == nullptr) - break; - - if (widget == nullptr) - break; - + if (gDropdownIsColour && gDropdownLastColourHover != dropdown_index) { - int32_t mid_point_x = widget->midX() + w->windowPos.x; - OpenRCT2::Audio::Play(OpenRCT2::Audio::SoundId::Click2, 0, mid_point_x); + gDropdownLastColourHover = dropdown_index; + WindowTooltipClose(); + + WindowTooltipShow( + OpenRCT2String{ kColourToTip.at(ColourDropDownIndexToColour(dropdown_index)), {} }, screenCoords); } - if (cursor_w_class != w->classification || cursor_w_number != w->number || widgetIndex != cursor_widgetIndex) - break; - if (WidgetIsDisabled(*w, widgetIndex)) - break; - - WidgetInvalidateByNumber(cursor_w_class, cursor_w_number, widgetIndex); - w->OnMouseUp(widgetIndex); - return; - - default: - return; - } - - _clickRepeatTicks = std::nullopt; - if (_inputState != InputState::DropdownActive) - { - // Hold down widget and drag outside of area?? - if (_inputFlags & INPUT_FLAG_WIDGET_PRESSED) - { - _inputFlags &= ~INPUT_FLAG_WIDGET_PRESSED; - WidgetInvalidateByNumber(cursor_w_class, cursor_w_number, cursor_widgetIndex); - } - return; - } - else if (gDropdownIsColour) - { - // This is ordinarily covered in InputWidgetOver but the dropdown with colours is a special case. - InputUpdateTooltip(w, widgetIndex, screenCoords); - } - - gDropdownHighlightedIndex = -1; - WindowInvalidateByClass(WindowClass::Dropdown); - if (w == nullptr) - { - return; - } - - if (w->classification == WindowClass::Dropdown) - { - int32_t dropdown_index = DropdownIndexFromPoint(screenCoords, w); - if (dropdown_index == -1) - { - return; - } - - if (gDropdownIsColour && gDropdownLastColourHover != dropdown_index) - { - gDropdownLastColourHover = dropdown_index; - WindowTooltipClose(); - - WindowTooltipShow(OpenRCT2String{ kColourToTip.at(ColourDropDownIndexToColour(dropdown_index)), {} }, screenCoords); - } - - if (dropdown_index < Dropdown::ItemsMaxSize && Dropdown::IsDisabled(dropdown_index)) - { - return; - } - - if (gDropdownItems[dropdown_index].IsSeparator()) - { - return; - } - - gDropdownHighlightedIndex = dropdown_index; - WindowInvalidateByClass(WindowClass::Dropdown); - } - else - { - gDropdownLastColourHover = -1; - WindowTooltipClose(); - } -} - -static void InputUpdateTooltip(WindowBase* w, WidgetIndex widgetIndex, const ScreenCoordsXY& screenCoords) -{ - if (gTooltipWidget.window_classification == WindowClass::Null) - { - if (gTooltipCursor == screenCoords) - { - if (gCurrentRealTimeTicks >= _tooltipNotShownTimeout && w != nullptr && WidgetIsVisible(*w, widgetIndex)) + if (dropdown_index < Dropdown::ItemsMaxSize && Dropdown::IsDisabled(dropdown_index)) { - gTooltipCloseTimeout = gCurrentRealTimeTicks + 8000; - WindowTooltipOpen(w, widgetIndex, screenCoords); + return; } + + if (gDropdownItems[dropdown_index].IsSeparator()) + { + return; + } + + gDropdownHighlightedIndex = dropdown_index; + WindowInvalidateByClass(WindowClass::Dropdown); } else { - ResetTooltipNotShown(); - } - - gTooltipCloseTimeout = gCurrentRealTimeTicks + 8000; - gTooltipCursor = screenCoords; - } - else - { - gTooltipCursor = screenCoords; - ResetTooltipNotShown(); - - if (w == nullptr || gTooltipWidget.window_classification != w->classification - || gTooltipWidget.window_number != w->number || gTooltipWidget.widget_index != widgetIndex - || !WidgetIsVisible(*w, widgetIndex)) - { + gDropdownLastColourHover = -1; WindowTooltipClose(); } + } - if (gCurrentRealTimeTicks >= gTooltipCloseTimeout) + static void InputUpdateTooltip(WindowBase* w, WidgetIndex widgetIndex, const ScreenCoordsXY& screenCoords) + { + if (gTooltipWidget.window_classification == WindowClass::Null) { - WindowCloseByClass(WindowClass::Tooltip); + if (gTooltipCursor == screenCoords) + { + if (gCurrentRealTimeTicks >= _tooltipNotShownTimeout && w != nullptr && WidgetIsVisible(*w, widgetIndex)) + { + gTooltipCloseTimeout = gCurrentRealTimeTicks + 8000; + WindowTooltipOpen(w, widgetIndex, screenCoords); + } + } + else + { + ResetTooltipNotShown(); + } + + gTooltipCloseTimeout = gCurrentRealTimeTicks + 8000; + gTooltipCursor = screenCoords; + } + else + { + gTooltipCursor = screenCoords; + ResetTooltipNotShown(); + + if (w == nullptr || gTooltipWidget.window_classification != w->classification + || gTooltipWidget.window_number != w->number || gTooltipWidget.widget_index != widgetIndex + || !WidgetIsVisible(*w, widgetIndex)) + { + WindowTooltipClose(); + } + + if (gCurrentRealTimeTicks >= gTooltipCloseTimeout) + { + WindowCloseByClass(WindowClass::Tooltip); + } } } -} #pragma endregion -/** - * - * rct2: 0x006ED990 - */ -void SetCursor(CursorID cursor_id) -{ - assert(cursor_id != CursorID::Undefined); - if (_inputState == InputState::Resizing) + /** + * + * rct2: 0x006ED990 + */ + void SetCursor(CursorID cursor_id) { - cursor_id = CursorID::DiagonalArrows; + assert(cursor_id != CursorID::Undefined); + if (_inputState == InputState::Resizing) + { + cursor_id = CursorID::DiagonalArrows; + } + ContextSetCurrentCursor(cursor_id); } - ContextSetCurrentCursor(cursor_id); -} -/** - * - * rct2: 0x006E876D - */ -void InvalidateScroll() -{ - auto* windowMgr = GetContext()->GetUiContext()->GetWindowManager(); - WindowBase* w = windowMgr->FindByNumber(gPressedWidget.window_classification, gPressedWidget.window_number); - if (w != nullptr) + /** + * + * rct2: 0x006E876D + */ + void InvalidateScroll() { - // Reset to basic scroll - w->scrolls[_currentScrollIndex].flags &= 0xFF11; - WindowInvalidateByNumber(gPressedWidget.window_classification, gPressedWidget.window_number); + auto* windowMgr = GetContext()->GetUiContext()->GetWindowManager(); + WindowBase* w = windowMgr->FindByNumber(gPressedWidget.window_classification, gPressedWidget.window_number); + if (w != nullptr) + { + // Reset to basic scroll + w->scrolls[_currentScrollIndex].flags &= 0xFF11; + WindowInvalidateByNumber(gPressedWidget.window_classification, gPressedWidget.window_number); + } } -} -/** - * rct2: 0x00406C96 - */ -void StoreMouseInput(MouseState state, const ScreenCoordsXY& screenCoords) -{ - uint32_t writeIndex = _mouseInputQueueWriteIndex; - uint32_t nextWriteIndex = (writeIndex + 1) % std::size(_mouseInputQueue); - - // Check if the queue is full - if (nextWriteIndex != _mouseInputQueueReadIndex) + /** + * rct2: 0x00406C96 + */ + void StoreMouseInput(MouseState state, const ScreenCoordsXY& screenCoords) { - RCTMouseData* item = &_mouseInputQueue[writeIndex]; - item->x = screenCoords.x; - item->y = screenCoords.y; - item->state = state; + uint32_t writeIndex = _mouseInputQueueWriteIndex; + uint32_t nextWriteIndex = (writeIndex + 1) % std::size(_mouseInputQueue); - _mouseInputQueueWriteIndex = nextWriteIndex; + // Check if the queue is full + if (nextWriteIndex != _mouseInputQueueReadIndex) + { + RCTMouseData* item = &_mouseInputQueue[writeIndex]; + item->x = screenCoords.x; + item->y = screenCoords.y; + item->state = state; + + _mouseInputQueueWriteIndex = nextWriteIndex; + } } -} -void GameHandleEdgeScroll() -{ - WindowBase* mainWindow; - int32_t scrollX, scrollY; - - mainWindow = WindowGetMain(); - if (mainWindow == nullptr) - return; - if ((mainWindow->flags & WF_NO_SCROLLING) || (gScreenFlags & (SCREEN_FLAGS_TRACK_MANAGER | SCREEN_FLAGS_TITLE_DEMO))) - return; - if (mainWindow->viewport == nullptr) - return; - if (!ContextHasFocus()) - return; - - scrollX = 0; - scrollY = 0; - - // Scroll left / right - const CursorState* cursorState = ContextGetCursorState(); - if (cursorState->position.x == 0) - scrollX = -1; - else if (cursorState->position.x >= ContextGetWidth() - 1) - scrollX = 1; - - // Scroll up / down - if (cursorState->position.y == 0) - scrollY = -1; - else if (cursorState->position.y >= ContextGetHeight() - 1) - scrollY = 1; - - InputScrollViewport(ScreenCoordsXY(scrollX, scrollY)); -} - -void InputScrollViewport(const ScreenCoordsXY& scrollScreenCoords) -{ - WindowBase* mainWindow = WindowGetMain(); - if (mainWindow == nullptr) - return; - - Viewport* viewport = mainWindow->viewport; - if (viewport == nullptr) - return; - - const int32_t speed = Config::Get().general.EdgeScrollingSpeed; - - int32_t multiplier = viewport->zoom.ApplyTo(speed); - int32_t dx = scrollScreenCoords.x * multiplier; - int32_t dy = scrollScreenCoords.y * multiplier; - - if (scrollScreenCoords.x != 0) + void GameHandleEdgeScroll() { - // Speed up scrolling horizontally when at the edge of the map - // so that the speed is consistent with vertical edge scrolling. - int32_t x = mainWindow->savedViewPos.x + viewport->ViewWidth() / 2 + dx; - int32_t y = mainWindow->savedViewPos.y + viewport->ViewHeight() / 2; - int32_t y_dy = mainWindow->savedViewPos.y + viewport->ViewHeight() / 2 + dy; + WindowBase* mainWindow; + int32_t scrollX, scrollY; - auto mapCoord = ViewportPosToMapPos({ x, y }, 0, viewport->rotation); - auto mapCoord_dy = ViewportPosToMapPos({ x, y_dy }, 0, viewport->rotation); + mainWindow = WindowGetMain(); + if (mainWindow == nullptr) + return; + if ((mainWindow->flags & WF_NO_SCROLLING) || (gScreenFlags & (SCREEN_FLAGS_TRACK_MANAGER | SCREEN_FLAGS_TITLE_DEMO))) + return; + if (mainWindow->viewport == nullptr) + return; + if (!ContextHasFocus()) + return; - // Check if we're crossing the boundary - // Clamp to the map minimum value - int32_t at_map_edge = 0; - int32_t at_map_edge_dy = 0; - if (mapCoord.x < kMapMinimumXY || mapCoord.y < kMapMinimumXY) - { - at_map_edge = 1; - } - if (mapCoord_dy.x < kMapMinimumXY || mapCoord_dy.y < kMapMinimumXY) - { - at_map_edge_dy = 1; - } + scrollX = 0; + scrollY = 0; - // Clamp to the map maximum value (scenario specific) - auto mapSizeMinus2 = GetMapSizeMinus2(); - if (mapCoord.x > mapSizeMinus2.x || mapCoord.y > mapSizeMinus2.y) - { - at_map_edge = 1; - } - if (mapCoord_dy.x > mapSizeMinus2.x || mapCoord_dy.y > mapSizeMinus2.y) - { - at_map_edge_dy = 1; - } + // Scroll left / right + const CursorState* cursorState = ContextGetCursorState(); + if (cursorState->position.x == 0) + scrollX = -1; + else if (cursorState->position.x >= ContextGetWidth() - 1) + scrollX = 1; - // If we crossed the boundary, multiply the distance by 2 - if (at_map_edge && at_map_edge_dy) - { - dx *= 2; - } + // Scroll up / down + if (cursorState->position.y == 0) + scrollY = -1; + else if (cursorState->position.y >= ContextGetHeight() - 1) + scrollY = 1; - mainWindow->savedViewPos.x += dx; - _inputFlags |= INPUT_FLAG_VIEWPORT_SCROLLING; + InputScrollViewport(ScreenCoordsXY(scrollX, scrollY)); } - if (scrollScreenCoords.y != 0) + + void InputScrollViewport(const ScreenCoordsXY& scrollScreenCoords) { - mainWindow->savedViewPos.y += dy; - _inputFlags |= INPUT_FLAG_VIEWPORT_SCROLLING; + WindowBase* mainWindow = WindowGetMain(); + if (mainWindow == nullptr) + return; + + Viewport* viewport = mainWindow->viewport; + if (viewport == nullptr) + return; + + const int32_t speed = Config::Get().general.EdgeScrollingSpeed; + + int32_t multiplier = viewport->zoom.ApplyTo(speed); + int32_t dx = scrollScreenCoords.x * multiplier; + int32_t dy = scrollScreenCoords.y * multiplier; + + if (scrollScreenCoords.x != 0) + { + // Speed up scrolling horizontally when at the edge of the map + // so that the speed is consistent with vertical edge scrolling. + int32_t x = mainWindow->savedViewPos.x + viewport->ViewWidth() / 2 + dx; + int32_t y = mainWindow->savedViewPos.y + viewport->ViewHeight() / 2; + int32_t y_dy = mainWindow->savedViewPos.y + viewport->ViewHeight() / 2 + dy; + + auto mapCoord = ViewportPosToMapPos({ x, y }, 0, viewport->rotation); + auto mapCoord_dy = ViewportPosToMapPos({ x, y_dy }, 0, viewport->rotation); + + // Check if we're crossing the boundary + // Clamp to the map minimum value + int32_t at_map_edge = 0; + int32_t at_map_edge_dy = 0; + if (mapCoord.x < kMapMinimumXY || mapCoord.y < kMapMinimumXY) + { + at_map_edge = 1; + } + if (mapCoord_dy.x < kMapMinimumXY || mapCoord_dy.y < kMapMinimumXY) + { + at_map_edge_dy = 1; + } + + // Clamp to the map maximum value (scenario specific) + auto mapSizeMinus2 = GetMapSizeMinus2(); + if (mapCoord.x > mapSizeMinus2.x || mapCoord.y > mapSizeMinus2.y) + { + at_map_edge = 1; + } + if (mapCoord_dy.x > mapSizeMinus2.x || mapCoord_dy.y > mapSizeMinus2.y) + { + at_map_edge_dy = 1; + } + + // If we crossed the boundary, multiply the distance by 2 + if (at_map_edge && at_map_edge_dy) + { + dx *= 2; + } + + mainWindow->savedViewPos.x += dx; + _inputFlags |= INPUT_FLAG_VIEWPORT_SCROLLING; + } + if (scrollScreenCoords.y != 0) + { + mainWindow->savedViewPos.y += dy; + _inputFlags |= INPUT_FLAG_VIEWPORT_SCROLLING; + } } -} +} // namespace OpenRCT2 diff --git a/src/openrct2-ui/input/MouseInput.h b/src/openrct2-ui/input/MouseInput.h index 14bfda2f66..4fe8a5e99b 100644 --- a/src/openrct2-ui/input/MouseInput.h +++ b/src/openrct2-ui/input/MouseInput.h @@ -11,21 +11,24 @@ #include -enum class MouseState : uint32_t +namespace OpenRCT2 { - Released, - LeftPress, - LeftRelease, - RightPress, - RightRelease -}; + enum class MouseState : uint32_t + { + Released, + LeftPress, + LeftRelease, + RightPress, + RightRelease + }; -extern ScreenCoordsXY gInputDragLast; + extern ScreenCoordsXY gInputDragLast; -void InputWindowPositionBegin(WindowBase& w, WidgetIndex widgetIndex, const ScreenCoordsXY& screenCoords); -void GameHandleInput(); -void GameHandleEdgeScroll(); + void InputWindowPositionBegin(WindowBase& w, WidgetIndex widgetIndex, const ScreenCoordsXY& screenCoords); + void GameHandleInput(); + void GameHandleEdgeScroll(); -void StoreMouseInput(MouseState state, const ScreenCoordsXY& screenCoords); + void StoreMouseInput(MouseState state, const ScreenCoordsXY& screenCoords); -void InputScrollViewport(const ScreenCoordsXY& screenCoords); + void InputScrollViewport(const ScreenCoordsXY& screenCoords); +} // namespace OpenRCT2 diff --git a/src/openrct2-ui/interface/LandTool.h b/src/openrct2-ui/interface/LandTool.h index 936667197b..e7cac0c701 100644 --- a/src/openrct2-ui/interface/LandTool.h +++ b/src/openrct2-ui/interface/LandTool.h @@ -10,6 +10,7 @@ #pragma once #include +#include constexpr uint16_t kLandToolMinimumSize = 1; constexpr uint16_t kLandToolMaximumSize = 64; diff --git a/src/openrct2-ui/interface/Window.cpp b/src/openrct2-ui/interface/Window.cpp index 9f6d30e8f4..c22f6ecb4e 100644 --- a/src/openrct2-ui/interface/Window.cpp +++ b/src/openrct2-ui/interface/Window.cpp @@ -32,695 +32,699 @@ #include #include -using namespace OpenRCT2; -using namespace OpenRCT2::Ui; - -// The amount of pixels to scroll per wheel click -constexpr int32_t WindowScrollPixels = 17; - -static int32_t _previousAbsoluteWheel = 0; - -static bool WindowFitsBetweenOthers(const ScreenCoordsXY& loc, int32_t width, int32_t height) +namespace OpenRCT2 { - for (auto& w : g_window_list) + using namespace OpenRCT2::Ui; + + // The amount of pixels to scroll per wheel click + constexpr int32_t WindowScrollPixels = 17; + + static int32_t _previousAbsoluteWheel = 0; + + static bool WindowFitsBetweenOthers(const ScreenCoordsXY& loc, int32_t width, int32_t height) { - if (w->flags & WF_DEAD) - continue; - if (w->flags & WF_STICK_TO_BACK) - continue; - - if (loc.x + width <= w->windowPos.x) - continue; - if (loc.x >= w->windowPos.x + w->width) - continue; - if (loc.y + height <= w->windowPos.y) - continue; - if (loc.y >= w->windowPos.y + w->height) - continue; - return false; - } - - return true; -} - -static bool WindowFitsWithinSpace(const ScreenCoordsXY& loc, int32_t width, int32_t height) -{ - if (loc.x < 0) - return false; - if (loc.y <= kTopToolbarHeight && !(gScreenFlags & SCREEN_FLAGS_TITLE_DEMO)) - return false; - if (loc.x + width > ContextGetWidth()) - return false; - if (loc.y + height > ContextGetHeight()) - return false; - return WindowFitsBetweenOthers(loc, width, height); -} - -static bool WindowFitsOnScreen(const ScreenCoordsXY& loc, int32_t width, int32_t height) -{ - uint16_t screenWidth = ContextGetWidth(); - uint16_t screenHeight = ContextGetHeight(); - int32_t unk; - - unk = -(width / 4); - if (loc.x < unk) - return false; - unk = screenWidth + (unk * 2); - if (loc.x > unk) - return false; - if (loc.y <= kTopToolbarHeight && !(gScreenFlags & SCREEN_FLAGS_TITLE_DEMO)) - return false; - unk = screenHeight - (height / 4); - if (loc.y > unk) - return false; - return WindowFitsBetweenOthers(loc, width, height); -} - -static ScreenCoordsXY ClampWindowToScreen( - const ScreenCoordsXY& pos, const int32_t screenWidth, const int32_t screenHeight, const int32_t width, const int32_t height) -{ - auto screenPos = pos; - if (width > screenWidth || screenPos.x < 0) - screenPos.x = 0; - else if (screenPos.x + width > screenWidth) - screenPos.x = screenWidth - width; - - auto toolbarAllowance = (gScreenFlags & SCREEN_FLAGS_TITLE_DEMO) ? 0 : (kTopToolbarHeight + 1); - if (height - toolbarAllowance > screenHeight || screenPos.y < toolbarAllowance) - screenPos.y = toolbarAllowance; - else if (screenPos.y + height - toolbarAllowance > screenHeight) - screenPos.y = screenHeight + toolbarAllowance - height; - - return screenPos; -} - -static ScreenCoordsXY GetAutoPositionForNewWindow(int32_t width, int32_t height) -{ - auto uiContext = GetContext()->GetUiContext(); - auto screenWidth = uiContext->GetWidth(); - auto screenHeight = uiContext->GetHeight(); - - // Place window in an empty corner of the screen - const ScreenCoordsXY cornerPositions[] = { - { 0, 30 }, // topLeft - { screenWidth - width, 30 }, // topRight - { 0, screenHeight - 34 - height }, // bottomLeft - { screenWidth - width, screenHeight - 34 - height }, // bottomRight - }; - - for (const auto& cornerPos : cornerPositions) - { - if (WindowFitsWithinSpace(cornerPos, width, height)) + for (auto& w : g_window_list) { - return ClampWindowToScreen(cornerPos, screenWidth, screenHeight, width, height); + if (w->flags & WF_DEAD) + continue; + if (w->flags & WF_STICK_TO_BACK) + continue; + + if (loc.x + width <= w->windowPos.x) + continue; + if (loc.x >= w->windowPos.x + w->width) + continue; + if (loc.y + height <= w->windowPos.y) + continue; + if (loc.y >= w->windowPos.y + w->height) + continue; + return false; } + + return true; } - // Place window next to another - for (auto& w : g_window_list) + static bool WindowFitsWithinSpace(const ScreenCoordsXY& loc, int32_t width, int32_t height) { - if (w->flags & WF_DEAD) - continue; - if (w->flags & WF_STICK_TO_BACK) - continue; + if (loc.x < 0) + return false; + if (loc.y <= kTopToolbarHeight && !(gScreenFlags & SCREEN_FLAGS_TITLE_DEMO)) + return false; + if (loc.x + width > ContextGetWidth()) + return false; + if (loc.y + height > ContextGetHeight()) + return false; + return WindowFitsBetweenOthers(loc, width, height); + } - const ScreenCoordsXY offsets[] = { - { w->width + 2, 0 }, - { -w->width - 2, 0 }, - { 0, w->height + 2 }, - { 0, -w->height - 2 }, - { w->width + 2, -w->height - 2 }, - { -w->width - 2, -w->height - 2 }, - { w->width + 2, w->height + 2 }, - { -w->width - 2, w->height + 2 }, + static bool WindowFitsOnScreen(const ScreenCoordsXY& loc, int32_t width, int32_t height) + { + uint16_t screenWidth = ContextGetWidth(); + uint16_t screenHeight = ContextGetHeight(); + int32_t unk; + + unk = -(width / 4); + if (loc.x < unk) + return false; + unk = screenWidth + (unk * 2); + if (loc.x > unk) + return false; + if (loc.y <= kTopToolbarHeight && !(gScreenFlags & SCREEN_FLAGS_TITLE_DEMO)) + return false; + unk = screenHeight - (height / 4); + if (loc.y > unk) + return false; + return WindowFitsBetweenOthers(loc, width, height); + } + + static ScreenCoordsXY ClampWindowToScreen( + const ScreenCoordsXY& pos, const int32_t screenWidth, const int32_t screenHeight, const int32_t width, + const int32_t height) + { + auto screenPos = pos; + if (width > screenWidth || screenPos.x < 0) + screenPos.x = 0; + else if (screenPos.x + width > screenWidth) + screenPos.x = screenWidth - width; + + auto toolbarAllowance = (gScreenFlags & SCREEN_FLAGS_TITLE_DEMO) ? 0 : (kTopToolbarHeight + 1); + if (height - toolbarAllowance > screenHeight || screenPos.y < toolbarAllowance) + screenPos.y = toolbarAllowance; + else if (screenPos.y + height - toolbarAllowance > screenHeight) + screenPos.y = screenHeight + toolbarAllowance - height; + + return screenPos; + } + + static ScreenCoordsXY GetAutoPositionForNewWindow(int32_t width, int32_t height) + { + auto uiContext = GetContext()->GetUiContext(); + auto screenWidth = uiContext->GetWidth(); + auto screenHeight = uiContext->GetHeight(); + + // Place window in an empty corner of the screen + const ScreenCoordsXY cornerPositions[] = { + { 0, 30 }, // topLeft + { screenWidth - width, 30 }, // topRight + { 0, screenHeight - 34 - height }, // bottomLeft + { screenWidth - width, screenHeight - 34 - height }, // bottomRight }; - for (const auto& offset : offsets) + for (const auto& cornerPos : cornerPositions) { - auto screenPos = w->windowPos + offset; - if (WindowFitsWithinSpace(screenPos, width, height)) + if (WindowFitsWithinSpace(cornerPos, width, height)) { - return ClampWindowToScreen(screenPos, screenWidth, screenHeight, width, height); + return ClampWindowToScreen(cornerPos, screenWidth, screenHeight, width, height); } } - } - // Overlap - for (auto& w : g_window_list) - { - if (w->flags & WF_DEAD) - continue; - if (w->flags & WF_STICK_TO_BACK) - continue; - - const ScreenCoordsXY offsets[] = { - { w->width + 2, 0 }, - { -w->width - 2, 0 }, - { 0, w->height + 2 }, - { 0, -w->height - 2 }, - }; - - for (const auto& offset : offsets) + // Place window next to another + for (auto& w : g_window_list) { - auto screenPos = w->windowPos + offset; - if (WindowFitsOnScreen(screenPos, width, height)) + if (w->flags & WF_DEAD) + continue; + if (w->flags & WF_STICK_TO_BACK) + continue; + + const ScreenCoordsXY offsets[] = { + { w->width + 2, 0 }, + { -w->width - 2, 0 }, + { 0, w->height + 2 }, + { 0, -w->height - 2 }, + { w->width + 2, -w->height - 2 }, + { -w->width - 2, -w->height - 2 }, + { w->width + 2, w->height + 2 }, + { -w->width - 2, w->height + 2 }, + }; + + for (const auto& offset : offsets) { - return ClampWindowToScreen(screenPos, screenWidth, screenHeight, width, height); + auto screenPos = w->windowPos + offset; + if (WindowFitsWithinSpace(screenPos, width, height)) + { + return ClampWindowToScreen(screenPos, screenWidth, screenHeight, width, height); + } } } - } - // Cascade - auto screenPos = ScreenCoordsXY{ 0, 30 }; - for (auto& w : g_window_list) - { - if (screenPos == w->windowPos) + // Overlap + for (auto& w : g_window_list) { - screenPos.x += 5; - screenPos.y += 5; + if (w->flags & WF_DEAD) + continue; + if (w->flags & WF_STICK_TO_BACK) + continue; + + const ScreenCoordsXY offsets[] = { + { w->width + 2, 0 }, + { -w->width - 2, 0 }, + { 0, w->height + 2 }, + { 0, -w->height - 2 }, + }; + + for (const auto& offset : offsets) + { + auto screenPos = w->windowPos + offset; + if (WindowFitsOnScreen(screenPos, width, height)) + { + return ClampWindowToScreen(screenPos, screenWidth, screenHeight, width, height); + } + } } + + // Cascade + auto screenPos = ScreenCoordsXY{ 0, 30 }; + for (auto& w : g_window_list) + { + if (screenPos == w->windowPos) + { + screenPos.x += 5; + screenPos.y += 5; + } + } + + return ClampWindowToScreen(screenPos, screenWidth, screenHeight, width, height); } - return ClampWindowToScreen(screenPos, screenWidth, screenHeight, width, height); -} - -static ScreenCoordsXY GetCentrePositionForNewWindow(int32_t width, int32_t height) -{ - auto uiContext = GetContext()->GetUiContext(); - auto screenWidth = uiContext->GetWidth(); - auto screenHeight = uiContext->GetHeight(); - return ScreenCoordsXY{ (screenWidth - width) / 2, std::max(kTopToolbarHeight + 1, (screenHeight - height) / 2) }; -} - -static int32_t WindowGetWidgetIndex(const WindowBase& w, Widget* widget) -{ - const auto it = std::find_if(w.widgets.begin(), w.widgets.end(), [&](auto& otherWidget) { return &otherWidget == widget; }); - if (it == w.widgets.end()) - return -1; - return std::distance(w.widgets.begin(), it); -} - -static int32_t WindowGetScrollIndex(const WindowBase& w, int32_t targetWidgetIndex) -{ - if (w.widgets[targetWidgetIndex].type != WindowWidgetType::Scroll) - return -1; - - int32_t scrollIndex = 0; - for (WidgetIndex widgetIndex = 0; widgetIndex < w.widgets.size(); widgetIndex++) + static ScreenCoordsXY GetCentrePositionForNewWindow(int32_t width, int32_t height) { - if (widgetIndex == targetWidgetIndex) - break; - auto& widget = w.widgets[widgetIndex]; - if (widget.type == WindowWidgetType::Scroll) - scrollIndex++; + auto uiContext = GetContext()->GetUiContext(); + auto screenWidth = uiContext->GetWidth(); + auto screenHeight = uiContext->GetHeight(); + return ScreenCoordsXY{ (screenWidth - width) / 2, std::max(kTopToolbarHeight + 1, (screenHeight - height) / 2) }; } - return scrollIndex; -} - -static Widget* WindowGetScrollWidget(const WindowBase& w, int32_t scrollIndex) -{ - for (WidgetIndex widgetIndex = 0; widgetIndex < w.widgets.size(); widgetIndex++) + static int32_t WindowGetWidgetIndex(const WindowBase& w, Widget* widget) { - auto& widget = w.widgets[widgetIndex]; - if (widget.type != WindowWidgetType::Scroll) - continue; - - if (scrollIndex == 0) - return const_cast(&widget); - scrollIndex--; + const auto it = std::find_if( + w.widgets.begin(), w.widgets.end(), [&](auto& otherWidget) { return &otherWidget == widget; }); + if (it == w.widgets.end()) + return -1; + return std::distance(w.widgets.begin(), it); } - return nullptr; -} - -/** - * - * rct2: 0x006E78E3 - */ -static void WindowScrollWheelInput(WindowBase& w, int32_t scrollIndex, int32_t wheel) -{ - auto& scroll = w.scrolls[scrollIndex]; - Widget* widget = WindowGetScrollWidget(w, scrollIndex); - WidgetIndex widgetIndex = WindowGetWidgetIndex(w, widget); - - if (scroll.flags & VSCROLLBAR_VISIBLE) + static int32_t WindowGetScrollIndex(const WindowBase& w, int32_t targetWidgetIndex) { - int32_t size = widget->height() - 1; - if (scroll.flags & HSCROLLBAR_VISIBLE) - size -= 11; - size = std::max(0, scroll.contentHeight - size); - scroll.contentOffsetY = std::min(std::max(0, scroll.contentOffsetY + wheel), size); + if (w.widgets[targetWidgetIndex].type != WindowWidgetType::Scroll) + return -1; + + int32_t scrollIndex = 0; + for (WidgetIndex widgetIndex = 0; widgetIndex < w.widgets.size(); widgetIndex++) + { + if (widgetIndex == targetWidgetIndex) + break; + auto& widget = w.widgets[widgetIndex]; + if (widget.type == WindowWidgetType::Scroll) + scrollIndex++; + } + + return scrollIndex; } - else + + static Widget* WindowGetScrollWidget(const WindowBase& w, int32_t scrollIndex) { - int32_t size = widget->width() - 1; + for (WidgetIndex widgetIndex = 0; widgetIndex < w.widgets.size(); widgetIndex++) + { + auto& widget = w.widgets[widgetIndex]; + if (widget.type != WindowWidgetType::Scroll) + continue; + + if (scrollIndex == 0) + return const_cast(&widget); + scrollIndex--; + } + + return nullptr; + } + + /** + * + * rct2: 0x006E78E3 + */ + static void WindowScrollWheelInput(WindowBase& w, int32_t scrollIndex, int32_t wheel) + { + auto& scroll = w.scrolls[scrollIndex]; + Widget* widget = WindowGetScrollWidget(w, scrollIndex); + WidgetIndex widgetIndex = WindowGetWidgetIndex(w, widget); + if (scroll.flags & VSCROLLBAR_VISIBLE) - size -= 11; - size = std::max(0, scroll.contentWidth - size); - scroll.contentOffsetX = std::min(std::max(0, scroll.contentOffsetX + wheel), size); - } - - WidgetScrollUpdateThumbs(w, widgetIndex); - WidgetInvalidate(w, widgetIndex); -} - -/** - * - * rct2: 0x006E793B - */ -static int32_t WindowWheelInput(WindowBase& w, int32_t wheel) -{ - int32_t scrollIndex = 0; - for (WidgetIndex widgetIndex = 0; widgetIndex < w.widgets.size(); widgetIndex++) - { - const auto& widget = w.widgets[widgetIndex]; - if (widget.type != WindowWidgetType::Scroll) - continue; - - // Originally always checked first scroll view, bug maybe? - const auto& scroll = w.scrolls[scrollIndex]; - if (scroll.flags & (HSCROLLBAR_VISIBLE | VSCROLLBAR_VISIBLE)) { - WindowScrollWheelInput(w, scrollIndex, wheel); - return 1; - } - scrollIndex++; - } - - return 0; -} - -/** - * - * rct2: 0x006E79FB - */ -static void WindowViewportWheelInput(WindowBase& w, int32_t wheel) -{ - if (gScreenFlags & (SCREEN_FLAGS_TRACK_MANAGER | SCREEN_FLAGS_TITLE_DEMO)) - return; - - if (wheel < 0) - Windows::WindowZoomIn(w, true); - else if (wheel > 0) - Windows::WindowZoomOut(w, true); -} - -static bool isSpinnerGroup(WindowBase& w, WidgetIndex index, WindowWidgetType buttonType) -{ - const auto& widgets = w.widgets; - - if (widgets[index].type != WindowWidgetType::Spinner && widgets[index].type != WindowWidgetType::ImgBtn) - return false; - - if (widgets[index + 1].type != buttonType) - return false; - - if (widgets[index + 2].type != buttonType) - return false; - - return true; -} - -static std::optional getSpinnerGroupWidgetIndex(WindowBase& w, WidgetIndex startIndex) -{ - // We only iterate 3 times as we might be at the spinner or one of its buttons. - for (WidgetIndex index = 0; index < 3; index++) - { - const auto reverseIndex = startIndex - index; - if (reverseIndex < 0) - { - break; - } - - if (isSpinnerGroup(w, reverseIndex, WindowWidgetType::TrnBtn)) - { - return reverseIndex; - } - - if (isSpinnerGroup(w, reverseIndex, WindowWidgetType::Button)) - { - return reverseIndex; - } - } - - return std::nullopt; -} - -// Allow mouse wheel scrolling to manipulate spinner widgets and tool sizes -static bool WindowOtherWheelInput(WindowBase& w, WidgetIndex widgetIndex, int32_t wheel) -{ - // HACK: Until we have a new window system that allows us to add new events like mouse wheel easily, - // this selective approach will have to do. - - const auto spinnerGroupIndex = getSpinnerGroupWidgetIndex(w, widgetIndex); - if (!spinnerGroupIndex.has_value()) - { - return false; - } - - const auto entryWidgetType = w.widgets[*spinnerGroupIndex].type; - auto targetWidgetIndex = *spinnerGroupIndex; - - if (entryWidgetType == WindowWidgetType::ImgBtn) - { - auto expectedContent1 = ImageId(SPR_LAND_TOOL_DECREASE, FilterPaletteID::PaletteNull); - auto expectedContent2 = ImageId(SPR_LAND_TOOL_INCREASE, FilterPaletteID::PaletteNull); - - auto button1Image = w.widgets[*spinnerGroupIndex + 1].image; - auto button2Image = w.widgets[*spinnerGroupIndex + 2].image; - if (button1Image != expectedContent1 || button2Image != expectedContent2) - { - return false; - } - - // Expected widget order: decrease, increase - targetWidgetIndex += wheel < 0 ? 2 : 1; - } - else if (entryWidgetType == WindowWidgetType::Spinner) - { - auto button1StringId = w.widgets[*spinnerGroupIndex + 1].text; - auto button2StringId = w.widgets[*spinnerGroupIndex + 2].text; - if (button1StringId != STR_NUMERIC_UP || button2StringId != STR_NUMERIC_DOWN) - { - return false; - } - - // Expected widget order: increase, decrease - targetWidgetIndex += wheel < 0 ? 1 : 2; - } - - if (WidgetIsDisabled(w, targetWidgetIndex)) - { - return false; - } - - w.OnMouseDown(targetWidgetIndex); - return true; -} - -/** - * - * rct2: 0x006E7868 - */ -void WindowAllWheelInput() -{ - // Get wheel value - auto cursorState = ContextGetCursorState(); - int32_t absolute_wheel = cursorState->wheel; - int32_t relative_wheel = absolute_wheel - _previousAbsoluteWheel; - int32_t pixel_scroll = relative_wheel * WindowScrollPixels; - _previousAbsoluteWheel = absolute_wheel; - - if (relative_wheel == 0) - return; - - // Check window cursor is over - if (!(InputTestFlag(INPUT_FLAG_5))) - { - auto* windowMgr = GetContext()->GetUiContext()->GetWindowManager(); - WindowBase* w = windowMgr->FindFromPoint(cursorState->position); - if (w != nullptr) - { - // Check if main window - if (w->classification == WindowClass::MainWindow || w->classification == WindowClass::Viewport) - { - WindowViewportWheelInput(*w, relative_wheel); - return; - } - - // Check scroll view, cursor is over - WidgetIndex widgetIndex = windowMgr->FindWidgetFromPoint(*w, cursorState->position); - if (widgetIndex != kWidgetIndexNull) - { - const auto& widget = w->widgets[widgetIndex]; - if (widget.type == WindowWidgetType::Scroll) - { - int32_t scrollIndex = WindowGetScrollIndex(*w, widgetIndex); - const auto& scroll = w->scrolls[scrollIndex]; - if (scroll.flags & (HSCROLLBAR_VISIBLE | VSCROLLBAR_VISIBLE)) - { - WindowScrollWheelInput(*w, WindowGetScrollIndex(*w, widgetIndex), pixel_scroll); - return; - } - } - else - { - if (WindowOtherWheelInput(*w, widgetIndex, pixel_scroll)) - { - return; - } - } - - // Check other scroll views on window - if (WindowWheelInput(*w, pixel_scroll)) - return; - } - } - } -} - -void ApplyScreenSaverLockSetting() -{ - Config::Get().general.DisableScreensaver ? SDL_DisableScreenSaver() : SDL_EnableScreenSaver(); -} - -/** - * - * rct2: 0x006EA776 - */ -static void WindowInvalidatePressedImageButton(const WindowBase& w) -{ - for (WidgetIndex widgetIndex = 0; widgetIndex < w.widgets.size(); widgetIndex++) - { - auto& widget = w.widgets[widgetIndex]; - if (widget.type != WindowWidgetType::ImgBtn) - continue; - - if (WidgetIsPressed(w, widgetIndex) || isToolActive(w, widgetIndex)) - GfxSetDirtyBlocks({ w.windowPos, w.windowPos + ScreenCoordsXY{ w.width, w.height } }); - } -} - -void Window::ScrollToViewport() -{ - if (viewport == nullptr || !focus.has_value()) - return; - - CoordsXYZ newCoords = focus.value().GetPos(); - - auto mainWindow = WindowGetMain(); - if (mainWindow != nullptr) - WindowScrollToLocation(*mainWindow, newCoords); -} - -void Window::OnDraw(DrawPixelInfo& dpi) -{ - Windows::WindowDrawWidgets(*this, dpi); -} - -void Window::OnDrawWidget(WidgetIndex widgetIndex, DrawPixelInfo& dpi) -{ - WidgetDraw(dpi, *this, widgetIndex); -} - -void Window::InitScrollWidgets() -{ - Windows::WindowInitScrollWidgets(*this); -} - -void Window::InvalidateWidget(WidgetIndex widgetIndex) -{ - WidgetInvalidate(*this, widgetIndex); -} - -bool Window::IsWidgetDisabled(WidgetIndex widgetIndex) const -{ - return WidgetIsDisabled(*this, widgetIndex); -} - -bool Window::IsWidgetPressed(WidgetIndex widgetIndex) const -{ - return WidgetIsPressed(*this, widgetIndex); -} - -void Window::SetWidgetEnabled(WidgetIndex widgetIndex, bool value) -{ - WidgetSetEnabled(*this, widgetIndex, value); -} - -void Window::SetWidgetDisabled(WidgetIndex widgetIndex, bool value) -{ - WidgetSetDisabled(*this, widgetIndex, value); -} - -void Window::SetWidgetDisabledAndInvalidate(WidgetIndex widgetIndex, bool value) -{ - bool oldState = IsWidgetDisabled(widgetIndex); - if (oldState != value) - { - WidgetSetDisabled(*this, widgetIndex, value); - InvalidateWidget(widgetIndex); - } -} - -void Window::SetWidgetPressed(WidgetIndex widgetIndex, bool value) -{ - WidgetSetPressed(*this, widgetIndex, value); -} - -void Window::SetCheckboxValue(WidgetIndex widgetIndex, bool value) -{ - SetWidgetPressed(widgetIndex, value); -} - -void Window::DrawWidgets(DrawPixelInfo& dpi) -{ - Windows::WindowDrawWidgets(*this, dpi); -} - -void Window::Close() -{ - CloseWindowModifier modifier = GetCloseModifier(); - - if (modifier == CloseWindowModifier::Shift) - { - CloseOthers(); - } - else if (modifier == CloseWindowModifier::Control) - { - CloseOthersOfThisClass(); - } - else - { - WindowClose(*this); - } -} - -void Window::CloseOthers() -{ - WindowCloseAllExceptNumberAndClass(number, classification); -} - -void Window::CloseOthersOfThisClass() -{ - WindowCloseByClass(classification); -} - -CloseWindowModifier Window::GetCloseModifier() -{ - CloseWindowModifier lastModifier = CloseWindowModifier::None; - - if (gLastCloseModifier.window.number == number && gLastCloseModifier.window.classification == classification) - { - lastModifier = gLastCloseModifier.modifier; - } - - gLastCloseModifier.modifier = CloseWindowModifier::None; - - return lastModifier; -} - -void Window::TextInputOpen( - WidgetIndex callWidget, StringId title, StringId description, const Formatter& descriptionArgs, StringId existingText, - uintptr_t existingArgs, int32_t maxLength) -{ - OpenRCT2::Ui::Windows::WindowTextInputOpen( - this, callWidget, title, description, descriptionArgs, existingText, existingArgs, maxLength); -} - -void Window::ResizeFrame() -{ - // Frame - widgets[0].right = width - 1; - widgets[0].bottom = height - 1; - // Title - widgets[1].right = width - 2; - // Close button - if (Config::Get().interface.WindowButtonsOnTheLeft) - { - widgets[2].left = 2; - widgets[2].right = 2 + kCloseButtonWidth; - } - else - { - widgets[2].left = width - 3 - kCloseButtonWidth; - widgets[2].right = width - 3; - } -} - -void Window::ResizeFrameWithPage() -{ - ResizeFrame(); - // Page background - widgets[3].right = width - 1; - widgets[3].bottom = height - 1; -} - -void Window::ResizeSpinner(WidgetIndex widgetIndex, const ScreenCoordsXY& origin, const ScreenSize& size) -{ - auto right = origin.x + size.width - 1; - auto bottom = origin.y + size.height - 1; - widgets[widgetIndex].left = origin.x; - widgets[widgetIndex].top = origin.y; - widgets[widgetIndex].right = right; - widgets[widgetIndex].bottom = bottom; - - widgets[widgetIndex + 1].left = right - size.height; // subtract height to maintain aspect ratio - widgets[widgetIndex + 1].top = origin.y + 1; - widgets[widgetIndex + 1].right = right - 1; - widgets[widgetIndex + 1].bottom = bottom - 1; - - widgets[widgetIndex + 2].left = right - size.height * 2; - widgets[widgetIndex + 2].top = origin.y + 1; - widgets[widgetIndex + 2].right = right - size.height - 1; - widgets[widgetIndex + 2].bottom = bottom - 1; -} - -void Window::ResizeDropdown(WidgetIndex widgetIndex, const ScreenCoordsXY& origin, const ScreenSize& size) -{ - auto right = origin.x + size.width - 1; - auto bottom = origin.y + size.height - 1; - widgets[widgetIndex].left = origin.x; - widgets[widgetIndex].top = origin.y; - widgets[widgetIndex].right = right; - widgets[widgetIndex].bottom = bottom; - - widgets[widgetIndex + 1].left = right - size.height + 1; // subtract height to maintain aspect ratio - widgets[widgetIndex + 1].top = origin.y + 1; - widgets[widgetIndex + 1].right = right - 1; - widgets[widgetIndex + 1].bottom = bottom - 1; -} - -void WindowAlignTabs(WindowBase* w, WidgetIndex start_tab_id, WidgetIndex end_tab_id) -{ - int32_t i, x = w->widgets[start_tab_id].left; - int32_t tab_width = w->widgets[start_tab_id].width(); - - for (i = start_tab_id; i <= end_tab_id; i++) - { - auto& widget = w->widgets[i]; - if (!WidgetIsDisabled(*w, i)) - { - widget.left = x; - widget.right = x + tab_width; - x += tab_width + 1; + int32_t size = widget->height() - 1; + if (scroll.flags & HSCROLLBAR_VISIBLE) + size -= 11; + size = std::max(0, scroll.contentHeight - size); + scroll.contentOffsetY = std::min(std::max(0, scroll.contentOffsetY + wheel), size); } else { - // Workaround #20535: Avoid disabled widgets from sharing the same space as active ones, otherwise - // WindowFindWidgetFromPoint could return the disabled one, causing issues. - widget.left = 0; - widget.right = 0; + int32_t size = widget->width() - 1; + if (scroll.flags & VSCROLLBAR_VISIBLE) + size -= 11; + size = std::max(0, scroll.contentWidth - size); + scroll.contentOffsetX = std::min(std::max(0, scroll.contentOffsetX + wheel), size); + } + + WidgetScrollUpdateThumbs(w, widgetIndex); + WidgetInvalidate(w, widgetIndex); + } + + /** + * + * rct2: 0x006E793B + */ + static int32_t WindowWheelInput(WindowBase& w, int32_t wheel) + { + int32_t scrollIndex = 0; + for (WidgetIndex widgetIndex = 0; widgetIndex < w.widgets.size(); widgetIndex++) + { + const auto& widget = w.widgets[widgetIndex]; + if (widget.type != WindowWidgetType::Scroll) + continue; + + // Originally always checked first scroll view, bug maybe? + const auto& scroll = w.scrolls[scrollIndex]; + if (scroll.flags & (HSCROLLBAR_VISIBLE | VSCROLLBAR_VISIBLE)) + { + WindowScrollWheelInput(w, scrollIndex, wheel); + return 1; + } + scrollIndex++; + } + + return 0; + } + + /** + * + * rct2: 0x006E79FB + */ + static void WindowViewportWheelInput(WindowBase& w, int32_t wheel) + { + if (gScreenFlags & (SCREEN_FLAGS_TRACK_MANAGER | SCREEN_FLAGS_TITLE_DEMO)) + return; + + if (wheel < 0) + Windows::WindowZoomIn(w, true); + else if (wheel > 0) + Windows::WindowZoomOut(w, true); + } + + static bool isSpinnerGroup(WindowBase& w, WidgetIndex index, WindowWidgetType buttonType) + { + const auto& widgets = w.widgets; + + if (widgets[index].type != WindowWidgetType::Spinner && widgets[index].type != WindowWidgetType::ImgBtn) + return false; + + if (widgets[index + 1].type != buttonType) + return false; + + if (widgets[index + 2].type != buttonType) + return false; + + return true; + } + + static std::optional getSpinnerGroupWidgetIndex(WindowBase& w, WidgetIndex startIndex) + { + // We only iterate 3 times as we might be at the spinner or one of its buttons. + for (WidgetIndex index = 0; index < 3; index++) + { + const auto reverseIndex = startIndex - index; + if (reverseIndex < 0) + { + break; + } + + if (isSpinnerGroup(w, reverseIndex, WindowWidgetType::TrnBtn)) + { + return reverseIndex; + } + + if (isSpinnerGroup(w, reverseIndex, WindowWidgetType::Button)) + { + return reverseIndex; + } + } + + return std::nullopt; + } + + // Allow mouse wheel scrolling to manipulate spinner widgets and tool sizes + static bool WindowOtherWheelInput(WindowBase& w, WidgetIndex widgetIndex, int32_t wheel) + { + // HACK: Until we have a new window system that allows us to add new events like mouse wheel easily, + // this selective approach will have to do. + + const auto spinnerGroupIndex = getSpinnerGroupWidgetIndex(w, widgetIndex); + if (!spinnerGroupIndex.has_value()) + { + return false; + } + + const auto entryWidgetType = w.widgets[*spinnerGroupIndex].type; + auto targetWidgetIndex = *spinnerGroupIndex; + + if (entryWidgetType == WindowWidgetType::ImgBtn) + { + auto expectedContent1 = ImageId(SPR_LAND_TOOL_DECREASE, FilterPaletteID::PaletteNull); + auto expectedContent2 = ImageId(SPR_LAND_TOOL_INCREASE, FilterPaletteID::PaletteNull); + + auto button1Image = w.widgets[*spinnerGroupIndex + 1].image; + auto button2Image = w.widgets[*spinnerGroupIndex + 2].image; + if (button1Image != expectedContent1 || button2Image != expectedContent2) + { + return false; + } + + // Expected widget order: decrease, increase + targetWidgetIndex += wheel < 0 ? 2 : 1; + } + else if (entryWidgetType == WindowWidgetType::Spinner) + { + auto button1StringId = w.widgets[*spinnerGroupIndex + 1].text; + auto button2StringId = w.widgets[*spinnerGroupIndex + 2].text; + if (button1StringId != STR_NUMERIC_UP || button2StringId != STR_NUMERIC_DOWN) + { + return false; + } + + // Expected widget order: increase, decrease + targetWidgetIndex += wheel < 0 ? 1 : 2; + } + + if (WidgetIsDisabled(w, targetWidgetIndex)) + { + return false; + } + + w.OnMouseDown(targetWidgetIndex); + return true; + } + + /** + * + * rct2: 0x006E7868 + */ + void WindowAllWheelInput() + { + // Get wheel value + auto cursorState = ContextGetCursorState(); + int32_t absolute_wheel = cursorState->wheel; + int32_t relative_wheel = absolute_wheel - _previousAbsoluteWheel; + int32_t pixel_scroll = relative_wheel * WindowScrollPixels; + _previousAbsoluteWheel = absolute_wheel; + + if (relative_wheel == 0) + return; + + // Check window cursor is over + if (!(InputTestFlag(INPUT_FLAG_5))) + { + auto* windowMgr = GetContext()->GetUiContext()->GetWindowManager(); + WindowBase* w = windowMgr->FindFromPoint(cursorState->position); + if (w != nullptr) + { + // Check if main window + if (w->classification == WindowClass::MainWindow || w->classification == WindowClass::Viewport) + { + WindowViewportWheelInput(*w, relative_wheel); + return; + } + + // Check scroll view, cursor is over + WidgetIndex widgetIndex = windowMgr->FindWidgetFromPoint(*w, cursorState->position); + if (widgetIndex != kWidgetIndexNull) + { + const auto& widget = w->widgets[widgetIndex]; + if (widget.type == WindowWidgetType::Scroll) + { + int32_t scrollIndex = WindowGetScrollIndex(*w, widgetIndex); + const auto& scroll = w->scrolls[scrollIndex]; + if (scroll.flags & (HSCROLLBAR_VISIBLE | VSCROLLBAR_VISIBLE)) + { + WindowScrollWheelInput(*w, WindowGetScrollIndex(*w, widgetIndex), pixel_scroll); + return; + } + } + else + { + if (WindowOtherWheelInput(*w, widgetIndex, pixel_scroll)) + { + return; + } + } + + // Check other scroll views on window + if (WindowWheelInput(*w, pixel_scroll)) + return; + } + } } } -} -ScreenCoordsXY WindowGetViewportSoundIconPos(WindowBase& w) -{ - const uint8_t buttonOffset = (Config::Get().interface.WindowButtonsOnTheLeft) ? kCloseButtonWidth + 2 : 0; - return w.windowPos + ScreenCoordsXY{ 2 + buttonOffset, 2 }; -} + void ApplyScreenSaverLockSetting() + { + Config::Get().general.DisableScreensaver ? SDL_DisableScreenSaver() : SDL_EnableScreenSaver(); + } + + /** + * + * rct2: 0x006EA776 + */ + static void WindowInvalidatePressedImageButton(const WindowBase& w) + { + for (WidgetIndex widgetIndex = 0; widgetIndex < w.widgets.size(); widgetIndex++) + { + auto& widget = w.widgets[widgetIndex]; + if (widget.type != WindowWidgetType::ImgBtn) + continue; + + if (WidgetIsPressed(w, widgetIndex) || isToolActive(w, widgetIndex)) + GfxSetDirtyBlocks({ w.windowPos, w.windowPos + ScreenCoordsXY{ w.width, w.height } }); + } + } + + void Window::ScrollToViewport() + { + if (viewport == nullptr || !focus.has_value()) + return; + + CoordsXYZ newCoords = focus.value().GetPos(); + + auto mainWindow = WindowGetMain(); + if (mainWindow != nullptr) + WindowScrollToLocation(*mainWindow, newCoords); + } + + void Window::OnDraw(DrawPixelInfo& dpi) + { + Windows::WindowDrawWidgets(*this, dpi); + } + + void Window::OnDrawWidget(WidgetIndex widgetIndex, DrawPixelInfo& dpi) + { + WidgetDraw(dpi, *this, widgetIndex); + } + + void Window::InitScrollWidgets() + { + Windows::WindowInitScrollWidgets(*this); + } + + void Window::InvalidateWidget(WidgetIndex widgetIndex) + { + WidgetInvalidate(*this, widgetIndex); + } + + bool Window::IsWidgetDisabled(WidgetIndex widgetIndex) const + { + return WidgetIsDisabled(*this, widgetIndex); + } + + bool Window::IsWidgetPressed(WidgetIndex widgetIndex) const + { + return WidgetIsPressed(*this, widgetIndex); + } + + void Window::SetWidgetEnabled(WidgetIndex widgetIndex, bool value) + { + WidgetSetEnabled(*this, widgetIndex, value); + } + + void Window::SetWidgetDisabled(WidgetIndex widgetIndex, bool value) + { + WidgetSetDisabled(*this, widgetIndex, value); + } + + void Window::SetWidgetDisabledAndInvalidate(WidgetIndex widgetIndex, bool value) + { + bool oldState = IsWidgetDisabled(widgetIndex); + if (oldState != value) + { + WidgetSetDisabled(*this, widgetIndex, value); + InvalidateWidget(widgetIndex); + } + } + + void Window::SetWidgetPressed(WidgetIndex widgetIndex, bool value) + { + WidgetSetPressed(*this, widgetIndex, value); + } + + void Window::SetCheckboxValue(WidgetIndex widgetIndex, bool value) + { + SetWidgetPressed(widgetIndex, value); + } + + void Window::DrawWidgets(DrawPixelInfo& dpi) + { + Windows::WindowDrawWidgets(*this, dpi); + } + + void Window::Close() + { + CloseWindowModifier modifier = GetCloseModifier(); + + if (modifier == CloseWindowModifier::Shift) + { + CloseOthers(); + } + else if (modifier == CloseWindowModifier::Control) + { + CloseOthersOfThisClass(); + } + else + { + WindowClose(*this); + } + } + + void Window::CloseOthers() + { + WindowCloseAllExceptNumberAndClass(number, classification); + } + + void Window::CloseOthersOfThisClass() + { + WindowCloseByClass(classification); + } + + CloseWindowModifier Window::GetCloseModifier() + { + CloseWindowModifier lastModifier = CloseWindowModifier::None; + + if (gLastCloseModifier.window.number == number && gLastCloseModifier.window.classification == classification) + { + lastModifier = gLastCloseModifier.modifier; + } + + gLastCloseModifier.modifier = CloseWindowModifier::None; + + return lastModifier; + } + + void Window::TextInputOpen( + WidgetIndex callWidget, StringId title, StringId description, const Formatter& descriptionArgs, StringId existingText, + uintptr_t existingArgs, int32_t maxLength) + { + OpenRCT2::Ui::Windows::WindowTextInputOpen( + this, callWidget, title, description, descriptionArgs, existingText, existingArgs, maxLength); + } + + void Window::ResizeFrame() + { + // Frame + widgets[0].right = width - 1; + widgets[0].bottom = height - 1; + // Title + widgets[1].right = width - 2; + // Close button + if (Config::Get().interface.WindowButtonsOnTheLeft) + { + widgets[2].left = 2; + widgets[2].right = 2 + kCloseButtonWidth; + } + else + { + widgets[2].left = width - 3 - kCloseButtonWidth; + widgets[2].right = width - 3; + } + } + + void Window::ResizeFrameWithPage() + { + ResizeFrame(); + // Page background + widgets[3].right = width - 1; + widgets[3].bottom = height - 1; + } + + void Window::ResizeSpinner(WidgetIndex widgetIndex, const ScreenCoordsXY& origin, const ScreenSize& size) + { + auto right = origin.x + size.width - 1; + auto bottom = origin.y + size.height - 1; + widgets[widgetIndex].left = origin.x; + widgets[widgetIndex].top = origin.y; + widgets[widgetIndex].right = right; + widgets[widgetIndex].bottom = bottom; + + widgets[widgetIndex + 1].left = right - size.height; // subtract height to maintain aspect ratio + widgets[widgetIndex + 1].top = origin.y + 1; + widgets[widgetIndex + 1].right = right - 1; + widgets[widgetIndex + 1].bottom = bottom - 1; + + widgets[widgetIndex + 2].left = right - size.height * 2; + widgets[widgetIndex + 2].top = origin.y + 1; + widgets[widgetIndex + 2].right = right - size.height - 1; + widgets[widgetIndex + 2].bottom = bottom - 1; + } + + void Window::ResizeDropdown(WidgetIndex widgetIndex, const ScreenCoordsXY& origin, const ScreenSize& size) + { + auto right = origin.x + size.width - 1; + auto bottom = origin.y + size.height - 1; + widgets[widgetIndex].left = origin.x; + widgets[widgetIndex].top = origin.y; + widgets[widgetIndex].right = right; + widgets[widgetIndex].bottom = bottom; + + widgets[widgetIndex + 1].left = right - size.height + 1; // subtract height to maintain aspect ratio + widgets[widgetIndex + 1].top = origin.y + 1; + widgets[widgetIndex + 1].right = right - 1; + widgets[widgetIndex + 1].bottom = bottom - 1; + } + + void WindowAlignTabs(WindowBase* w, WidgetIndex start_tab_id, WidgetIndex end_tab_id) + { + int32_t i, x = w->widgets[start_tab_id].left; + int32_t tab_width = w->widgets[start_tab_id].width(); + + for (i = start_tab_id; i <= end_tab_id; i++) + { + auto& widget = w->widgets[i]; + if (!WidgetIsDisabled(*w, i)) + { + widget.left = x; + widget.right = x + tab_width; + x += tab_width + 1; + } + else + { + // Workaround #20535: Avoid disabled widgets from sharing the same space as active ones, otherwise + // WindowFindWidgetFromPoint could return the disabled one, causing issues. + widget.left = 0; + widget.right = 0; + } + } + } + + ScreenCoordsXY WindowGetViewportSoundIconPos(WindowBase& w) + { + const uint8_t buttonOffset = (Config::Get().interface.WindowButtonsOnTheLeft) ? kCloseButtonWidth + 2 : 0; + return w.windowPos + ScreenCoordsXY{ 2 + buttonOffset, 2 }; + } +} // namespace OpenRCT2 namespace OpenRCT2::Ui::Windows { diff --git a/src/openrct2-ui/interface/Window.h b/src/openrct2-ui/interface/Window.h index 6b2000da8c..d5003c07c0 100644 --- a/src/openrct2-ui/interface/Window.h +++ b/src/openrct2-ui/interface/Window.h @@ -14,41 +14,44 @@ struct TextInputSession; -struct Window : WindowBase +namespace OpenRCT2 { - virtual void OnDraw(DrawPixelInfo& dpi) override; - virtual void OnDrawWidget(WidgetIndex widgetIndex, DrawPixelInfo& dpi) override; + struct Window : WindowBase + { + void OnDraw(DrawPixelInfo& dpi) override; + void OnDrawWidget(WidgetIndex widgetIndex, DrawPixelInfo& dpi) override; - void ScrollToViewport(); - void InitScrollWidgets(); - void InvalidateWidget(WidgetIndex widgetIndex); - bool IsWidgetDisabled(WidgetIndex widgetIndex) const; - bool IsWidgetPressed(WidgetIndex widgetIndex) const; - void SetWidgetEnabled(WidgetIndex widgetIndex, bool value); - void SetWidgetDisabled(WidgetIndex widgetIndex, bool value); - void SetWidgetDisabledAndInvalidate(WidgetIndex widgetIndex, bool value); - void SetWidgetPressed(WidgetIndex widgetIndex, bool value); - void SetCheckboxValue(WidgetIndex widgetIndex, bool value); - void DrawWidgets(DrawPixelInfo& dpi); - void Close(); - void CloseOthers(); - void CloseOthersOfThisClass(); - CloseWindowModifier GetCloseModifier(); - void TextInputOpen( - WidgetIndex callWidget, StringId title, StringId description, const Formatter& descriptionArgs, StringId existingText, - uintptr_t existingArgs, int32_t maxLength); + void ScrollToViewport(); + void InitScrollWidgets(); + void InvalidateWidget(WidgetIndex widgetIndex); + bool IsWidgetDisabled(WidgetIndex widgetIndex) const; + bool IsWidgetPressed(WidgetIndex widgetIndex) const; + void SetWidgetEnabled(WidgetIndex widgetIndex, bool value); + void SetWidgetDisabled(WidgetIndex widgetIndex, bool value); + void SetWidgetDisabledAndInvalidate(WidgetIndex widgetIndex, bool value); + void SetWidgetPressed(WidgetIndex widgetIndex, bool value); + void SetCheckboxValue(WidgetIndex widgetIndex, bool value); + void DrawWidgets(DrawPixelInfo& dpi); + void Close(); + void CloseOthers(); + void CloseOthersOfThisClass(); + CloseWindowModifier GetCloseModifier(); + void TextInputOpen( + WidgetIndex callWidget, StringId title, StringId description, const Formatter& descriptionArgs, + StringId existingText, uintptr_t existingArgs, int32_t maxLength); - void ResizeFrame(); - void ResizeFrameWithPage(); + void ResizeFrame(); + void ResizeFrameWithPage(); - void ResizeSpinner(WidgetIndex widgetIndex, const ScreenCoordsXY& origin, const ScreenSize& size); - void ResizeDropdown(WidgetIndex widgetIndex, const ScreenCoordsXY& origin, const ScreenSize& size); -}; + void ResizeSpinner(WidgetIndex widgetIndex, const ScreenCoordsXY& origin, const ScreenSize& size); + void ResizeDropdown(WidgetIndex widgetIndex, const ScreenCoordsXY& origin, const ScreenSize& size); + }; -void WindowAllWheelInput(); -void ApplyScreenSaverLockSetting(); -void WindowAlignTabs(WindowBase* w, WidgetIndex start_tab_id, WidgetIndex end_tab_id); -ScreenCoordsXY WindowGetViewportSoundIconPos(WindowBase& w); + void WindowAllWheelInput(); + void ApplyScreenSaverLockSetting(); + void WindowAlignTabs(WindowBase* w, WidgetIndex start_tab_id, WidgetIndex end_tab_id); + ScreenCoordsXY WindowGetViewportSoundIconPos(WindowBase& w); +} // namespace OpenRCT2 namespace OpenRCT2::Ui::Windows { diff --git a/src/openrct2-ui/scripting/CustomListView.cpp b/src/openrct2-ui/scripting/CustomListView.cpp index 15d927029e..c7841755d9 100644 --- a/src/openrct2-ui/scripting/CustomListView.cpp +++ b/src/openrct2-ui/scripting/CustomListView.cpp @@ -830,7 +830,7 @@ std::optional CustomListView::GetItemIndexAt(const ScreenCoordsXY& po return result; } -Widget* CustomListView::GetWidget() const +OpenRCT2::Widget* CustomListView::GetWidget() const { size_t scrollIndex = 0; for (WidgetIndex widgetIndex = 0; widgetIndex < ParentWindow->widgets.size(); widgetIndex++) diff --git a/src/openrct2-ui/windows/Window.h b/src/openrct2-ui/windows/Window.h index ddc0faaf7d..a953b8954c 100644 --- a/src/openrct2-ui/windows/Window.h +++ b/src/openrct2-ui/windows/Window.h @@ -11,6 +11,7 @@ #include #include +#include #include struct ObjectEntryDescriptor; diff --git a/src/openrct2/Context.h b/src/openrct2/Context.h index 290dce7602..1a8649e6d8 100644 --- a/src/openrct2/Context.h +++ b/src/openrct2/Context.h @@ -25,12 +25,13 @@ enum class CursorID : uint8_t; namespace OpenRCT2 { struct IStream; -} + class Intent; + struct WindowBase; +} // namespace OpenRCT2 + struct ITrackDesignRepository; struct IGameStateSnapshots; -class Intent; -struct WindowBase; struct NewVersionInfo; struct TTFFontDescriptor; @@ -221,12 +222,12 @@ int32_t ContextGetWidth(); int32_t ContextGetHeight(); bool ContextHasFocus(); void ContextSetCursorTrap(bool value); -WindowBase* ContextOpenWindow(WindowClass wc); -WindowBase* ContextOpenDetailWindow(uint8_t type, int32_t id); -WindowBase* ContextOpenWindowView(uint8_t view); -WindowBase* ContextShowError(StringId title, StringId message, const class Formatter& args, bool autoClose = false); -WindowBase* ContextOpenIntent(Intent* intent); -void ContextBroadcastIntent(Intent* intent); +OpenRCT2::WindowBase* ContextOpenWindow(WindowClass wc); +OpenRCT2::WindowBase* ContextOpenDetailWindow(uint8_t type, int32_t id); +OpenRCT2::WindowBase* ContextOpenWindowView(uint8_t view); +OpenRCT2::WindowBase* ContextShowError(StringId title, StringId message, const class Formatter& args, bool autoClose = false); +OpenRCT2::WindowBase* ContextOpenIntent(OpenRCT2::Intent* intent); +void ContextBroadcastIntent(OpenRCT2::Intent* intent); void ContextForceCloseWindowByClass(WindowClass wc); void ContextHandleInput(); void ContextInputHandleKeyboard(bool isTitle); diff --git a/src/openrct2/Game.h b/src/openrct2/Game.h index 9b4908e1b1..e83ddfbd53 100644 --- a/src/openrct2/Game.h +++ b/src/openrct2/Game.h @@ -13,7 +13,11 @@ #include -class Intent; +namespace OpenRCT2 +{ + class Intent; +} + struct ParkLoadResult; enum class GameCommand : int32_t @@ -165,7 +169,7 @@ void PauseToggle(); bool GameIsPaused(); bool GameIsNotPaused(); void SaveGame(); -std::unique_ptr CreateSaveGameAsIntent(); +std::unique_ptr CreateSaveGameAsIntent(); void SaveGameAs(); void SaveGameCmd(u8string_view name = {}); void SaveGameWithName(u8string_view name); diff --git a/src/openrct2/Input.cpp b/src/openrct2/Input.cpp index c25e902ed1..2f85b6a3a2 100644 --- a/src/openrct2/Input.cpp +++ b/src/openrct2/Input.cpp @@ -12,65 +12,68 @@ #include "Context.h" #include "Game.h" -InputState _inputState; -uint8_t _inputFlags; - -WidgetRef gHoverWidget; -WidgetRef gPressedWidget; - -uint32_t _tooltipNotShownTimeout; - -/** - * - * rct2: 0x006E3B43 - */ -void TitleHandleKeyboardInput() +namespace OpenRCT2 { - ContextInputHandleKeyboard(true); -} + InputState _inputState; + uint8_t _inputFlags; -/** - * - * rct2: 0x006E3B43 - */ -void GameHandleKeyboardInput() -{ - ContextInputHandleKeyboard(false); -} + WidgetRef gHoverWidget; + WidgetRef gPressedWidget; -void InputSetFlag(INPUT_FLAGS flag, bool on) -{ - if (on) + uint32_t _tooltipNotShownTimeout; + + /** + * + * rct2: 0x006E3B43 + */ + void TitleHandleKeyboardInput() { - _inputFlags |= flag; + ContextInputHandleKeyboard(true); } - else + + /** + * + * rct2: 0x006E3B43 + */ + void GameHandleKeyboardInput() { - _inputFlags &= ~flag; + ContextInputHandleKeyboard(false); } -} -bool InputTestFlag(INPUT_FLAGS flag) -{ - return _inputFlags & flag; -} + void InputSetFlag(INPUT_FLAGS flag, bool on) + { + if (on) + { + _inputFlags |= flag; + } + else + { + _inputFlags &= ~flag; + } + } -void InputResetFlags() -{ - _inputFlags = 0; -} + bool InputTestFlag(INPUT_FLAGS flag) + { + return _inputFlags & flag; + } -void InputSetState(InputState state) -{ - _inputState = state; -} + void InputResetFlags() + { + _inputFlags = 0; + } -InputState InputGetState() -{ - return _inputState; -} + void InputSetState(InputState state) + { + _inputState = state; + } -void ResetTooltipNotShown() -{ - _tooltipNotShownTimeout = gCurrentRealTimeTicks + 50; -} + InputState InputGetState() + { + return _inputState; + } + + void ResetTooltipNotShown() + { + _tooltipNotShownTimeout = gCurrentRealTimeTicks + 50; + } +} // namespace OpenRCT2 diff --git a/src/openrct2/Input.h b/src/openrct2/Input.h index bf38db927c..8189e18926 100644 --- a/src/openrct2/Input.h +++ b/src/openrct2/Input.h @@ -11,65 +11,68 @@ #include "interface/Window.h" -enum INPUT_FLAGS +namespace OpenRCT2 { - INPUT_FLAG_WIDGET_PRESSED = (1 << 0), + enum INPUT_FLAGS + { + INPUT_FLAG_WIDGET_PRESSED = (1 << 0), - // The dropdown can stay open if the mouse is released, set on flag Dropdown::Flag::StayOpen - INPUT_FLAG_DROPDOWN_STAY_OPEN = (1 << 1), + // The dropdown can stay open if the mouse is released, set on flag Dropdown::Flag::StayOpen + INPUT_FLAG_DROPDOWN_STAY_OPEN = (1 << 1), - // The mouse has been released and the dropdown is still open - // INPUT_FLAG_DROPDOWN_STAY_OPEN is already set if this happens - INPUT_FLAG_DROPDOWN_MOUSE_UP = (1 << 2), + // The mouse has been released and the dropdown is still open + // INPUT_FLAG_DROPDOWN_STAY_OPEN is already set if this happens + INPUT_FLAG_DROPDOWN_MOUSE_UP = (1 << 2), - INPUT_FLAG_TOOL_ACTIVE = (1 << 3), + INPUT_FLAG_TOOL_ACTIVE = (1 << 3), - // Left click on a viewport - INPUT_FLAG_4 = (1 << 4), + // Left click on a viewport + INPUT_FLAG_4 = (1 << 4), - INPUT_FLAG_5 = (1 << 5), + INPUT_FLAG_5 = (1 << 5), - // Some of the map tools (clear, footpath, scenery) - // never read as far as I know. - INPUT_FLAG_6 = (1 << 6), + // Some of the map tools (clear, footpath, scenery) + // never read as far as I know. + INPUT_FLAG_6 = (1 << 6), - INPUT_FLAG_VIEWPORT_SCROLLING = (1 << 7) -}; + INPUT_FLAG_VIEWPORT_SCROLLING = (1 << 7) + }; -enum class InputState -{ - Reset, - Normal, - WidgetPressed, - PositioningWindow, - ViewportRight, - DropdownActive, - ViewportLeft, - ScrollLeft, - Resizing, - ScrollRight -}; + enum class InputState + { + Reset, + Normal, + WidgetPressed, + PositioningWindow, + ViewportRight, + DropdownActive, + ViewportLeft, + ScrollLeft, + Resizing, + ScrollRight + }; -extern WidgetRef gHoverWidget; -extern WidgetRef gPressedWidget; + extern WidgetRef gHoverWidget; + extern WidgetRef gPressedWidget; -extern uint32_t gTooltipCloseTimeout; -extern WidgetRef gTooltipWidget; -extern ScreenCoordsXY gTooltipCursor; + extern uint32_t gTooltipCloseTimeout; + extern WidgetRef gTooltipWidget; + extern ScreenCoordsXY gTooltipCursor; -// TODO: Move to openrct2-ui and make static again -extern InputState _inputState; -extern uint8_t _inputFlags; -extern uint32_t _tooltipNotShownTimeout; + // TODO: Move to openrct2-ui and make static again + extern InputState _inputState; + extern uint8_t _inputFlags; + extern uint32_t _tooltipNotShownTimeout; -void TitleHandleKeyboardInput(); -void GameHandleKeyboardInput(); + void TitleHandleKeyboardInput(); + void GameHandleKeyboardInput(); -void InputSetFlag(INPUT_FLAGS flag, bool on); -bool InputTestFlag(INPUT_FLAGS flag); -void InputResetFlags(); + void InputSetFlag(INPUT_FLAGS flag, bool on); + bool InputTestFlag(INPUT_FLAGS flag); + void InputResetFlags(); -void InputSetState(InputState state); -InputState InputGetState(); + void InputSetState(InputState state); + InputState InputGetState(); -void ResetTooltipNotShown(); + void ResetTooltipNotShown(); +} // namespace OpenRCT2 diff --git a/src/openrct2/interface/Viewport.cpp b/src/openrct2/interface/Viewport.cpp index a250b66b40..83acf71165 100644 --- a/src/openrct2/interface/Viewport.cpp +++ b/src/openrct2/interface/Viewport.cpp @@ -50,1974 +50,1980 @@ #include #include -using namespace OpenRCT2; -using namespace OpenRCT2::Numerics; - -enum : uint8_t +namespace OpenRCT2 { - IMAGE_TYPE_DEFAULT = 0, - IMAGE_TYPE_REMAP = (1 << 1), - IMAGE_TYPE_TRANSPARENT = (1 << 2), -}; + using namespace OpenRCT2::Numerics; -uint8_t gShowGridLinesRefCount; -uint8_t gShowLandRightsRefCount; -uint8_t gShowConstructionRightsRefCount; - -static std::list _viewports; -Viewport* g_music_tracking_viewport; - -static std::unique_ptr _paintJobs; -static std::vector _paintColumns; - -InteractionInfo::InteractionInfo(const PaintStruct* ps) - : Loc(ps->MapPos) - , Element(ps->Element) - , Entity(ps->Entity) - , interactionType(ps->InteractionItem) -{ -} - -static void ViewportPaintWeatherGloom(DrawPixelInfo& dpi); -static void ViewportPaint(const Viewport* viewport, DrawPixelInfo& dpi); -static void ViewportUpdateFollowSprite(WindowBase* window); -static void ViewportUpdateSmartFollowEntity(WindowBase* window); -static void ViewportUpdateSmartFollowStaff(WindowBase* window, const Staff& peep); -static void ViewportUpdateSmartFollowVehicle(WindowBase* window); -static void ViewportInvalidate(const Viewport* viewport, const ScreenRect& screenRect); - -/** - * This is not a viewport function. It is used to setup many variables for - * multiple things. - * rct2: 0x006E6EAC - */ -void ViewportInitAll() -{ - if (!gOpenRCT2NoGraphics) + enum : uint8_t + { + IMAGE_TYPE_DEFAULT = 0, + IMAGE_TYPE_REMAP = (1 << 1), + IMAGE_TYPE_TRANSPARENT = (1 << 2), + }; + + uint8_t gShowGridLinesRefCount; + uint8_t gShowLandRightsRefCount; + uint8_t gShowConstructionRightsRefCount; + + static std::list _viewports; + Viewport* g_music_tracking_viewport; + + static std::unique_ptr _paintJobs; + static std::vector _paintColumns; + + InteractionInfo::InteractionInfo(const PaintStruct* ps) + : Loc(ps->MapPos) + , Element(ps->Element) + , Entity(ps->Entity) + , interactionType(ps->InteractionItem) { - ColoursInitMaps(); } - WindowInitAll(); + static void ViewportPaintWeatherGloom(DrawPixelInfo& dpi); + static void ViewportPaint(const Viewport* viewport, DrawPixelInfo& dpi); + static void ViewportUpdateFollowSprite(WindowBase* window); + static void ViewportUpdateSmartFollowEntity(WindowBase* window); + static void ViewportUpdateSmartFollowStaff(WindowBase* window, const Staff& peep); + static void ViewportUpdateSmartFollowVehicle(WindowBase* window); + static void ViewportInvalidate(const Viewport* viewport, const ScreenRect& screenRect); - // ? - InputResetFlags(); - InputSetState(InputState::Reset); - gPressedWidget.window_classification = WindowClass::Null; - gPickupPeepImage = ImageId(); - ResetTooltipNotShown(); - gMapSelectFlags = 0; - ClearPatrolAreaToRender(); - TextinputCancel(); -} - -/** - * Converts between 3d point of a sprite to 2d coordinates for centring on that - * sprite - * rct2: 0x006EB0C1 - * x : ax - * y : bx - * z : cx - * out_x : ax - * out_y : bx - */ -std::optional centre_2d_coordinates(const CoordsXYZ& loc, Viewport* viewport) -{ - // If the start location was invalid - // propagate the invalid location to the output. - // This fixes a bug that caused the game to enter an infinite loop. - if (loc.IsNull()) + /** + * This is not a viewport function. It is used to setup many variables for + * multiple things. + * rct2: 0x006E6EAC + */ + void ViewportInitAll() { - return std::nullopt; + if (!gOpenRCT2NoGraphics) + { + ColoursInitMaps(); + } + + WindowInitAll(); + + // ? + InputResetFlags(); + InputSetState(InputState::Reset); + gPressedWidget.window_classification = WindowClass::Null; + gPickupPeepImage = ImageId(); + ResetTooltipNotShown(); + gMapSelectFlags = 0; + ClearPatrolAreaToRender(); + TextinputCancel(); } - auto screenCoord = Translate3DTo2DWithZ(viewport->rotation, loc); - screenCoord.x -= viewport->ViewWidth() / 2; - screenCoord.y -= viewport->ViewHeight() / 2; - return { screenCoord }; -} + /** + * Converts between 3d point of a sprite to 2d coordinates for centring on that + * sprite + * rct2: 0x006EB0C1 + * x : ax + * y : bx + * z : cx + * out_x : ax + * out_y : bx + */ + std::optional centre_2d_coordinates(const CoordsXYZ& loc, Viewport* viewport) + { + // If the start location was invalid + // propagate the invalid location to the output. + // This fixes a bug that caused the game to enter an infinite loop. + if (loc.IsNull()) + { + return std::nullopt; + } -CoordsXYZ Focus::GetPos() const -{ - return std::visit( - [](auto&& arg) { - using T = std::decay_t; - if constexpr (std::is_same_v) - return arg; - else if constexpr (std::is_same_v) - { - auto* centreEntity = GetEntity(arg); - if (centreEntity != nullptr) + auto screenCoord = Translate3DTo2DWithZ(viewport->rotation, loc); + screenCoord.x -= viewport->ViewWidth() / 2; + screenCoord.y -= viewport->ViewHeight() / 2; + return { screenCoord }; + } + + CoordsXYZ Focus::GetPos() const + { + return std::visit( + [](auto&& arg) { + using T = std::decay_t; + if constexpr (std::is_same_v) + return arg; + else if constexpr (std::is_same_v) { - return CoordsXYZ{ centreEntity->x, centreEntity->y, centreEntity->z }; + auto* centreEntity = GetEntity(arg); + if (centreEntity != nullptr) + { + return CoordsXYZ{ centreEntity->x, centreEntity->y, centreEntity->z }; + } + else + { + LOG_ERROR("Invalid entity for focus."); + return CoordsXYZ{}; + } } - else - { - LOG_ERROR("Invalid entity for focus."); - return CoordsXYZ{}; - } - } - }, - data); -} - -/** - * Viewport will look at sprite or at coordinates as specified in flags 0b_1X - * for sprite 0b_0X for coordinates - * - * rct2: 0x006EB009 - * x: ax - * y: eax (top 16) - * width: bx - * height: ebx (top 16) - * zoom: cl (8 bits) - * centre_x: edx lower 16 bits - * centre_y: edx upper 16 bits - * centre_z: ecx upper 16 bits - * sprite: edx lower 16 bits - * flags: edx top most 2 bits 0b_X1 for zoom clear see below for 2nd bit. - * w: esi - */ -void ViewportCreate(WindowBase* w, const ScreenCoordsXY& screenCoords, int32_t width, int32_t height, const Focus& focus) -{ - Viewport* viewport = nullptr; - if (_viewports.size() >= kMaxViewportCount) - { - LOG_ERROR("No more viewport slots left to allocate."); - return; + }, + data); } - auto itViewport = _viewports.insert(_viewports.end(), Viewport{}); - - viewport = &*itViewport; - viewport->pos = screenCoords; - viewport->width = width; - viewport->height = height; - - const auto zoom = focus.zoom; - viewport->zoom = zoom; - viewport->flags = 0; - viewport->rotation = GetCurrentRotation(); - - if (Config::Get().general.AlwaysShowGridlines) - viewport->flags |= VIEWPORT_FLAG_GRIDLINES; - w->viewport = viewport; - - CoordsXYZ centrePos = focus.GetPos(); - w->viewport_target_sprite = std::visit( - [](auto&& arg) { - using T = std::decay_t; - if constexpr (std::is_same_v) - return EntityId::GetNull(); - else if constexpr (std::is_same_v) - return arg; - }, - focus.data); - - auto centreLoc = centre_2d_coordinates(centrePos, viewport); - if (!centreLoc.has_value()) + /** + * Viewport will look at sprite or at coordinates as specified in flags 0b_1X + * for sprite 0b_0X for coordinates + * + * rct2: 0x006EB009 + * x: ax + * y: eax (top 16) + * width: bx + * height: ebx (top 16) + * zoom: cl (8 bits) + * centre_x: edx lower 16 bits + * centre_y: edx upper 16 bits + * centre_z: ecx upper 16 bits + * sprite: edx lower 16 bits + * flags: edx top most 2 bits 0b_X1 for zoom clear see below for 2nd bit. + * w: esi + */ + void ViewportCreate(WindowBase* w, const ScreenCoordsXY& screenCoords, int32_t width, int32_t height, const Focus& focus) { - LOG_ERROR("Invalid location for viewport."); - return; - } - w->savedViewPos = *centreLoc; - viewport->viewPos = *centreLoc; -} - -void ViewportRemove(Viewport* viewport) -{ - auto it = std::find_if(_viewports.begin(), _viewports.end(), [viewport](const auto& vp) { return &vp == viewport; }); - if (it == _viewports.end()) - { - LOG_ERROR("Unable to remove viewport: %p", viewport); - return; - } - _viewports.erase(it); -} - -static Viewport* ViewportGetMain() -{ - auto mainWindow = WindowGetMain(); - if (mainWindow == nullptr) - { - return nullptr; - } - return mainWindow->viewport; -} - -void ViewportsInvalidate(int32_t x, int32_t y, int32_t z0, int32_t z1, ZoomLevel maxZoom) -{ - for (auto& vp : _viewports) - { - if (maxZoom == ZoomLevel{ -1 } || vp.zoom <= ZoomLevel{ maxZoom }) + Viewport* viewport = nullptr; + if (_viewports.size() >= kMaxViewportCount) { - int32_t x1, y1, x2, y2; - - x += 16; - y += 16; - auto screenCoord = Translate3DTo2DWithZ(vp.rotation, CoordsXYZ{ x, y, 0 }); - - x1 = screenCoord.x - 32; - y1 = screenCoord.y - 32 - z1; - x2 = screenCoord.x + 32; - y2 = screenCoord.y + 32 - z0; - - ViewportInvalidate(&vp, ScreenRect{ { x1, y1 }, { x2, y2 } }); - } - } -} - -void ViewportsInvalidate(const CoordsXYZ& pos, int32_t width, int32_t minHeight, int32_t maxHeight, ZoomLevel maxZoom) -{ - for (auto& vp : _viewports) - { - if (maxZoom == ZoomLevel{ -1 } || vp.zoom <= ZoomLevel{ maxZoom }) - { - auto screenCoords = Translate3DTo2DWithZ(vp.rotation, pos); - auto screenPos = ScreenRect( - screenCoords - ScreenCoordsXY{ width, minHeight }, screenCoords + ScreenCoordsXY{ width, maxHeight }); - - ViewportInvalidate(&vp, screenPos); - } - } -} - -void ViewportsInvalidate(const ScreenRect& screenRect, ZoomLevel maxZoom) -{ - for (auto& vp : _viewports) - { - if (maxZoom == ZoomLevel{ -1 } || vp.zoom <= ZoomLevel{ maxZoom }) - { - ViewportInvalidate(&vp, screenRect); - } - } -} - -/** - * - * rct2: 0x00689174 - * edx is assumed to be (and always is) the current rotation, so it is not - * needed as parameter. - */ -CoordsXYZ ViewportAdjustForMapHeight(const ScreenCoordsXY& startCoords, uint8_t rotation) -{ - int32_t height = 0; - - CoordsXY pos{}; - for (int32_t i = 0; i < 6; i++) - { - pos = ViewportPosToMapPos(startCoords, height, rotation); - height = TileElementHeight(pos); - - // HACK: This is to prevent the x and y values being set to values outside - // of the map. This can happen when the height is larger than the map size. - auto mapSizeMinus2 = GetMapSizeMinus2(); - if (pos.x > mapSizeMinus2.x && pos.y > mapSizeMinus2.y) - { - static constexpr CoordsXY corr[] = { - { -1, -1 }, - { 1, -1 }, - { 1, 1 }, - { -1, 1 }, - }; - pos.x += corr[rotation].x * height; - pos.y += corr[rotation].y * height; - } - } - - return { pos, height }; -} - -/* - * rct2: 0x006E7FF3 - */ -static void ViewportRedrawAfterShift( - DrawPixelInfo& dpi, WindowBase* window, const WindowBase* originalWindow, const ScreenCoordsXY shift, - const ScreenRect& drawRect) -{ - // sub-divide by intersecting windows - if (window != nullptr) - { - // skip current window and non-intersecting windows - if (window == originalWindow || drawRect.GetRight() <= window->windowPos.x - || drawRect.GetLeft() >= window->windowPos.x + window->width || drawRect.GetBottom() <= window->windowPos.y - || drawRect.GetTop() >= window->windowPos.y + window->height) - { - auto itWindowPos = WindowGetIterator(window); - auto itNextWindow = itWindowPos != g_window_list.end() ? std::next(itWindowPos) : g_window_list.end(); - ViewportRedrawAfterShift( - dpi, itNextWindow == g_window_list.end() ? nullptr : itNextWindow->get(), originalWindow, shift, drawRect); + LOG_ERROR("No more viewport slots left to allocate."); return; } - if (drawRect.GetLeft() < window->windowPos.x) - { - ScreenRect leftRect = { drawRect.Point1, { window->windowPos.x, drawRect.GetBottom() } }; - ViewportRedrawAfterShift(dpi, window, originalWindow, shift, leftRect); + auto itViewport = _viewports.insert(_viewports.end(), Viewport{}); - ScreenRect rightRect = { { window->windowPos.x, drawRect.GetTop() }, drawRect.Point2 }; - ViewportRedrawAfterShift(dpi, window, originalWindow, shift, rightRect); + viewport = &*itViewport; + viewport->pos = screenCoords; + viewport->width = width; + viewport->height = height; + + const auto zoom = focus.zoom; + viewport->zoom = zoom; + viewport->flags = 0; + viewport->rotation = GetCurrentRotation(); + + if (Config::Get().general.AlwaysShowGridlines) + viewport->flags |= VIEWPORT_FLAG_GRIDLINES; + w->viewport = viewport; + + CoordsXYZ centrePos = focus.GetPos(); + w->viewport_target_sprite = std::visit( + [](auto&& arg) { + using T = std::decay_t; + if constexpr (std::is_same_v) + return EntityId::GetNull(); + else if constexpr (std::is_same_v) + return arg; + }, + focus.data); + + auto centreLoc = centre_2d_coordinates(centrePos, viewport); + if (!centreLoc.has_value()) + { + LOG_ERROR("Invalid location for viewport."); + return; } - else if (drawRect.GetRight() > window->windowPos.x + window->width) - { - ScreenRect leftRect = { drawRect.Point1, { window->windowPos.x + window->width, drawRect.GetBottom() } }; - ViewportRedrawAfterShift(dpi, window, originalWindow, shift, leftRect); + w->savedViewPos = *centreLoc; + viewport->viewPos = *centreLoc; + } - ScreenRect rightRect = { { window->windowPos.x + window->width, drawRect.GetTop() }, drawRect.Point2 }; - ViewportRedrawAfterShift(dpi, window, originalWindow, shift, rightRect); + void ViewportRemove(Viewport* viewport) + { + auto it = std::find_if(_viewports.begin(), _viewports.end(), [viewport](const auto& vp) { return &vp == viewport; }); + if (it == _viewports.end()) + { + LOG_ERROR("Unable to remove viewport: %p", viewport); + return; } - else if (drawRect.GetTop() < window->windowPos.y) - { - ScreenRect topRect = { drawRect.Point1, { drawRect.GetRight(), window->windowPos.y } }; - ViewportRedrawAfterShift(dpi, window, originalWindow, shift, topRect); + _viewports.erase(it); + } - ScreenRect bottomRect = { { drawRect.GetLeft(), window->windowPos.y }, drawRect.Point2 }; - ViewportRedrawAfterShift(dpi, window, originalWindow, shift, bottomRect); + static Viewport* ViewportGetMain() + { + auto mainWindow = WindowGetMain(); + if (mainWindow == nullptr) + { + return nullptr; } - else if (drawRect.GetBottom() > window->windowPos.y + window->height) - { - ScreenRect topRect = { drawRect.Point1, { drawRect.GetRight(), window->windowPos.y + window->height } }; - ViewportRedrawAfterShift(dpi, window, originalWindow, shift, topRect); + return mainWindow->viewport; + } - ScreenRect bottomRect = { { drawRect.GetLeft(), window->windowPos.y + window->height }, drawRect.Point2 }; - ViewportRedrawAfterShift(dpi, window, originalWindow, shift, bottomRect); + void ViewportsInvalidate(int32_t x, int32_t y, int32_t z0, int32_t z1, ZoomLevel maxZoom) + { + for (auto& vp : _viewports) + { + if (maxZoom == ZoomLevel{ -1 } || vp.zoom <= ZoomLevel{ maxZoom }) + { + int32_t x1, y1, x2, y2; + + x += 16; + y += 16; + auto screenCoord = Translate3DTo2DWithZ(vp.rotation, CoordsXYZ{ x, y, 0 }); + + x1 = screenCoord.x - 32; + y1 = screenCoord.y - 32 - z1; + x2 = screenCoord.x + 32; + y2 = screenCoord.y + 32 - z0; + + ViewportInvalidate(&vp, ScreenRect{ { x1, y1 }, { x2, y2 } }); + } } } - else + + void ViewportsInvalidate(const CoordsXYZ& pos, int32_t width, int32_t minHeight, int32_t maxHeight, ZoomLevel maxZoom) { - auto left = drawRect.GetLeft(); - auto right = drawRect.GetRight(); - auto top = drawRect.GetTop(); - auto bottom = drawRect.GetBottom(); - - // if moved more than the draw rectangle size - if (abs(shift.x) < drawRect.GetWidth() && abs(shift.y) < drawRect.GetHeight()) + for (auto& vp : _viewports) { - // update whole block ? - DrawingEngineCopyRect( - drawRect.GetLeft(), drawRect.GetTop(), drawRect.GetWidth(), drawRect.GetHeight(), shift.x, shift.y); + if (maxZoom == ZoomLevel{ -1 } || vp.zoom <= ZoomLevel{ maxZoom }) + { + auto screenCoords = Translate3DTo2DWithZ(vp.rotation, pos); + auto screenPos = ScreenRect( + screenCoords - ScreenCoordsXY{ width, minHeight }, screenCoords + ScreenCoordsXY{ width, maxHeight }); - if (shift.x > 0) - { - // draw left - auto _right = left + shift.x; - WindowDrawAll(dpi, left, top, _right, bottom); - left += shift.x; + ViewportInvalidate(&vp, screenPos); } - else if (shift.x < 0) + } + } + + void ViewportsInvalidate(const ScreenRect& screenRect, ZoomLevel maxZoom) + { + for (auto& vp : _viewports) + { + if (maxZoom == ZoomLevel{ -1 } || vp.zoom <= ZoomLevel{ maxZoom }) { - // draw right - auto _left = right + shift.x; - WindowDrawAll(dpi, _left, top, right, bottom); - right += shift.x; + ViewportInvalidate(&vp, screenRect); + } + } + } + + /** + * + * rct2: 0x00689174 + * edx is assumed to be (and always is) the current rotation, so it is not + * needed as parameter. + */ + CoordsXYZ ViewportAdjustForMapHeight(const ScreenCoordsXY& startCoords, uint8_t rotation) + { + int32_t height = 0; + + CoordsXY pos{}; + for (int32_t i = 0; i < 6; i++) + { + pos = ViewportPosToMapPos(startCoords, height, rotation); + height = TileElementHeight(pos); + + // HACK: This is to prevent the x and y values being set to values outside + // of the map. This can happen when the height is larger than the map size. + auto mapSizeMinus2 = GetMapSizeMinus2(); + if (pos.x > mapSizeMinus2.x && pos.y > mapSizeMinus2.y) + { + static constexpr CoordsXY corr[] = { + { -1, -1 }, + { 1, -1 }, + { 1, 1 }, + { -1, 1 }, + }; + pos.x += corr[rotation].x * height; + pos.y += corr[rotation].y * height; + } + } + + return { pos, height }; + } + + /* + * rct2: 0x006E7FF3 + */ + static void ViewportRedrawAfterShift( + DrawPixelInfo& dpi, WindowBase* window, const WindowBase* originalWindow, const ScreenCoordsXY shift, + const ScreenRect& drawRect) + { + // sub-divide by intersecting windows + if (window != nullptr) + { + // skip current window and non-intersecting windows + if (window == originalWindow || drawRect.GetRight() <= window->windowPos.x + || drawRect.GetLeft() >= window->windowPos.x + window->width || drawRect.GetBottom() <= window->windowPos.y + || drawRect.GetTop() >= window->windowPos.y + window->height) + { + auto itWindowPos = WindowGetIterator(window); + auto itNextWindow = itWindowPos != g_window_list.end() ? std::next(itWindowPos) : g_window_list.end(); + ViewportRedrawAfterShift( + dpi, itNextWindow == g_window_list.end() ? nullptr : itNextWindow->get(), originalWindow, shift, drawRect); + return; } - if (shift.y > 0) + if (drawRect.GetLeft() < window->windowPos.x) { - // draw top - bottom = top + shift.y; - WindowDrawAll(dpi, left, top, right, bottom); + ScreenRect leftRect = { drawRect.Point1, { window->windowPos.x, drawRect.GetBottom() } }; + ViewportRedrawAfterShift(dpi, window, originalWindow, shift, leftRect); + + ScreenRect rightRect = { { window->windowPos.x, drawRect.GetTop() }, drawRect.Point2 }; + ViewportRedrawAfterShift(dpi, window, originalWindow, shift, rightRect); } - else if (shift.y < 0) + else if (drawRect.GetRight() > window->windowPos.x + window->width) { - // draw bottom - top = bottom + shift.y; - WindowDrawAll(dpi, left, top, right, bottom); + ScreenRect leftRect = { drawRect.Point1, { window->windowPos.x + window->width, drawRect.GetBottom() } }; + ViewportRedrawAfterShift(dpi, window, originalWindow, shift, leftRect); + + ScreenRect rightRect = { { window->windowPos.x + window->width, drawRect.GetTop() }, drawRect.Point2 }; + ViewportRedrawAfterShift(dpi, window, originalWindow, shift, rightRect); + } + else if (drawRect.GetTop() < window->windowPos.y) + { + ScreenRect topRect = { drawRect.Point1, { drawRect.GetRight(), window->windowPos.y } }; + ViewportRedrawAfterShift(dpi, window, originalWindow, shift, topRect); + + ScreenRect bottomRect = { { drawRect.GetLeft(), window->windowPos.y }, drawRect.Point2 }; + ViewportRedrawAfterShift(dpi, window, originalWindow, shift, bottomRect); + } + else if (drawRect.GetBottom() > window->windowPos.y + window->height) + { + ScreenRect topRect = { drawRect.Point1, { drawRect.GetRight(), window->windowPos.y + window->height } }; + ViewportRedrawAfterShift(dpi, window, originalWindow, shift, topRect); + + ScreenRect bottomRect = { { drawRect.GetLeft(), window->windowPos.y + window->height }, drawRect.Point2 }; + ViewportRedrawAfterShift(dpi, window, originalWindow, shift, bottomRect); } } else { - // redraw whole draw rectangle - WindowDrawAll(dpi, left, top, right, bottom); + auto left = drawRect.GetLeft(); + auto right = drawRect.GetRight(); + auto top = drawRect.GetTop(); + auto bottom = drawRect.GetBottom(); + + // if moved more than the draw rectangle size + if (abs(shift.x) < drawRect.GetWidth() && abs(shift.y) < drawRect.GetHeight()) + { + // update whole block ? + DrawingEngineCopyRect( + drawRect.GetLeft(), drawRect.GetTop(), drawRect.GetWidth(), drawRect.GetHeight(), shift.x, shift.y); + + if (shift.x > 0) + { + // draw left + auto _right = left + shift.x; + WindowDrawAll(dpi, left, top, _right, bottom); + left += shift.x; + } + else if (shift.x < 0) + { + // draw right + auto _left = right + shift.x; + WindowDrawAll(dpi, _left, top, right, bottom); + right += shift.x; + } + + if (shift.y > 0) + { + // draw top + bottom = top + shift.y; + WindowDrawAll(dpi, left, top, right, bottom); + } + else if (shift.y < 0) + { + // draw bottom + top = bottom + shift.y; + WindowDrawAll(dpi, left, top, right, bottom); + } + } + else + { + // redraw whole draw rectangle + WindowDrawAll(dpi, left, top, right, bottom); + } } } -} -static void ViewportShiftPixels(DrawPixelInfo& dpi, WindowBase* window, Viewport* viewport, int32_t x_diff, int32_t y_diff) -{ - // This loop redraws all parts covered by transparent windows. - auto it = WindowGetIterator(window); - for (; it != g_window_list.end(); it++) + static void ViewportShiftPixels(DrawPixelInfo& dpi, WindowBase* window, Viewport* viewport, int32_t x_diff, int32_t y_diff) { - auto w = it->get(); - if (!(w->flags & WF_TRANSPARENT)) - continue; - if (w->viewport == viewport) - continue; + // This loop redraws all parts covered by transparent windows. + auto it = WindowGetIterator(window); + for (; it != g_window_list.end(); it++) + { + auto w = it->get(); + if (!(w->flags & WF_TRANSPARENT)) + continue; + if (w->viewport == viewport) + continue; - if (viewport->pos.x + viewport->width <= w->windowPos.x) - continue; - if (w->windowPos.x + w->width <= viewport->pos.x) - continue; + if (viewport->pos.x + viewport->width <= w->windowPos.x) + continue; + if (w->windowPos.x + w->width <= viewport->pos.x) + continue; - if (viewport->pos.y + viewport->height <= w->windowPos.y) - continue; - if (w->windowPos.y + w->height <= viewport->pos.y) - continue; + if (viewport->pos.y + viewport->height <= w->windowPos.y) + continue; + if (w->windowPos.y + w->height <= viewport->pos.y) + continue; - auto left = w->windowPos.x; - auto right = w->windowPos.x + w->width; - auto top = w->windowPos.y; - auto bottom = w->windowPos.y + w->height; + auto left = w->windowPos.x; + auto right = w->windowPos.x + w->width; + auto top = w->windowPos.y; + auto bottom = w->windowPos.y + w->height; - if (left < viewport->pos.x) - left = viewport->pos.x; - if (right > viewport->pos.x + viewport->width) - right = viewport->pos.x + viewport->width; + if (left < viewport->pos.x) + left = viewport->pos.x; + if (right > viewport->pos.x + viewport->width) + right = viewport->pos.x + viewport->width; - if (top < viewport->pos.y) - top = viewport->pos.y; - if (bottom > viewport->pos.y + viewport->height) - bottom = viewport->pos.y + viewport->height; + if (top < viewport->pos.y) + top = viewport->pos.y; + if (bottom > viewport->pos.y + viewport->height) + bottom = viewport->pos.y + viewport->height; - if (left >= right) - continue; - if (top >= bottom) - continue; + if (left >= right) + continue; + if (top >= bottom) + continue; - WindowDrawAll(dpi, left, top, right, bottom); + WindowDrawAll(dpi, left, top, right, bottom); + } + + ViewportRedrawAfterShift( + dpi, window, window, { x_diff, y_diff }, + { viewport->pos, { viewport->pos.x + viewport->width, viewport->pos.y + viewport->height } }); } - ViewportRedrawAfterShift( - dpi, window, window, { x_diff, y_diff }, - { viewport->pos, { viewport->pos.x + viewport->width, viewport->pos.y + viewport->height } }); -} - -static void ViewportMove(const ScreenCoordsXY& coords, WindowBase* w, Viewport* viewport) -{ - auto zoom = viewport->zoom; - - // Note: do not do the subtraction and then divide! - // Note: Due to arithmetic shift != /zoom a shift will have to be used - // hopefully when 0x006E7FF3 is finished this can be converted to /zoom. - auto x_diff = viewport->zoom.ApplyInversedTo(viewport->viewPos.x) - viewport->zoom.ApplyInversedTo(coords.x); - auto y_diff = viewport->zoom.ApplyInversedTo(viewport->viewPos.y) - viewport->zoom.ApplyInversedTo(coords.y); - - viewport->viewPos = coords; - - // If no change in viewing area - if ((!x_diff) && (!y_diff)) - return; - - if (w->flags & WF_7) + static void ViewportMove(const ScreenCoordsXY& coords, WindowBase* w, Viewport* viewport) { - int32_t left = std::max(viewport->pos.x, 0); - int32_t top = std::max(viewport->pos.y, 0); - int32_t right = std::min(viewport->pos.x + viewport->width, ContextGetWidth()); - int32_t bottom = std::min(viewport->pos.y + viewport->height, ContextGetHeight()); + auto zoom = viewport->zoom; - if (left >= right) + // Note: do not do the subtraction and then divide! + // Note: Due to arithmetic shift != /zoom a shift will have to be used + // hopefully when 0x006E7FF3 is finished this can be converted to /zoom. + auto x_diff = viewport->zoom.ApplyInversedTo(viewport->viewPos.x) - viewport->zoom.ApplyInversedTo(coords.x); + auto y_diff = viewport->zoom.ApplyInversedTo(viewport->viewPos.y) - viewport->zoom.ApplyInversedTo(coords.y); + + viewport->viewPos = coords; + + // If no change in viewing area + if ((!x_diff) && (!y_diff)) return; - if (top >= bottom) + + if (w->flags & WF_7) + { + int32_t left = std::max(viewport->pos.x, 0); + int32_t top = std::max(viewport->pos.y, 0); + int32_t right = std::min(viewport->pos.x + viewport->width, ContextGetWidth()); + int32_t bottom = std::min(viewport->pos.y + viewport->height, ContextGetHeight()); + + if (left >= right) + return; + if (top >= bottom) + return; + + if (DrawingEngineHasDirtyOptimisations()) + { + DrawPixelInfo& dpi = DrawingEngineGetDpi(); + WindowDrawAll(dpi, left, top, right, bottom); + return; + } + } + + Viewport view_copy = *viewport; + + if (viewport->pos.x < 0) + { + viewport->width += viewport->pos.x; + viewport->viewPos.x -= zoom.ApplyTo(viewport->pos.x); + viewport->pos.x = 0; + } + + int32_t eax = viewport->pos.x + viewport->width - ContextGetWidth(); + if (eax > 0) + { + viewport->width -= eax; + } + + if (viewport->width <= 0) + { + *viewport = view_copy; return; + } + + if (viewport->pos.y < 0) + { + viewport->height += viewport->pos.y; + viewport->viewPos.y -= zoom.ApplyTo(viewport->pos.y); + viewport->pos.y = 0; + } + + eax = viewport->pos.y + viewport->height - ContextGetHeight(); + if (eax > 0) + { + viewport->height -= eax; + } + + if (viewport->height <= 0) + { + *viewport = view_copy; + return; + } if (DrawingEngineHasDirtyOptimisations()) { DrawPixelInfo& dpi = DrawingEngineGetDpi(); - WindowDrawAll(dpi, left, top, right, bottom); + ViewportShiftPixels(dpi, w, viewport, x_diff, y_diff); + } + + *viewport = view_copy; + } + + // rct2: 0x006E7A15 + static void ViewportSetUndergroundFlag(int32_t underground, WindowBase* window, Viewport* viewport) + { + if (window->classification != WindowClass::MainWindow + || (window->classification == WindowClass::MainWindow && !window->viewport_smart_follow_sprite.IsNull())) + { + if (!underground) + { + int32_t bit = viewport->flags & VIEWPORT_FLAG_UNDERGROUND_INSIDE; + viewport->flags &= ~VIEWPORT_FLAG_UNDERGROUND_INSIDE; + if (!bit) + return; + } + else + { + int32_t bit = viewport->flags & VIEWPORT_FLAG_UNDERGROUND_INSIDE; + viewport->flags |= VIEWPORT_FLAG_UNDERGROUND_INSIDE; + if (bit) + return; + } + window->Invalidate(); + } + } + + /** + * + * rct2: 0x006E7A3A + */ + void ViewportUpdatePosition(WindowBase* window) + { + window->OnResize(); + + Viewport* viewport = window->viewport; + if (viewport == nullptr) return; - } - } - Viewport view_copy = *viewport; - - if (viewport->pos.x < 0) - { - viewport->width += viewport->pos.x; - viewport->viewPos.x -= zoom.ApplyTo(viewport->pos.x); - viewport->pos.x = 0; - } - - int32_t eax = viewport->pos.x + viewport->width - ContextGetWidth(); - if (eax > 0) - { - viewport->width -= eax; - } - - if (viewport->width <= 0) - { - *viewport = view_copy; - return; - } - - if (viewport->pos.y < 0) - { - viewport->height += viewport->pos.y; - viewport->viewPos.y -= zoom.ApplyTo(viewport->pos.y); - viewport->pos.y = 0; - } - - eax = viewport->pos.y + viewport->height - ContextGetHeight(); - if (eax > 0) - { - viewport->height -= eax; - } - - if (viewport->height <= 0) - { - *viewport = view_copy; - return; - } - - if (DrawingEngineHasDirtyOptimisations()) - { - DrawPixelInfo& dpi = DrawingEngineGetDpi(); - ViewportShiftPixels(dpi, w, viewport, x_diff, y_diff); - } - - *viewport = view_copy; -} - -// rct2: 0x006E7A15 -static void ViewportSetUndergroundFlag(int32_t underground, WindowBase* window, Viewport* viewport) -{ - if (window->classification != WindowClass::MainWindow - || (window->classification == WindowClass::MainWindow && !window->viewport_smart_follow_sprite.IsNull())) - { - if (!underground) + if (!window->viewport_smart_follow_sprite.IsNull()) { - int32_t bit = viewport->flags & VIEWPORT_FLAG_UNDERGROUND_INSIDE; - viewport->flags &= ~VIEWPORT_FLAG_UNDERGROUND_INSIDE; - if (!bit) - return; + ViewportUpdateSmartFollowEntity(window); } - else - { - int32_t bit = viewport->flags & VIEWPORT_FLAG_UNDERGROUND_INSIDE; - viewport->flags |= VIEWPORT_FLAG_UNDERGROUND_INSIDE; - if (bit) - return; - } - window->Invalidate(); - } -} - -/** - * - * rct2: 0x006E7A3A - */ -void ViewportUpdatePosition(WindowBase* window) -{ - window->OnResize(); - - Viewport* viewport = window->viewport; - if (viewport == nullptr) - return; - - if (!window->viewport_smart_follow_sprite.IsNull()) - { - ViewportUpdateSmartFollowEntity(window); - } - - if (!window->viewport_target_sprite.IsNull()) - { - ViewportUpdateFollowSprite(window); - return; - } - - ViewportSetUndergroundFlag(0, window, viewport); - - auto viewportMidPoint = ScreenCoordsXY{ window->savedViewPos.x + viewport->ViewWidth() / 2, - window->savedViewPos.y + viewport->ViewHeight() / 2 }; - - auto mapCoord = ViewportPosToMapPos(viewportMidPoint, 0, viewport->rotation); - - // Clamp to the map minimum value - int32_t at_map_edge = 0; - if (mapCoord.x < kMapMinimumXY) - { - mapCoord.x = kMapMinimumXY; - at_map_edge = 1; - } - if (mapCoord.y < kMapMinimumXY) - { - mapCoord.y = kMapMinimumXY; - at_map_edge = 1; - } - - // Clamp to the map maximum value (scenario specific) - auto mapSizeMinus2 = GetMapSizeMinus2(); - if (mapCoord.x > mapSizeMinus2.x) - { - mapCoord.x = mapSizeMinus2.x; - at_map_edge = 1; - } - if (mapCoord.y > mapSizeMinus2.y) - { - mapCoord.y = mapSizeMinus2.y; - at_map_edge = 1; - } - - if (at_map_edge) - { - auto centreLoc = centre_2d_coordinates({ mapCoord, 0 }, viewport); - if (centreLoc.has_value()) - { - window->savedViewPos = centreLoc.value(); - } - } - - auto windowCoords = window->savedViewPos; - if (window->flags & WF_SCROLLING_TO_LOCATION) - { - // Moves the viewport if focusing in on an item - uint8_t flags = 0; - windowCoords.x -= viewport->viewPos.x; - if (windowCoords.x < 0) - { - windowCoords.x = -windowCoords.x; - flags |= 1; - } - windowCoords.y -= viewport->viewPos.y; - if (windowCoords.y < 0) - { - windowCoords.y = -windowCoords.y; - flags |= 2; - } - windowCoords.x = (windowCoords.x + 7) / 8; - windowCoords.y = (windowCoords.y + 7) / 8; - - // If we are at the final zoom position - if (!windowCoords.x && !windowCoords.y) - { - window->flags &= ~WF_SCROLLING_TO_LOCATION; - } - if (flags & 1) - { - windowCoords.x = -windowCoords.x; - } - if (flags & 2) - { - windowCoords.y = -windowCoords.y; - } - windowCoords.x += viewport->viewPos.x; - windowCoords.y += viewport->viewPos.y; - } - - ViewportMove(windowCoords, window, viewport); -} - -void ViewportUpdateFollowSprite(WindowBase* window) -{ - if (!window->viewport_target_sprite.IsNull() && window->viewport != nullptr) - { - auto* sprite = GetEntity(window->viewport_target_sprite); - if (sprite == nullptr) + + if (!window->viewport_target_sprite.IsNull()) { + ViewportUpdateFollowSprite(window); return; } - if (!(gScreenFlags & SCREEN_FLAGS_TITLE_DEMO)) + ViewportSetUndergroundFlag(0, window, viewport); + + auto viewportMidPoint = ScreenCoordsXY{ window->savedViewPos.x + viewport->ViewWidth() / 2, + window->savedViewPos.y + viewport->ViewHeight() / 2 }; + + auto mapCoord = ViewportPosToMapPos(viewportMidPoint, 0, viewport->rotation); + + // Clamp to the map minimum value + int32_t at_map_edge = 0; + if (mapCoord.x < kMapMinimumXY) { - int32_t height = (TileElementHeight({ sprite->x, sprite->y }))-16; - int32_t underground = sprite->z < height; - ViewportSetUndergroundFlag(underground, window, window->viewport); + mapCoord.x = kMapMinimumXY; + at_map_edge = 1; + } + if (mapCoord.y < kMapMinimumXY) + { + mapCoord.y = kMapMinimumXY; + at_map_edge = 1; } - auto centreLoc = centre_2d_coordinates(sprite->GetLocation(), window->viewport); - if (centreLoc.has_value()) + // Clamp to the map maximum value (scenario specific) + auto mapSizeMinus2 = GetMapSizeMinus2(); + if (mapCoord.x > mapSizeMinus2.x) { - window->savedViewPos = *centreLoc; - ViewportMove(*centreLoc, window, window->viewport); + mapCoord.x = mapSizeMinus2.x; + at_map_edge = 1; } - } -} - -void ViewportUpdateSmartFollowEntity(WindowBase* window) -{ - auto entity = TryGetEntity(window->viewport_smart_follow_sprite); - if (entity == nullptr || entity->Type == EntityType::Null) - { - window->viewport_smart_follow_sprite = EntityId::GetNull(); - window->viewport_target_sprite = EntityId::GetNull(); - return; - } - - switch (entity->Type) - { - case EntityType::Vehicle: - ViewportUpdateSmartFollowVehicle(window); - break; - - case EntityType::Guest: + if (mapCoord.y > mapSizeMinus2.y) { - auto* guest = entity->As(); - if (guest == nullptr) + mapCoord.y = mapSizeMinus2.y; + at_map_edge = 1; + } + + if (at_map_edge) + { + auto centreLoc = centre_2d_coordinates({ mapCoord, 0 }, viewport); + if (centreLoc.has_value()) + { + window->savedViewPos = centreLoc.value(); + } + } + + auto windowCoords = window->savedViewPos; + if (window->flags & WF_SCROLLING_TO_LOCATION) + { + // Moves the viewport if focusing in on an item + uint8_t flags = 0; + windowCoords.x -= viewport->viewPos.x; + if (windowCoords.x < 0) + { + windowCoords.x = -windowCoords.x; + flags |= 1; + } + windowCoords.y -= viewport->viewPos.y; + if (windowCoords.y < 0) + { + windowCoords.y = -windowCoords.y; + flags |= 2; + } + windowCoords.x = (windowCoords.x + 7) / 8; + windowCoords.y = (windowCoords.y + 7) / 8; + + // If we are at the final zoom position + if (!windowCoords.x && !windowCoords.y) + { + window->flags &= ~WF_SCROLLING_TO_LOCATION; + } + if (flags & 1) + { + windowCoords.x = -windowCoords.x; + } + if (flags & 2) + { + windowCoords.y = -windowCoords.y; + } + windowCoords.x += viewport->viewPos.x; + windowCoords.y += viewport->viewPos.y; + } + + ViewportMove(windowCoords, window, viewport); + } + + void ViewportUpdateFollowSprite(WindowBase* window) + { + if (!window->viewport_target_sprite.IsNull() && window->viewport != nullptr) + { + auto* sprite = GetEntity(window->viewport_target_sprite); + if (sprite == nullptr) { return; } - ViewportUpdateSmartFollowGuest(window, *guest); - break; - } - case EntityType::Staff: - { - auto* staff = entity->As(); - if (staff == nullptr) + + if (!(gScreenFlags & SCREEN_FLAGS_TITLE_DEMO)) { - return; + int32_t height = (TileElementHeight({ sprite->x, sprite->y }))-16; + int32_t underground = sprite->z < height; + ViewportSetUndergroundFlag(underground, window, window->viewport); } - ViewportUpdateSmartFollowStaff(window, *staff); - break; - } - default: // All other types don't need any "smart" following; steam particle, duck, money effect, etc. - window->focus = Focus(window->viewport_smart_follow_sprite); - window->viewport_target_sprite = window->viewport_smart_follow_sprite; - break; - } -} -void ViewportUpdateSmartFollowGuest(WindowBase* window, const Guest& peep) -{ - Focus focus = Focus(peep.Id); - window->viewport_target_sprite = peep.Id; - - if (peep.State == PeepState::Picked) - { - window->viewport_smart_follow_sprite = EntityId::GetNull(); - window->viewport_target_sprite = EntityId::GetNull(); - window->focus = std::nullopt; // No focus - return; - } - - bool overallFocus = true; - if (peep.State == PeepState::OnRide || peep.State == PeepState::EnteringRide - || (peep.State == PeepState::LeavingRide && peep.x == kLocationNull)) - { - auto ride = GetRide(peep.CurrentRide); - if (ride != nullptr && (ride->lifecycle_flags & RIDE_LIFECYCLE_ON_TRACK)) - { - auto train = GetEntity(ride->vehicles[peep.CurrentTrain]); - if (train != nullptr) + auto centreLoc = centre_2d_coordinates(sprite->GetLocation(), window->viewport); + if (centreLoc.has_value()) { - const auto car = train->GetCar(peep.CurrentCar); - if (car != nullptr) + window->savedViewPos = *centreLoc; + ViewportMove(*centreLoc, window, window->viewport); + } + } + } + + void ViewportUpdateSmartFollowEntity(WindowBase* window) + { + auto entity = TryGetEntity(window->viewport_smart_follow_sprite); + if (entity == nullptr || entity->Type == EntityType::Null) + { + window->viewport_smart_follow_sprite = EntityId::GetNull(); + window->viewport_target_sprite = EntityId::GetNull(); + return; + } + + switch (entity->Type) + { + case EntityType::Vehicle: + ViewportUpdateSmartFollowVehicle(window); + break; + + case EntityType::Guest: + { + auto* guest = entity->As(); + if (guest == nullptr) { - focus = Focus(car->Id); - overallFocus = false; - window->viewport_target_sprite = car->Id; + return; + } + ViewportUpdateSmartFollowGuest(window, *guest); + break; + } + case EntityType::Staff: + { + auto* staff = entity->As(); + if (staff == nullptr) + { + return; + } + ViewportUpdateSmartFollowStaff(window, *staff); + break; + } + default: // All other types don't need any "smart" following; steam particle, duck, money effect, etc. + window->focus = Focus(window->viewport_smart_follow_sprite); + window->viewport_target_sprite = window->viewport_smart_follow_sprite; + break; + } + } + + void ViewportUpdateSmartFollowGuest(WindowBase* window, const Guest& peep) + { + Focus focus = Focus(peep.Id); + window->viewport_target_sprite = peep.Id; + + if (peep.State == PeepState::Picked) + { + window->viewport_smart_follow_sprite = EntityId::GetNull(); + window->viewport_target_sprite = EntityId::GetNull(); + window->focus = std::nullopt; // No focus + return; + } + + bool overallFocus = true; + if (peep.State == PeepState::OnRide || peep.State == PeepState::EnteringRide + || (peep.State == PeepState::LeavingRide && peep.x == kLocationNull)) + { + auto ride = GetRide(peep.CurrentRide); + if (ride != nullptr && (ride->lifecycle_flags & RIDE_LIFECYCLE_ON_TRACK)) + { + auto train = GetEntity(ride->vehicles[peep.CurrentTrain]); + if (train != nullptr) + { + const auto car = train->GetCar(peep.CurrentCar); + if (car != nullptr) + { + focus = Focus(car->Id); + overallFocus = false; + window->viewport_target_sprite = car->Id; + } } } } - } - if (peep.x == kLocationNull && overallFocus) - { - auto ride = GetRide(peep.CurrentRide); - if (ride != nullptr) + if (peep.x == kLocationNull && overallFocus) { - auto xy = ride->overall_view.ToTileCentre(); - CoordsXYZ coordFocus; - coordFocus.x = xy.x; - coordFocus.y = xy.y; - coordFocus.z = TileElementHeight(xy) + (4 * kCoordsZStep); - focus = Focus(coordFocus); - window->viewport_target_sprite = EntityId::GetNull(); + auto ride = GetRide(peep.CurrentRide); + if (ride != nullptr) + { + auto xy = ride->overall_view.ToTileCentre(); + CoordsXYZ coordFocus; + coordFocus.x = xy.x; + coordFocus.y = xy.y; + coordFocus.z = TileElementHeight(xy) + (4 * kCoordsZStep); + focus = Focus(coordFocus); + window->viewport_target_sprite = EntityId::GetNull(); + } } + + window->focus = focus; } - window->focus = focus; -} - -void ViewportUpdateSmartFollowStaff(WindowBase* window, const Staff& peep) -{ - if (peep.State == PeepState::Picked) + void ViewportUpdateSmartFollowStaff(WindowBase* window, const Staff& peep) { - window->viewport_smart_follow_sprite = EntityId::GetNull(); - window->viewport_target_sprite = EntityId::GetNull(); - window->focus = std::nullopt; - return; + if (peep.State == PeepState::Picked) + { + window->viewport_smart_follow_sprite = EntityId::GetNull(); + window->viewport_target_sprite = EntityId::GetNull(); + window->focus = std::nullopt; + return; + } + + window->focus = Focus(window->viewport_smart_follow_sprite); + window->viewport_target_sprite = window->viewport_smart_follow_sprite; } - window->focus = Focus(window->viewport_smart_follow_sprite); - window->viewport_target_sprite = window->viewport_smart_follow_sprite; -} - -void ViewportUpdateSmartFollowVehicle(WindowBase* window) -{ - window->focus = Focus(window->viewport_smart_follow_sprite); - window->viewport_target_sprite = window->viewport_smart_follow_sprite; -} - -static void ViewportRotateSingleInternal(WindowBase& w, int32_t direction) -{ - auto* viewport = w.viewport; - if (viewport == nullptr) - return; - - auto windowPos = ScreenCoordsXY{ (viewport->width >> 1), (viewport->height >> 1) } + viewport->pos; - - // has something to do with checking if middle of the viewport is obstructed - Viewport* other; - auto mapXYCoords = ScreenGetMapXY(windowPos, &other); - CoordsXYZ coords{}; - - // other != viewport probably triggers on viewports in ride or guest window? - // mapXYCoords is nullopt if middle of viewport is obstructed by another window? - if (!mapXYCoords.has_value() || other != viewport) + void ViewportUpdateSmartFollowVehicle(WindowBase* window) { - auto viewPos = ScreenCoordsXY{ (viewport->ViewWidth() >> 1), (viewport->ViewHeight() >> 1) } + viewport->viewPos; - - coords = ViewportAdjustForMapHeight(viewPos, viewport->rotation); + window->focus = Focus(window->viewport_smart_follow_sprite); + window->viewport_target_sprite = window->viewport_smart_follow_sprite; } - else + + static void ViewportRotateSingleInternal(WindowBase& w, int32_t direction) { - coords.x = mapXYCoords->x; - coords.y = mapXYCoords->y; - coords.z = TileElementHeight(coords); - } - - viewport->rotation = (viewport->rotation + direction) & 3; - - auto centreLoc = centre_2d_coordinates(coords, viewport); - - if (centreLoc.has_value()) - { - w.savedViewPos = centreLoc.value(); - viewport->viewPos = *centreLoc; - } - - w.Invalidate(); - w.OnViewportRotate(); -} - -void ViewportRotateSingle(WindowBase* window, int32_t direction) -{ - ViewportRotateSingleInternal(*window, direction); -} - -void ViewportRotateAll(int32_t direction) -{ - WindowVisitEach([direction](WindowBase* w) { - auto* viewport = w->viewport; + auto* viewport = w.viewport; if (viewport == nullptr) return; - if (viewport->flags & VIEWPORT_FLAG_INDEPEDENT_ROTATION) + + auto windowPos = ScreenCoordsXY{ (viewport->width >> 1), (viewport->height >> 1) } + viewport->pos; + + // has something to do with checking if middle of the viewport is obstructed + Viewport* other; + auto mapXYCoords = ScreenGetMapXY(windowPos, &other); + CoordsXYZ coords{}; + + // other != viewport probably triggers on viewports in ride or guest window? + // mapXYCoords is nullopt if middle of viewport is obstructed by another window? + if (!mapXYCoords.has_value() || other != viewport) + { + auto viewPos = ScreenCoordsXY{ (viewport->ViewWidth() >> 1), (viewport->ViewHeight() >> 1) } + viewport->viewPos; + + coords = ViewportAdjustForMapHeight(viewPos, viewport->rotation); + } + else + { + coords.x = mapXYCoords->x; + coords.y = mapXYCoords->y; + coords.z = TileElementHeight(coords); + } + + viewport->rotation = (viewport->rotation + direction) & 3; + + auto centreLoc = centre_2d_coordinates(coords, viewport); + + if (centreLoc.has_value()) + { + w.savedViewPos = centreLoc.value(); + viewport->viewPos = *centreLoc; + } + + w.Invalidate(); + w.OnViewportRotate(); + } + + void ViewportRotateSingle(WindowBase* window, int32_t direction) + { + ViewportRotateSingleInternal(*window, direction); + } + + void ViewportRotateAll(int32_t direction) + { + WindowVisitEach([direction](WindowBase* w) { + auto* viewport = w->viewport; + if (viewport == nullptr) + return; + if (viewport->flags & VIEWPORT_FLAG_INDEPEDENT_ROTATION) + return; + ViewportRotateSingleInternal(*w, direction); + }); + } + + /** + * + * rct2: 0x00685C02 + * ax: left + * bx: top + * dx: right + * esi: viewport + * edi: dpi + * ebp: bottom + */ + void ViewportRender(DrawPixelInfo& dpi, const Viewport* viewport) + { + if (viewport->flags & VIEWPORT_FLAG_RENDERING_INHIBITED) return; - ViewportRotateSingleInternal(*w, direction); - }); -} -/** - * - * rct2: 0x00685C02 - * ax: left - * bx: top - * dx: right - * esi: viewport - * edi: dpi - * ebp: bottom - */ -void ViewportRender(DrawPixelInfo& dpi, const Viewport* viewport) -{ - if (viewport->flags & VIEWPORT_FLAG_RENDERING_INHIBITED) - return; + if (dpi.x + dpi.width <= viewport->pos.x) + return; + if (dpi.y + dpi.height <= viewport->pos.y) + return; + if (dpi.x >= viewport->pos.x + viewport->width) + return; + if (dpi.y >= viewport->pos.y + viewport->height) + return; - if (dpi.x + dpi.width <= viewport->pos.x) - return; - if (dpi.y + dpi.height <= viewport->pos.y) - return; - if (dpi.x >= viewport->pos.x + viewport->width) - return; - if (dpi.y >= viewport->pos.y + viewport->height) - return; + ViewportPaint(viewport, dpi); + } - ViewportPaint(viewport, dpi); -} - -static void ViewportFillColumn(PaintSession& session) -{ - PROFILED_FUNCTION(); - - PaintSessionGenerate(session); - PaintSessionArrange(session); -} - -static void ViewportPaintColumn(PaintSession& session) -{ - PROFILED_FUNCTION(); - - if (session.ViewFlags - & (VIEWPORT_FLAG_HIDE_VERTICAL | VIEWPORT_FLAG_HIDE_BASE | VIEWPORT_FLAG_UNDERGROUND_INSIDE - | VIEWPORT_FLAG_CLIP_VIEW) - && (~session.ViewFlags & VIEWPORT_FLAG_TRANSPARENT_BACKGROUND)) + static void ViewportFillColumn(PaintSession& session) { - uint8_t colour = COLOUR_AQUAMARINE; - if (session.ViewFlags & VIEWPORT_FLAG_HIDE_ENTITIES) + PROFILED_FUNCTION(); + + PaintSessionGenerate(session); + PaintSessionArrange(session); + } + + static void ViewportPaintColumn(PaintSession& session) + { + PROFILED_FUNCTION(); + + if (session.ViewFlags + & (VIEWPORT_FLAG_HIDE_VERTICAL | VIEWPORT_FLAG_HIDE_BASE | VIEWPORT_FLAG_UNDERGROUND_INSIDE + | VIEWPORT_FLAG_CLIP_VIEW) + && (~session.ViewFlags & VIEWPORT_FLAG_TRANSPARENT_BACKGROUND)) { - colour = COLOUR_BLACK; - } - GfxClear(session.DPI, colour); - } - - PaintDrawStructs(session); - - if (Config::Get().general.RenderWeatherGloom && !gTrackDesignSaveMode && !(session.ViewFlags & VIEWPORT_FLAG_HIDE_ENTITIES) - && !(session.ViewFlags & VIEWPORT_FLAG_HIGHLIGHT_PATH_ISSUES)) - { - ViewportPaintWeatherGloom(session.DPI); - } - - if (session.PSStringHead != nullptr) - { - PaintDrawMoneyStructs(session.DPI, session.PSStringHead); - } -} - -/** - * - * rct2: 0x00685CBF - * eax: left - * ebx: top - * edx: right - * esi: viewport - * edi: dpi - * ebp: bottom - */ -static void ViewportPaint(const Viewport* viewport, DrawPixelInfo& dpi) -{ - PROFILED_FUNCTION(); - - const int32_t offsetX = dpi.x - viewport->pos.x; - const int32_t offsetY = dpi.y - viewport->pos.y; - const int32_t worldX = viewport->zoom.ApplyInversedTo(viewport->viewPos.x) + std::max(0, offsetX); - const int32_t worldY = viewport->zoom.ApplyInversedTo(viewport->viewPos.y) + std::max(0, offsetY); - const int32_t width = std::min(viewport->pos.x + viewport->width, dpi.x + dpi.width) - std::max(viewport->pos.x, dpi.x); - const int32_t height = std::min(viewport->pos.y + viewport->height, dpi.y + dpi.height) - std::max(viewport->pos.y, dpi.y); - - DrawPixelInfo worldDpi; - worldDpi.DrawingEngine = dpi.DrawingEngine; - worldDpi.bits = dpi.bits + std::max(0, -offsetX) + std::max(0, -offsetY) * dpi.LineStride(); - worldDpi.x = worldX; - worldDpi.y = worldY; - worldDpi.width = width; - worldDpi.height = height; - worldDpi.pitch = dpi.LineStride() - worldDpi.width; - worldDpi.zoom_level = viewport->zoom; - - _paintColumns.clear(); - - bool useMultithreading = Config::Get().general.MultiThreading; - if (useMultithreading && _paintJobs == nullptr) - { - _paintJobs = std::make_unique(); - } - else if (useMultithreading == false && _paintJobs != nullptr) - { - _paintJobs.reset(); - } - - bool useParallelDrawing = false; - if (useMultithreading && (dpi.DrawingEngine->GetFlags() & DEF_PARALLEL_DRAWING)) - { - useParallelDrawing = true; - } - - const int32_t columnWidth = worldDpi.zoom_level.ApplyInversedTo(kCoordsXYStep); - const int32_t rightBorder = worldDpi.x + worldDpi.width; - const int32_t alignedX = floor2(worldDpi.x, columnWidth); - - // Generate and sort columns. - for (int32_t x = alignedX; x < rightBorder; x += columnWidth) - { - PaintSession* session = PaintSessionAlloc(worldDpi, viewport->flags, viewport->rotation); - _paintColumns.push_back(session); - - DrawPixelInfo& columnDpi = session->DPI; - if (x >= columnDpi.x) - { - const int32_t leftPitch = x - columnDpi.x; - columnDpi.width = columnDpi.width - leftPitch; - columnDpi.bits += leftPitch; - columnDpi.pitch += leftPitch; - columnDpi.x = x; + uint8_t colour = COLOUR_AQUAMARINE; + if (session.ViewFlags & VIEWPORT_FLAG_HIDE_ENTITIES) + { + colour = COLOUR_BLACK; + } + GfxClear(session.DPI, colour); } - int32_t paintRight = columnDpi.x + columnDpi.width; - if (paintRight >= x + columnWidth) + PaintDrawStructs(session); + + if (Config::Get().general.RenderWeatherGloom && !gTrackDesignSaveMode + && !(session.ViewFlags & VIEWPORT_FLAG_HIDE_ENTITIES) && !(session.ViewFlags & VIEWPORT_FLAG_HIGHLIGHT_PATH_ISSUES)) { - const int32_t rightPitch = paintRight - x - columnWidth; - paintRight -= rightPitch; - columnDpi.pitch += rightPitch; + ViewportPaintWeatherGloom(session.DPI); + } + + if (session.PSStringHead != nullptr) + { + PaintDrawMoneyStructs(session.DPI, session.PSStringHead); + } + } + + /** + * + * rct2: 0x00685CBF + * eax: left + * ebx: top + * edx: right + * esi: viewport + * edi: dpi + * ebp: bottom + */ + static void ViewportPaint(const Viewport* viewport, DrawPixelInfo& dpi) + { + PROFILED_FUNCTION(); + + const int32_t offsetX = dpi.x - viewport->pos.x; + const int32_t offsetY = dpi.y - viewport->pos.y; + const int32_t worldX = viewport->zoom.ApplyInversedTo(viewport->viewPos.x) + std::max(0, offsetX); + const int32_t worldY = viewport->zoom.ApplyInversedTo(viewport->viewPos.y) + std::max(0, offsetY); + const int32_t width = std::min(viewport->pos.x + viewport->width, dpi.x + dpi.width) - std::max(viewport->pos.x, dpi.x); + const int32_t height = std::min(viewport->pos.y + viewport->height, dpi.y + dpi.height) + - std::max(viewport->pos.y, dpi.y); + + DrawPixelInfo worldDpi; + worldDpi.DrawingEngine = dpi.DrawingEngine; + worldDpi.bits = dpi.bits + std::max(0, -offsetX) + std::max(0, -offsetY) * dpi.LineStride(); + worldDpi.x = worldX; + worldDpi.y = worldY; + worldDpi.width = width; + worldDpi.height = height; + worldDpi.pitch = dpi.LineStride() - worldDpi.width; + worldDpi.zoom_level = viewport->zoom; + + _paintColumns.clear(); + + bool useMultithreading = Config::Get().general.MultiThreading; + if (useMultithreading && _paintJobs == nullptr) + { + _paintJobs = std::make_unique(); + } + else if (useMultithreading == false && _paintJobs != nullptr) + { + _paintJobs.reset(); + } + + bool useParallelDrawing = false; + if (useMultithreading && (dpi.DrawingEngine->GetFlags() & DEF_PARALLEL_DRAWING)) + { + useParallelDrawing = true; + } + + const int32_t columnWidth = worldDpi.zoom_level.ApplyInversedTo(kCoordsXYStep); + const int32_t rightBorder = worldDpi.x + worldDpi.width; + const int32_t alignedX = floor2(worldDpi.x, columnWidth); + + // Generate and sort columns. + for (int32_t x = alignedX; x < rightBorder; x += columnWidth) + { + PaintSession* session = PaintSessionAlloc(worldDpi, viewport->flags, viewport->rotation); + _paintColumns.push_back(session); + + DrawPixelInfo& columnDpi = session->DPI; + if (x >= columnDpi.x) + { + const int32_t leftPitch = x - columnDpi.x; + columnDpi.width = columnDpi.width - leftPitch; + columnDpi.bits += leftPitch; + columnDpi.pitch += leftPitch; + columnDpi.x = x; + } + + int32_t paintRight = columnDpi.x + columnDpi.width; + if (paintRight >= x + columnWidth) + { + const int32_t rightPitch = paintRight - x - columnWidth; + paintRight -= rightPitch; + columnDpi.pitch += rightPitch; + } + columnDpi.width = paintRight - columnDpi.x; + + if (useMultithreading) + { + _paintJobs->AddTask([session]() -> void { ViewportFillColumn(*session); }); + } + else + { + ViewportFillColumn(*session); + } } - columnDpi.width = paintRight - columnDpi.x; if (useMultithreading) { - _paintJobs->AddTask([session]() -> void { ViewportFillColumn(*session); }); + _paintJobs->Join(); } - else + + // Paint columns. + for (auto* session : _paintColumns) { - ViewportFillColumn(*session); + if (useParallelDrawing) + { + _paintJobs->AddTask([session]() -> void { ViewportPaintColumn(*session); }); + } + else + { + ViewportPaintColumn(*session); + } } - } - - if (useMultithreading) - { - _paintJobs->Join(); - } - - // Paint columns. - for (auto* session : _paintColumns) - { if (useParallelDrawing) { - _paintJobs->AddTask([session]() -> void { ViewportPaintColumn(*session); }); + _paintJobs->Join(); + } + + // Release resources. + for (auto* session : _paintColumns) + { + PaintSessionFree(session); + } + } + + static void ViewportPaintWeatherGloom(DrawPixelInfo& dpi) + { + auto paletteId = ClimateGetWeatherGloomPaletteId(GetGameState().ClimateCurrent); + if (paletteId != FilterPaletteID::PaletteNull) + { + auto x = dpi.x; + auto y = dpi.y; + auto w = dpi.width; + auto h = dpi.height; + GfxFilterRect(dpi, ScreenRect(x, y, x + w, y + h), paletteId); + } + } + + /** + * + * rct2: 0x0068958D + */ + std::optional ScreenPosToMapPos(const ScreenCoordsXY& screenCoords, int32_t* direction) + { + auto mapCoords = ScreenGetMapXY(screenCoords, nullptr); + if (!mapCoords.has_value()) + return std::nullopt; + + int32_t my_direction; + int32_t dist_from_centre_x = abs(mapCoords->x % 32); + int32_t dist_from_centre_y = abs(mapCoords->y % 32); + if (dist_from_centre_x > 8 && dist_from_centre_x < 24 && dist_from_centre_y > 8 && dist_from_centre_y < 24) + { + my_direction = 4; } else { - ViewportPaintColumn(*session); - } - } - if (useParallelDrawing) - { - _paintJobs->Join(); - } - - // Release resources. - for (auto* session : _paintColumns) - { - PaintSessionFree(session); - } -} - -static void ViewportPaintWeatherGloom(DrawPixelInfo& dpi) -{ - auto paletteId = ClimateGetWeatherGloomPaletteId(GetGameState().ClimateCurrent); - if (paletteId != FilterPaletteID::PaletteNull) - { - auto x = dpi.x; - auto y = dpi.y; - auto w = dpi.width; - auto h = dpi.height; - GfxFilterRect(dpi, ScreenRect(x, y, x + w, y + h), paletteId); - } -} - -/** - * - * rct2: 0x0068958D - */ -std::optional ScreenPosToMapPos(const ScreenCoordsXY& screenCoords, int32_t* direction) -{ - auto mapCoords = ScreenGetMapXY(screenCoords, nullptr); - if (!mapCoords.has_value()) - return std::nullopt; - - int32_t my_direction; - int32_t dist_from_centre_x = abs(mapCoords->x % 32); - int32_t dist_from_centre_y = abs(mapCoords->y % 32); - if (dist_from_centre_x > 8 && dist_from_centre_x < 24 && dist_from_centre_y > 8 && dist_from_centre_y < 24) - { - my_direction = 4; - } - else - { - auto mod_x = mapCoords->x & 0x1F; - auto mod_y = mapCoords->y & 0x1F; - if (mod_x <= 16) - { - if (mod_y < 16) + auto mod_x = mapCoords->x & 0x1F; + auto mod_y = mapCoords->y & 0x1F; + if (mod_x <= 16) { - my_direction = 2; - } - else - { - my_direction = 3; - } - } - else - { - if (mod_y < 16) - { - my_direction = 1; - } - else - { - my_direction = 0; - } - } - } - - if (direction != nullptr) - *direction = my_direction; - return { mapCoords->ToTileStart() }; -} - -[[nodiscard]] ScreenCoordsXY Viewport::ScreenToViewportCoord(const ScreenCoordsXY& screenCoords) const -{ - ScreenCoordsXY ret; - ret.x = (zoom.ApplyTo(screenCoords.x - pos.x)) + viewPos.x; - ret.y = (zoom.ApplyTo(screenCoords.y - pos.y)) + viewPos.y; - return ret; -} - -void Viewport::Invalidate() const -{ - ViewportInvalidate(this, { viewPos, viewPos + ScreenCoordsXY{ ViewWidth(), ViewHeight() } }); -} - -CoordsXY ViewportPosToMapPos(const ScreenCoordsXY& coords, int32_t z, uint8_t rotation) -{ - // Reverse of Translate3DTo2DWithZ - CoordsXY ret = { coords.y - coords.x / 2 + z, coords.y + coords.x / 2 + z }; - auto inverseRotation = DirectionFlipXAxis(rotation); - return ret.Rotate(inverseRotation); -} - -/** - * - * rct2: 0x00664689 - */ -void ShowGridlines() -{ - if (gShowGridLinesRefCount == 0) - { - WindowBase* mainWindow = WindowGetMain(); - if (mainWindow != nullptr) - { - if (!(mainWindow->viewport->flags & VIEWPORT_FLAG_GRIDLINES)) - { - mainWindow->viewport->flags |= VIEWPORT_FLAG_GRIDLINES; - mainWindow->Invalidate(); - } - } - } - gShowGridLinesRefCount++; -} - -/** - * - * rct2: 0x006646B4 - */ -void HideGridlines() -{ - if (gShowGridLinesRefCount > 0) - gShowGridLinesRefCount--; - - if (gShowGridLinesRefCount == 0) - { - WindowBase* mainWindow = WindowGetMain(); - if (mainWindow != nullptr) - { - if (!Config::Get().general.AlwaysShowGridlines) - { - mainWindow->viewport->flags &= ~VIEWPORT_FLAG_GRIDLINES; - mainWindow->Invalidate(); - } - } - } -} - -/** - * - * rct2: 0x00664E8E - */ -void ShowLandRights() -{ - if (gShowLandRightsRefCount == 0) - { - WindowBase* mainWindow = WindowGetMain(); - if (mainWindow != nullptr) - { - if (!(mainWindow->viewport->flags & VIEWPORT_FLAG_LAND_OWNERSHIP)) - { - mainWindow->viewport->flags |= VIEWPORT_FLAG_LAND_OWNERSHIP; - mainWindow->Invalidate(); - } - } - } - gShowLandRightsRefCount++; -} - -/** - * - * rct2: 0x00664EB9 - */ -void HideLandRights() -{ - if (gShowLandRightsRefCount > 0) - gShowLandRightsRefCount--; - - if (gShowLandRightsRefCount == 0) - { - WindowBase* mainWindow = WindowGetMain(); - if (mainWindow != nullptr) - { - if (mainWindow->viewport->flags & VIEWPORT_FLAG_LAND_OWNERSHIP) - { - mainWindow->viewport->flags &= ~VIEWPORT_FLAG_LAND_OWNERSHIP; - mainWindow->Invalidate(); - } - } - } -} - -/** - * - * rct2: 0x00664EDD - */ -void ShowConstructionRights() -{ - if (gShowConstructionRightsRefCount == 0) - { - WindowBase* mainWindow = WindowGetMain(); - if (mainWindow != nullptr) - { - if (!(mainWindow->viewport->flags & VIEWPORT_FLAG_CONSTRUCTION_RIGHTS)) - { - mainWindow->viewport->flags |= VIEWPORT_FLAG_CONSTRUCTION_RIGHTS; - mainWindow->Invalidate(); - } - } - } - gShowConstructionRightsRefCount++; -} - -/** - * - * rct2: 0x00664F08 - */ -void HideConstructionRights() -{ - if (gShowConstructionRightsRefCount > 0) - gShowConstructionRightsRefCount--; - - if (gShowConstructionRightsRefCount == 0) - { - WindowBase* mainWindow = WindowGetMain(); - if (mainWindow != nullptr) - { - if (mainWindow->viewport->flags & VIEWPORT_FLAG_CONSTRUCTION_RIGHTS) - { - mainWindow->viewport->flags &= ~VIEWPORT_FLAG_CONSTRUCTION_RIGHTS; - mainWindow->Invalidate(); - } - } - } -} - -/** - * - * rct2: 0x006CB70A - */ -void ViewportSetVisibility(ViewportVisibility mode) -{ - WindowBase* window = WindowGetMain(); - - if (window != nullptr) - { - Viewport* vp = window->viewport; - uint32_t invalidate = 0; - - switch (mode) - { - case ViewportVisibility::Default: - { // Set all these flags to 0, and invalidate if any were active - uint32_t mask = VIEWPORT_FLAG_UNDERGROUND_INSIDE | VIEWPORT_FLAG_HIDE_RIDES | VIEWPORT_FLAG_HIDE_SCENERY - | VIEWPORT_FLAG_HIDE_PATHS | VIEWPORT_FLAG_LAND_HEIGHTS | VIEWPORT_FLAG_TRACK_HEIGHTS - | VIEWPORT_FLAG_PATH_HEIGHTS | VIEWPORT_FLAG_HIDE_GUESTS | VIEWPORT_FLAG_HIDE_STAFF - | VIEWPORT_FLAG_HIDE_BASE | VIEWPORT_FLAG_HIDE_VERTICAL | VIEWPORT_FLAG_HIDE_VEHICLES - | VIEWPORT_FLAG_HIDE_SUPPORTS | VIEWPORT_FLAG_HIDE_VEGETATION; - - invalidate += vp->flags & mask; - vp->flags &= ~mask; - break; - } - case ViewportVisibility::UndergroundViewOn: // 6CB79D - case ViewportVisibility::UndergroundViewGhostOn: // 6CB7C4 - // Set underground on, invalidate if it was off - invalidate += !(vp->flags & VIEWPORT_FLAG_UNDERGROUND_INSIDE); - vp->flags |= VIEWPORT_FLAG_UNDERGROUND_INSIDE; - break; - case ViewportVisibility::TrackHeights: // 6CB7EB - // Set track heights on, invalidate if off - invalidate += !(vp->flags & VIEWPORT_FLAG_TRACK_HEIGHTS); - vp->flags |= VIEWPORT_FLAG_TRACK_HEIGHTS; - break; - case ViewportVisibility::UndergroundViewOff: // 6CB7B1 - case ViewportVisibility::UndergroundViewGhostOff: // 6CB7D8 - // Set underground off, invalidate if it was on - invalidate += vp->flags & VIEWPORT_FLAG_UNDERGROUND_INSIDE; - vp->flags &= ~(static_cast(VIEWPORT_FLAG_UNDERGROUND_INSIDE)); - break; - } - if (invalidate != 0) - window->Invalidate(); - } -} - -static bool IsCursorIdVegetation(CursorID cursor) -{ - switch (cursor) - { - case CursorID::TreeDown: - case CursorID::FlowerDown: - return true; - default: - return false; - } -} - -static bool IsTileElementVegetation(const TileElement* tileElement) -{ - switch (tileElement->GetType()) - { - case TileElementType::SmallScenery: - { - auto sceneryItem = tileElement->AsSmallScenery(); - auto sceneryEntry = sceneryItem->GetEntry(); - if (sceneryEntry != nullptr - && (sceneryEntry->HasFlag(SMALL_SCENERY_FLAG_IS_TREE) || IsCursorIdVegetation(sceneryEntry->tool_id))) - { - return true; - } - break; - } - case TileElementType::LargeScenery: - { - auto sceneryItem = tileElement->AsLargeScenery(); - auto sceneryEntry = sceneryItem->GetEntry(); - if (sceneryEntry != nullptr && IsCursorIdVegetation(sceneryEntry->tool_id)) - { - return true; - } - break; - } - case TileElementType::Wall: - { - auto sceneryItem = tileElement->AsWall(); - auto sceneryEntry = sceneryItem->GetEntry(); - if (sceneryEntry != nullptr && IsCursorIdVegetation(sceneryEntry->tool_id)) - { - return true; - } - break; - } - default: - break; - } - return false; -} - -VisibilityKind GetPaintStructVisibility(const PaintStruct* ps, uint32_t viewFlags) -{ - switch (ps->InteractionItem) - { - case ViewportInteractionItem::Entity: - if (ps->Entity != nullptr) - { - switch (ps->Entity->Type) + if (mod_y < 16) { - case EntityType::Vehicle: - { - if (viewFlags & VIEWPORT_FLAG_HIDE_VEHICLES) - { - return (viewFlags & VIEWPORT_FLAG_INVISIBLE_VEHICLES) ? VisibilityKind::Hidden - : VisibilityKind::Partial; - } - // Rides without track can technically have a 'vehicle': - // these should be hidden if 'hide rides' is enabled - if (viewFlags & VIEWPORT_FLAG_HIDE_RIDES) - { - auto vehicle = ps->Entity->As(); - if (vehicle == nullptr) - break; - - auto ride = vehicle->GetRide(); - if (ride != nullptr && !ride->GetRideTypeDescriptor().HasFlag(RtdFlag::hasTrack)) - { - return (viewFlags & VIEWPORT_FLAG_INVISIBLE_RIDES) ? VisibilityKind::Hidden - : VisibilityKind::Partial; - } - } - break; - } - case EntityType::Guest: - if (viewFlags & VIEWPORT_FLAG_HIDE_GUESTS) - { - return VisibilityKind::Hidden; - } - break; - case EntityType::Staff: - if (viewFlags & VIEWPORT_FLAG_HIDE_STAFF) - { - return VisibilityKind::Hidden; - } - break; - default: - break; - } - } - break; - case ViewportInteractionItem::Ride: - if (viewFlags & VIEWPORT_FLAG_HIDE_RIDES) - { - return (viewFlags & VIEWPORT_FLAG_INVISIBLE_RIDES) ? VisibilityKind::Hidden : VisibilityKind::Partial; - } - break; - case ViewportInteractionItem::Footpath: - case ViewportInteractionItem::PathAddition: - case ViewportInteractionItem::Banner: - if (viewFlags & VIEWPORT_FLAG_HIDE_PATHS) - { - return (viewFlags & VIEWPORT_FLAG_INVISIBLE_PATHS) ? VisibilityKind::Hidden : VisibilityKind::Partial; - } - break; - case ViewportInteractionItem::Scenery: - case ViewportInteractionItem::LargeScenery: - case ViewportInteractionItem::Wall: - if (ps->Element != nullptr) - { - if (IsTileElementVegetation(ps->Element)) - { - if (viewFlags & VIEWPORT_FLAG_HIDE_VEGETATION) - { - return (viewFlags & VIEWPORT_FLAG_INVISIBLE_VEGETATION) ? VisibilityKind::Hidden - : VisibilityKind::Partial; - } + my_direction = 2; } else { - if (viewFlags & VIEWPORT_FLAG_HIDE_SCENERY) - { - return (viewFlags & VIEWPORT_FLAG_INVISIBLE_SCENERY) ? VisibilityKind::Hidden : VisibilityKind::Partial; - } + my_direction = 3; } } - if (ps->InteractionItem == ViewportInteractionItem::Wall && (viewFlags & VIEWPORT_FLAG_UNDERGROUND_INSIDE)) + else { - return VisibilityKind::Partial; + if (mod_y < 16) + { + my_direction = 1; + } + else + { + my_direction = 0; + } } - break; - default: - break; - } - return VisibilityKind::Visible; -} + } -/** - * Checks if a PaintStruct sprite type is in the filter mask. - */ -static bool PSInteractionTypeIsInFilter(PaintStruct* ps, uint16_t filter) -{ - if (ps->InteractionItem != ViewportInteractionItem::None && ps->InteractionItem != ViewportInteractionItem::Label - && ps->InteractionItem <= ViewportInteractionItem::Banner) + if (direction != nullptr) + *direction = my_direction; + return { mapCoords->ToTileStart() }; + } + + [[nodiscard]] ScreenCoordsXY Viewport::ScreenToViewportCoord(const ScreenCoordsXY& screenCoords) const { - auto mask = EnumToFlag(ps->InteractionItem); - if (filter & mask) + ScreenCoordsXY ret; + ret.x = (zoom.ApplyTo(screenCoords.x - pos.x)) + viewPos.x; + ret.y = (zoom.ApplyTo(screenCoords.y - pos.y)) + viewPos.y; + return ret; + } + + void Viewport::Invalidate() const + { + ViewportInvalidate(this, { viewPos, viewPos + ScreenCoordsXY{ ViewWidth(), ViewHeight() } }); + } + + CoordsXY ViewportPosToMapPos(const ScreenCoordsXY& coords, int32_t z, uint8_t rotation) + { + // Reverse of Translate3DTo2DWithZ + CoordsXY ret = { coords.y - coords.x / 2 + z, coords.y + coords.x / 2 + z }; + auto inverseRotation = DirectionFlipXAxis(rotation); + return ret.Rotate(inverseRotation); + } + + /** + * + * rct2: 0x00664689 + */ + void ShowGridlines() + { + if (gShowGridLinesRefCount == 0) { - return true; + WindowBase* mainWindow = WindowGetMain(); + if (mainWindow != nullptr) + { + if (!(mainWindow->viewport->flags & VIEWPORT_FLAG_GRIDLINES)) + { + mainWindow->viewport->flags |= VIEWPORT_FLAG_GRIDLINES; + mainWindow->Invalidate(); + } + } + } + gShowGridLinesRefCount++; + } + + /** + * + * rct2: 0x006646B4 + */ + void HideGridlines() + { + if (gShowGridLinesRefCount > 0) + gShowGridLinesRefCount--; + + if (gShowGridLinesRefCount == 0) + { + WindowBase* mainWindow = WindowGetMain(); + if (mainWindow != nullptr) + { + if (!Config::Get().general.AlwaysShowGridlines) + { + mainWindow->viewport->flags &= ~VIEWPORT_FLAG_GRIDLINES; + mainWindow->Invalidate(); + } + } } } - return false; -} -/** - * rct2: 0x00679236, 0x00679662, 0x00679B0D, 0x00679FF1 - */ -static bool IsPixelPresentBMP( - const uint32_t imageType, const G1Element* g1, const int32_t x, const int32_t y, const PaletteMap& paletteMap) -{ - uint8_t* index = g1->offset + (y * g1->width) + x; - - // Needs investigation as it has no consideration for pure BMP maps. - if (!(g1->flags & G1_FLAG_HAS_TRANSPARENCY)) + /** + * + * rct2: 0x00664E8E + */ + void ShowLandRights() { + if (gShowLandRightsRefCount == 0) + { + WindowBase* mainWindow = WindowGetMain(); + if (mainWindow != nullptr) + { + if (!(mainWindow->viewport->flags & VIEWPORT_FLAG_LAND_OWNERSHIP)) + { + mainWindow->viewport->flags |= VIEWPORT_FLAG_LAND_OWNERSHIP; + mainWindow->Invalidate(); + } + } + } + gShowLandRightsRefCount++; + } + + /** + * + * rct2: 0x00664EB9 + */ + void HideLandRights() + { + if (gShowLandRightsRefCount > 0) + gShowLandRightsRefCount--; + + if (gShowLandRightsRefCount == 0) + { + WindowBase* mainWindow = WindowGetMain(); + if (mainWindow != nullptr) + { + if (mainWindow->viewport->flags & VIEWPORT_FLAG_LAND_OWNERSHIP) + { + mainWindow->viewport->flags &= ~VIEWPORT_FLAG_LAND_OWNERSHIP; + mainWindow->Invalidate(); + } + } + } + } + + /** + * + * rct2: 0x00664EDD + */ + void ShowConstructionRights() + { + if (gShowConstructionRightsRefCount == 0) + { + WindowBase* mainWindow = WindowGetMain(); + if (mainWindow != nullptr) + { + if (!(mainWindow->viewport->flags & VIEWPORT_FLAG_CONSTRUCTION_RIGHTS)) + { + mainWindow->viewport->flags |= VIEWPORT_FLAG_CONSTRUCTION_RIGHTS; + mainWindow->Invalidate(); + } + } + } + gShowConstructionRightsRefCount++; + } + + /** + * + * rct2: 0x00664F08 + */ + void HideConstructionRights() + { + if (gShowConstructionRightsRefCount > 0) + gShowConstructionRightsRefCount--; + + if (gShowConstructionRightsRefCount == 0) + { + WindowBase* mainWindow = WindowGetMain(); + if (mainWindow != nullptr) + { + if (mainWindow->viewport->flags & VIEWPORT_FLAG_CONSTRUCTION_RIGHTS) + { + mainWindow->viewport->flags &= ~VIEWPORT_FLAG_CONSTRUCTION_RIGHTS; + mainWindow->Invalidate(); + } + } + } + } + + /** + * + * rct2: 0x006CB70A + */ + void ViewportSetVisibility(ViewportVisibility mode) + { + WindowBase* window = WindowGetMain(); + + if (window != nullptr) + { + Viewport* vp = window->viewport; + uint32_t invalidate = 0; + + switch (mode) + { + case ViewportVisibility::Default: + { // Set all these flags to 0, and invalidate if any were active + uint32_t mask = VIEWPORT_FLAG_UNDERGROUND_INSIDE | VIEWPORT_FLAG_HIDE_RIDES | VIEWPORT_FLAG_HIDE_SCENERY + | VIEWPORT_FLAG_HIDE_PATHS | VIEWPORT_FLAG_LAND_HEIGHTS | VIEWPORT_FLAG_TRACK_HEIGHTS + | VIEWPORT_FLAG_PATH_HEIGHTS | VIEWPORT_FLAG_HIDE_GUESTS | VIEWPORT_FLAG_HIDE_STAFF + | VIEWPORT_FLAG_HIDE_BASE | VIEWPORT_FLAG_HIDE_VERTICAL | VIEWPORT_FLAG_HIDE_VEHICLES + | VIEWPORT_FLAG_HIDE_SUPPORTS | VIEWPORT_FLAG_HIDE_VEGETATION; + + invalidate += vp->flags & mask; + vp->flags &= ~mask; + break; + } + case ViewportVisibility::UndergroundViewOn: // 6CB79D + case ViewportVisibility::UndergroundViewGhostOn: // 6CB7C4 + // Set underground on, invalidate if it was off + invalidate += !(vp->flags & VIEWPORT_FLAG_UNDERGROUND_INSIDE); + vp->flags |= VIEWPORT_FLAG_UNDERGROUND_INSIDE; + break; + case ViewportVisibility::TrackHeights: // 6CB7EB + // Set track heights on, invalidate if off + invalidate += !(vp->flags & VIEWPORT_FLAG_TRACK_HEIGHTS); + vp->flags |= VIEWPORT_FLAG_TRACK_HEIGHTS; + break; + case ViewportVisibility::UndergroundViewOff: // 6CB7B1 + case ViewportVisibility::UndergroundViewGhostOff: // 6CB7D8 + // Set underground off, invalidate if it was on + invalidate += vp->flags & VIEWPORT_FLAG_UNDERGROUND_INSIDE; + vp->flags &= ~(static_cast(VIEWPORT_FLAG_UNDERGROUND_INSIDE)); + break; + } + if (invalidate != 0) + window->Invalidate(); + } + } + + static bool IsCursorIdVegetation(CursorID cursor) + { + switch (cursor) + { + case CursorID::TreeDown: + case CursorID::FlowerDown: + return true; + default: + return false; + } + } + + static bool IsTileElementVegetation(const TileElement* tileElement) + { + switch (tileElement->GetType()) + { + case TileElementType::SmallScenery: + { + auto sceneryItem = tileElement->AsSmallScenery(); + auto sceneryEntry = sceneryItem->GetEntry(); + if (sceneryEntry != nullptr + && (sceneryEntry->HasFlag(SMALL_SCENERY_FLAG_IS_TREE) || IsCursorIdVegetation(sceneryEntry->tool_id))) + { + return true; + } + break; + } + case TileElementType::LargeScenery: + { + auto sceneryItem = tileElement->AsLargeScenery(); + auto sceneryEntry = sceneryItem->GetEntry(); + if (sceneryEntry != nullptr && IsCursorIdVegetation(sceneryEntry->tool_id)) + { + return true; + } + break; + } + case TileElementType::Wall: + { + auto sceneryItem = tileElement->AsWall(); + auto sceneryEntry = sceneryItem->GetEntry(); + if (sceneryEntry != nullptr && IsCursorIdVegetation(sceneryEntry->tool_id)) + { + return true; + } + break; + } + default: + break; + } return false; } - if (imageType & IMAGE_TYPE_REMAP) + VisibilityKind GetPaintStructVisibility(const PaintStruct* ps, uint32_t viewFlags) { - return paletteMap[*index] != 0; + switch (ps->InteractionItem) + { + case ViewportInteractionItem::Entity: + if (ps->Entity != nullptr) + { + switch (ps->Entity->Type) + { + case EntityType::Vehicle: + { + if (viewFlags & VIEWPORT_FLAG_HIDE_VEHICLES) + { + return (viewFlags & VIEWPORT_FLAG_INVISIBLE_VEHICLES) ? VisibilityKind::Hidden + : VisibilityKind::Partial; + } + // Rides without track can technically have a 'vehicle': + // these should be hidden if 'hide rides' is enabled + if (viewFlags & VIEWPORT_FLAG_HIDE_RIDES) + { + auto vehicle = ps->Entity->As(); + if (vehicle == nullptr) + break; + + auto ride = vehicle->GetRide(); + if (ride != nullptr && !ride->GetRideTypeDescriptor().HasFlag(RtdFlag::hasTrack)) + { + return (viewFlags & VIEWPORT_FLAG_INVISIBLE_RIDES) ? VisibilityKind::Hidden + : VisibilityKind::Partial; + } + } + break; + } + case EntityType::Guest: + if (viewFlags & VIEWPORT_FLAG_HIDE_GUESTS) + { + return VisibilityKind::Hidden; + } + break; + case EntityType::Staff: + if (viewFlags & VIEWPORT_FLAG_HIDE_STAFF) + { + return VisibilityKind::Hidden; + } + break; + default: + break; + } + } + break; + case ViewportInteractionItem::Ride: + if (viewFlags & VIEWPORT_FLAG_HIDE_RIDES) + { + return (viewFlags & VIEWPORT_FLAG_INVISIBLE_RIDES) ? VisibilityKind::Hidden : VisibilityKind::Partial; + } + break; + case ViewportInteractionItem::Footpath: + case ViewportInteractionItem::PathAddition: + case ViewportInteractionItem::Banner: + if (viewFlags & VIEWPORT_FLAG_HIDE_PATHS) + { + return (viewFlags & VIEWPORT_FLAG_INVISIBLE_PATHS) ? VisibilityKind::Hidden : VisibilityKind::Partial; + } + break; + case ViewportInteractionItem::Scenery: + case ViewportInteractionItem::LargeScenery: + case ViewportInteractionItem::Wall: + if (ps->Element != nullptr) + { + if (IsTileElementVegetation(ps->Element)) + { + if (viewFlags & VIEWPORT_FLAG_HIDE_VEGETATION) + { + return (viewFlags & VIEWPORT_FLAG_INVISIBLE_VEGETATION) ? VisibilityKind::Hidden + : VisibilityKind::Partial; + } + } + else + { + if (viewFlags & VIEWPORT_FLAG_HIDE_SCENERY) + { + return (viewFlags & VIEWPORT_FLAG_INVISIBLE_SCENERY) ? VisibilityKind::Hidden + : VisibilityKind::Partial; + } + } + } + if (ps->InteractionItem == ViewportInteractionItem::Wall && (viewFlags & VIEWPORT_FLAG_UNDERGROUND_INSIDE)) + { + return VisibilityKind::Partial; + } + break; + default: + break; + } + return VisibilityKind::Visible; } - if (imageType & IMAGE_TYPE_TRANSPARENT) + /** + * Checks if a PaintStruct sprite type is in the filter mask. + */ + static bool PSInteractionTypeIsInFilter(PaintStruct* ps, uint16_t filter) { + if (ps->InteractionItem != ViewportInteractionItem::None && ps->InteractionItem != ViewportInteractionItem::Label + && ps->InteractionItem <= ViewportInteractionItem::Banner) + { + auto mask = EnumToFlag(ps->InteractionItem); + if (filter & mask) + { + return true; + } + } return false; } - return (*index != 0); -} - -/** - * rct2: 0x0067933B, 0x00679788, 0x00679C4A, 0x0067A117 - */ -static bool IsPixelPresentRLE(const uint8_t* imgData, const int32_t x, const int32_t y) -{ - uint16_t lineOffset; - std::memcpy(&lineOffset, &imgData[y * sizeof(uint16_t)], sizeof(uint16_t)); - const uint8_t* data8 = imgData + lineOffset; - - bool lastDataLine = false; - while (!lastDataLine) + /** + * rct2: 0x00679236, 0x00679662, 0x00679B0D, 0x00679FF1 + */ + static bool IsPixelPresentBMP( + const uint32_t imageType, const G1Element* g1, const int32_t x, const int32_t y, const PaletteMap& paletteMap) { - int32_t numPixels = *data8++; - uint8_t pixelRunStart = *data8++; - lastDataLine = numPixels & 0x80; - numPixels &= 0x7F; - data8 += numPixels; + uint8_t* index = g1->offset + (y * g1->width) + x; - if (pixelRunStart <= x && x < pixelRunStart + numPixels) - return true; - } - return false; -} - -/** - * rct2: 0x00679074 - */ -static bool IsSpriteInteractedWithPaletteSet( - DrawPixelInfo& dpi, ImageId imageId, const ScreenCoordsXY& coords, const PaletteMap& paletteMap, const uint8_t imageType) -{ - PROFILED_FUNCTION(); - - const G1Element* g1 = GfxGetG1Element(imageId); - if (g1 == nullptr) - { - return false; - } - - ZoomLevel zoomLevel = dpi.zoom_level; - ScreenCoordsXY interactionPoint{ dpi.WorldX(), dpi.WorldY() }; - ScreenCoordsXY origin = coords; - - if (dpi.zoom_level > ZoomLevel{ 0 }) - { - if (g1->flags & G1_FLAG_NO_ZOOM_DRAW) + // Needs investigation as it has no consideration for pure BMP maps. + if (!(g1->flags & G1_FLAG_HAS_TRANSPARENCY)) { return false; } - while (g1->flags & G1_FLAG_HAS_ZOOM_SPRITE && zoomLevel > ZoomLevel{ 0 }) + if (imageType & IMAGE_TYPE_REMAP) { - imageId = imageId.WithIndex(imageId.GetIndex() - g1->zoomed_offset); - g1 = GfxGetG1Element(imageId); - if (g1 == nullptr || g1->flags & G1_FLAG_NO_ZOOM_DRAW) - { - return false; - } - zoomLevel = zoomLevel - 1; - interactionPoint.x >>= 1; - interactionPoint.y >>= 1; - origin.x >>= 1; - origin.y >>= 1; + return paletteMap[*index] != 0; } + + if (imageType & IMAGE_TYPE_TRANSPARENT) + { + return false; + } + + return (*index != 0); } - origin.x += g1->x_offset; - origin.y += g1->y_offset; - interactionPoint -= origin; - - if (interactionPoint.x < 0 || interactionPoint.y < 0 || interactionPoint.x >= g1->width || interactionPoint.y >= g1->height) + /** + * rct2: 0x0067933B, 0x00679788, 0x00679C4A, 0x0067A117 + */ + static bool IsPixelPresentRLE(const uint8_t* imgData, const int32_t x, const int32_t y) { + uint16_t lineOffset; + std::memcpy(&lineOffset, &imgData[y * sizeof(uint16_t)], sizeof(uint16_t)); + const uint8_t* data8 = imgData + lineOffset; + + bool lastDataLine = false; + while (!lastDataLine) + { + int32_t numPixels = *data8++; + uint8_t pixelRunStart = *data8++; + lastDataLine = numPixels & 0x80; + numPixels &= 0x7F; + data8 += numPixels; + + if (pixelRunStart <= x && x < pixelRunStart + numPixels) + return true; + } return false; } - if (g1->flags & G1_FLAG_RLE_COMPRESSION) + /** + * rct2: 0x00679074 + */ + static bool IsSpriteInteractedWithPaletteSet( + DrawPixelInfo& dpi, ImageId imageId, const ScreenCoordsXY& coords, const PaletteMap& paletteMap, + const uint8_t imageType) { - return IsPixelPresentRLE(g1->offset, interactionPoint.x, interactionPoint.y); - } + PROFILED_FUNCTION(); - if (!(g1->flags & G1_FLAG_1)) - { - return IsPixelPresentBMP(imageType, g1, interactionPoint.x, interactionPoint.y, paletteMap); - } - - Guard::Assert(false, "Invalid image type encountered."); - return false; -} - -/** - * - * rct2: 0x00679023 - */ - -static bool IsSpriteInteractedWith(DrawPixelInfo& dpi, ImageId imageId, const ScreenCoordsXY& coords) -{ - PROFILED_FUNCTION(); - - auto paletteMap = PaletteMap::GetDefault(); - uint8_t imageType; - if (imageId.HasPrimary() || imageId.IsRemap()) - { - imageType = IMAGE_TYPE_REMAP; - uint8_t paletteIndex; - if (imageId.HasSecondary()) + const G1Element* g1 = GfxGetG1Element(imageId); + if (g1 == nullptr) { - paletteIndex = imageId.GetPrimary(); + return false; + } + + ZoomLevel zoomLevel = dpi.zoom_level; + ScreenCoordsXY interactionPoint{ dpi.WorldX(), dpi.WorldY() }; + ScreenCoordsXY origin = coords; + + if (dpi.zoom_level > ZoomLevel{ 0 }) + { + if (g1->flags & G1_FLAG_NO_ZOOM_DRAW) + { + return false; + } + + while (g1->flags & G1_FLAG_HAS_ZOOM_SPRITE && zoomLevel > ZoomLevel{ 0 }) + { + imageId = imageId.WithIndex(imageId.GetIndex() - g1->zoomed_offset); + g1 = GfxGetG1Element(imageId); + if (g1 == nullptr || g1->flags & G1_FLAG_NO_ZOOM_DRAW) + { + return false; + } + zoomLevel = zoomLevel - 1; + interactionPoint.x >>= 1; + interactionPoint.y >>= 1; + origin.x >>= 1; + origin.y >>= 1; + } + } + + origin.x += g1->x_offset; + origin.y += g1->y_offset; + interactionPoint -= origin; + + if (interactionPoint.x < 0 || interactionPoint.y < 0 || interactionPoint.x >= g1->width + || interactionPoint.y >= g1->height) + { + return false; + } + + if (g1->flags & G1_FLAG_RLE_COMPRESSION) + { + return IsPixelPresentRLE(g1->offset, interactionPoint.x, interactionPoint.y); + } + + if (!(g1->flags & G1_FLAG_1)) + { + return IsPixelPresentBMP(imageType, g1, interactionPoint.x, interactionPoint.y, paletteMap); + } + + Guard::Assert(false, "Invalid image type encountered."); + return false; + } + + /** + * + * rct2: 0x00679023 + */ + + static bool IsSpriteInteractedWith(DrawPixelInfo& dpi, ImageId imageId, const ScreenCoordsXY& coords) + { + PROFILED_FUNCTION(); + + auto paletteMap = PaletteMap::GetDefault(); + uint8_t imageType; + if (imageId.HasPrimary() || imageId.IsRemap()) + { + imageType = IMAGE_TYPE_REMAP; + uint8_t paletteIndex; + if (imageId.HasSecondary()) + { + paletteIndex = imageId.GetPrimary(); + } + else + { + paletteIndex = imageId.GetRemap(); + } + if (auto pm = GetPaletteMapForColour(paletteIndex); pm.has_value()) + { + paletteMap = pm.value(); + } } else { - paletteIndex = imageId.GetRemap(); - } - if (auto pm = GetPaletteMapForColour(paletteIndex); pm.has_value()) - { - paletteMap = pm.value(); + imageType = IMAGE_TYPE_DEFAULT; } + return IsSpriteInteractedWithPaletteSet(dpi, imageId, coords, paletteMap, imageType); } - else + + /** + * + * rct2: 0x0068862C + */ + InteractionInfo SetInteractionInfoFromPaintSession(PaintSession* session, uint32_t viewFlags, uint16_t filter) { - imageType = IMAGE_TYPE_DEFAULT; - } - return IsSpriteInteractedWithPaletteSet(dpi, imageId, coords, paletteMap, imageType); -} + PROFILED_FUNCTION(); -/** - * - * rct2: 0x0068862C - */ -InteractionInfo SetInteractionInfoFromPaintSession(PaintSession* session, uint32_t viewFlags, uint16_t filter) -{ - PROFILED_FUNCTION(); + InteractionInfo info{}; - InteractionInfo info{}; - - PaintStruct* ps = session->PaintHead; - while (ps != nullptr) - { - PaintStruct* old_ps = ps; - PaintStruct* next_ps = ps; - while (next_ps != nullptr) + PaintStruct* ps = session->PaintHead; + while (ps != nullptr) { - ps = next_ps; - if (IsSpriteInteractedWith(session->DPI, ps->image_id, ps->ScreenPos)) + PaintStruct* old_ps = ps; + PaintStruct* next_ps = ps; + while (next_ps != nullptr) { - if (PSInteractionTypeIsInFilter(ps, filter) - && GetPaintStructVisibility(ps, viewFlags) == VisibilityKind::Visible) + ps = next_ps; + if (IsSpriteInteractedWith(session->DPI, ps->image_id, ps->ScreenPos)) { - info = { ps }; + if (PSInteractionTypeIsInFilter(ps, filter) + && GetPaintStructVisibility(ps, viewFlags) == VisibilityKind::Visible) + { + info = { ps }; + } } + next_ps = ps->Children; } - next_ps = ps->Children; - } #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wnull-dereference" - for (AttachedPaintStruct* attached_ps = ps->Attached; attached_ps != nullptr; attached_ps = attached_ps->NextEntry) - { - if (IsSpriteInteractedWith(session->DPI, attached_ps->image_id, ps->ScreenPos + attached_ps->RelativePos)) + for (AttachedPaintStruct* attached_ps = ps->Attached; attached_ps != nullptr; attached_ps = attached_ps->NextEntry) { - if (PSInteractionTypeIsInFilter(ps, filter) - && GetPaintStructVisibility(ps, viewFlags) == VisibilityKind::Visible) + if (IsSpriteInteractedWith(session->DPI, attached_ps->image_id, ps->ScreenPos + attached_ps->RelativePos)) { - info = { ps }; + if (PSInteractionTypeIsInFilter(ps, filter) + && GetPaintStructVisibility(ps, viewFlags) == VisibilityKind::Visible) + { + info = { ps }; + } } } - } #pragma GCC diagnostic pop - ps = old_ps->NextQuadrantEntry; - } - return info; -} - -/** - * - * rct2: 0x00685ADC - * screenX: eax - * screenY: ebx - * flags: edx - * x: ax - * y: cx - * interactionType: bl - * tileElement: edx - * viewport: edi - */ -InteractionInfo GetMapCoordinatesFromPos(const ScreenCoordsXY& screenCoords, int32_t flags) -{ - auto* windowMgr = GetContext()->GetUiContext()->GetWindowManager(); - WindowBase* window = windowMgr->FindFromPoint(screenCoords); - return GetMapCoordinatesFromPosWindow(window, screenCoords, flags); -} - -InteractionInfo GetMapCoordinatesFromPosWindow(WindowBase* window, const ScreenCoordsXY& screenCoords, int32_t flags) -{ - InteractionInfo info{}; - if (window == nullptr || window->viewport == nullptr) - { + ps = old_ps->NextQuadrantEntry; + } return info; } - Viewport* viewport = window->viewport; - auto viewLoc = screenCoords; - viewLoc -= viewport->pos; - if (viewLoc.x >= 0 && viewLoc.x < static_cast(viewport->width) && viewLoc.y >= 0 - && viewLoc.y < static_cast(viewport->height)) + /** + * + * rct2: 0x00685ADC + * screenX: eax + * screenY: ebx + * flags: edx + * x: ax + * y: cx + * interactionType: bl + * tileElement: edx + * viewport: edi + */ + InteractionInfo GetMapCoordinatesFromPos(const ScreenCoordsXY& screenCoords, int32_t flags) { - viewLoc.x = viewport->zoom.ApplyTo(viewLoc.x); - viewLoc.y = viewport->zoom.ApplyTo(viewLoc.y); - viewLoc += viewport->viewPos; - if (viewport->zoom > ZoomLevel{ 0 }) - { - viewLoc.x &= viewport->zoom.ApplyTo(0xFFFFFFFF) & 0xFFFFFFFF; - viewLoc.y &= viewport->zoom.ApplyTo(0xFFFFFFFF) & 0xFFFFFFFF; - } - DrawPixelInfo dpi; - dpi.zoom_level = viewport->zoom; - dpi.x = viewport->zoom.ApplyInversedTo(viewLoc.x); - dpi.y = viewport->zoom.ApplyInversedTo(viewLoc.y); - dpi.height = 1; - dpi.width = 1; - - PaintSession* session = PaintSessionAlloc(dpi, viewport->flags, viewport->rotation); - PaintSessionGenerate(*session); - PaintSessionArrange(*session); - info = SetInteractionInfoFromPaintSession(session, viewport->flags, flags & 0xFFFF); - PaintSessionFree(session); + auto* windowMgr = GetContext()->GetUiContext()->GetWindowManager(); + WindowBase* window = windowMgr->FindFromPoint(screenCoords); + return GetMapCoordinatesFromPosWindow(window, screenCoords, flags); } - return info; -} -/** - * screenRect represents 2D map coordinates at zoom 0. - */ -void ViewportInvalidate(const Viewport* viewport, const ScreenRect& screenRect) -{ - PROFILED_FUNCTION(); - - // if unknown viewport visibility, use the containing window to discover the status - if (viewport->visibility == VisibilityCache::Unknown) + InteractionInfo GetMapCoordinatesFromPosWindow(WindowBase* window, const ScreenCoordsXY& screenCoords, int32_t flags) { - auto windowManager = GetContext()->GetUiContext()->GetWindowManager(); - auto owner = windowManager->GetOwner(viewport); - if (owner != nullptr && owner->classification != WindowClass::MainWindow) + InteractionInfo info{}; + if (window == nullptr || window->viewport == nullptr) { - // note, window_is_visible will update viewport->visibility, so this should have a low hit count - if (!WindowIsVisible(*owner)) + return info; + } + + Viewport* viewport = window->viewport; + auto viewLoc = screenCoords; + viewLoc -= viewport->pos; + if (viewLoc.x >= 0 && viewLoc.x < static_cast(viewport->width) && viewLoc.y >= 0 + && viewLoc.y < static_cast(viewport->height)) + { + viewLoc.x = viewport->zoom.ApplyTo(viewLoc.x); + viewLoc.y = viewport->zoom.ApplyTo(viewLoc.y); + viewLoc += viewport->viewPos; + if (viewport->zoom > ZoomLevel{ 0 }) { - return; + viewLoc.x &= viewport->zoom.ApplyTo(0xFFFFFFFF) & 0xFFFFFFFF; + viewLoc.y &= viewport->zoom.ApplyTo(0xFFFFFFFF) & 0xFFFFFFFF; + } + DrawPixelInfo dpi; + dpi.zoom_level = viewport->zoom; + dpi.x = viewport->zoom.ApplyInversedTo(viewLoc.x); + dpi.y = viewport->zoom.ApplyInversedTo(viewLoc.y); + dpi.height = 1; + dpi.width = 1; + + PaintSession* session = PaintSessionAlloc(dpi, viewport->flags, viewport->rotation); + PaintSessionGenerate(*session); + PaintSessionArrange(*session); + info = SetInteractionInfoFromPaintSession(session, viewport->flags, flags & 0xFFFF); + PaintSessionFree(session); + } + return info; + } + + /** + * screenRect represents 2D map coordinates at zoom 0. + */ + void ViewportInvalidate(const Viewport* viewport, const ScreenRect& screenRect) + { + PROFILED_FUNCTION(); + + // if unknown viewport visibility, use the containing window to discover the status + if (viewport->visibility == VisibilityCache::Unknown) + { + auto windowManager = GetContext()->GetUiContext()->GetWindowManager(); + auto owner = windowManager->GetOwner(viewport); + if (owner != nullptr && owner->classification != WindowClass::MainWindow) + { + // note, window_is_visible will update viewport->visibility, so this should have a low hit count + if (!WindowIsVisible(*owner)) + { + return; + } } } + + if (viewport->visibility == VisibilityCache::Covered) + return; + + auto zoom = viewport->zoom; + auto viewPos = viewport->viewPos; + auto viewportScreenPos = viewport->pos; + + ScreenRect invalidRect = { { zoom.ApplyInversedTo(screenRect.GetLeft() - viewPos.x), + zoom.ApplyInversedTo(screenRect.GetTop() - viewPos.y) }, + { zoom.ApplyInversedTo(screenRect.GetRight() - viewPos.x), + zoom.ApplyInversedTo(screenRect.GetBottom() - viewPos.y) } }; + + if (invalidRect.GetTop() >= viewport->height || invalidRect.GetBottom() <= 0 || invalidRect.GetLeft() >= viewport->width + || invalidRect.GetRight() <= 0) + { + return; + } + invalidRect.Point1 += viewportScreenPos; + invalidRect.Point2 += viewportScreenPos; + GfxSetDirtyBlocks(invalidRect); } - if (viewport->visibility == VisibilityCache::Covered) - return; - - auto zoom = viewport->zoom; - auto viewPos = viewport->viewPos; - auto viewportScreenPos = viewport->pos; - - ScreenRect invalidRect = { - { zoom.ApplyInversedTo(screenRect.GetLeft() - viewPos.x), zoom.ApplyInversedTo(screenRect.GetTop() - viewPos.y) }, - { zoom.ApplyInversedTo(screenRect.GetRight() - viewPos.x), zoom.ApplyInversedTo(screenRect.GetBottom() - viewPos.y) } - }; - - if (invalidRect.GetTop() >= viewport->height || invalidRect.GetBottom() <= 0 || invalidRect.GetLeft() >= viewport->width - || invalidRect.GetRight() <= 0) + static Viewport* ViewportFindFromPoint(const ScreenCoordsXY& screenCoords) { - return; - } - invalidRect.Point1 += viewportScreenPos; - invalidRect.Point2 += viewportScreenPos; - GfxSetDirtyBlocks(invalidRect); -} + auto* windowMgr = GetContext()->GetUiContext()->GetWindowManager(); + WindowBase* w = windowMgr->FindFromPoint(screenCoords); + if (w == nullptr) + return nullptr; -static Viewport* ViewportFindFromPoint(const ScreenCoordsXY& screenCoords) -{ - auto* windowMgr = GetContext()->GetUiContext()->GetWindowManager(); - WindowBase* w = windowMgr->FindFromPoint(screenCoords); - if (w == nullptr) - return nullptr; - - Viewport* viewport = w->viewport; - if (viewport == nullptr) - return nullptr; - - if (viewport->ContainsScreen(screenCoords)) - return viewport; - - return nullptr; -} - -/** - * - * rct2: 0x00688972 - * In: - * screen_x: eax - * screen_y: ebx - * Out: - * x: ax - * y: bx - * tile_element: edx ? - * viewport: edi - */ -std::optional ScreenGetMapXY(const ScreenCoordsXY& screenCoords, Viewport** viewport) -{ - auto* windowMgr = GetContext()->GetUiContext()->GetWindowManager(); - - // This will get the tile location but we will need the more accuracy - WindowBase* window = windowMgr->FindFromPoint(screenCoords); - if (window == nullptr || window->viewport == nullptr) - { - return std::nullopt; - } - auto myViewport = window->viewport; - auto info = GetMapCoordinatesFromPosWindow(window, screenCoords, EnumsToFlags(ViewportInteractionItem::Terrain)); - if (info.interactionType == ViewportInteractionItem::None) - { - return std::nullopt; - } - - auto start_vp_pos = myViewport->ScreenToViewportCoord(screenCoords); - CoordsXY cursorMapPos = info.Loc.ToTileCentre(); - - // Iterates the cursor location to work out exactly where on the tile it is - for (int32_t i = 0; i < 5; i++) - { - int32_t z = TileElementHeight(cursorMapPos); - cursorMapPos = ViewportPosToMapPos(start_vp_pos, z, myViewport->rotation); - cursorMapPos.x = std::clamp(cursorMapPos.x, info.Loc.x, info.Loc.x + 31); - cursorMapPos.y = std::clamp(cursorMapPos.y, info.Loc.y, info.Loc.y + 31); - } - - if (viewport != nullptr) - *viewport = myViewport; - - return cursorMapPos; -} - -/** - * - * rct2: 0x006894D4 - */ -std::optional ScreenGetMapXYWithZ(const ScreenCoordsXY& screenCoords, int32_t z) -{ - Viewport* viewport = ViewportFindFromPoint(screenCoords); - if (viewport == nullptr) - { - return std::nullopt; - } - - auto vpCoords = viewport->ScreenToViewportCoord(screenCoords); - auto mapPosition = ViewportPosToMapPos(vpCoords, z, viewport->rotation); - if (!MapIsLocationValid(mapPosition)) - { - return std::nullopt; - } - - return mapPosition; -} - -/** - * - * rct2: 0x00689604 - */ -std::optional ScreenGetMapXYQuadrant(const ScreenCoordsXY& screenCoords, uint8_t* quadrant) -{ - auto mapCoords = ScreenGetMapXY(screenCoords, nullptr); - if (!mapCoords.has_value()) - return std::nullopt; - - *quadrant = MapGetTileQuadrant(*mapCoords); - return mapCoords->ToTileStart(); -} - -/** - * - * rct2: 0x0068964B - */ -std::optional ScreenGetMapXYQuadrantWithZ(const ScreenCoordsXY& screenCoords, int32_t z, uint8_t* quadrant) -{ - auto mapCoords = ScreenGetMapXYWithZ(screenCoords, z); - if (!mapCoords.has_value()) - return std::nullopt; - - *quadrant = MapGetTileQuadrant(*mapCoords); - return mapCoords->ToTileStart(); -} - -/** - * - * rct2: 0x00689692 - */ -std::optional ScreenGetMapXYSide(const ScreenCoordsXY& screenCoords, uint8_t* side) -{ - auto mapCoords = ScreenGetMapXY(screenCoords, nullptr); - if (!mapCoords.has_value()) - return std::nullopt; - - *side = MapGetTileSide(*mapCoords); - return mapCoords->ToTileStart(); -} - -/** - * - * rct2: 0x006896DC - */ -std::optional ScreenGetMapXYSideWithZ(const ScreenCoordsXY& screenCoords, int32_t z, uint8_t* side) -{ - auto mapCoords = ScreenGetMapXYWithZ(screenCoords, z); - if (!mapCoords.has_value()) - return std::nullopt; - - *side = MapGetTileSide(*mapCoords); - return mapCoords->ToTileStart(); -} - -ScreenCoordsXY Translate3DTo2DWithZ(int32_t rotation, const CoordsXYZ& pos) -{ - auto rotated = pos.Rotate(rotation); - // Use right shift to avoid issues like #9301 - return ScreenCoordsXY{ rotated.y - rotated.x, ((rotated.x + rotated.y) >> 1) - pos.z }; -} - -/** - * Get current viewport rotation. - * - * If an invalid rotation is detected and DEBUG_LEVEL_1 is enabled, an error - * will be reported. - * - * @returns rotation in range 0-3 (inclusive) - */ -uint8_t GetCurrentRotation() -{ - auto* viewport = ViewportGetMain(); - if (viewport == nullptr) - { - LOG_VERBOSE("No viewport found! Will return 0."); - return 0; - } - uint8_t rotation = viewport->rotation; - uint8_t rotation_masked = rotation & 3; -#if defined(DEBUG_LEVEL_1) && DEBUG_LEVEL_1 - if (rotation != rotation_masked) - { - LOG_ERROR( - "Found wrong rotation %d! Will return %d instead.", static_cast(rotation), - static_cast(rotation_masked)); - } -#endif // DEBUG_LEVEL_1 - return rotation_masked; -} - -int32_t GetHeightMarkerOffset() -{ - // Height labels in units - if (Config::Get().general.ShowHeightAsUnits) - return 0; - - // Height labels in feet - if (Config::Get().general.MeasurementFormat == MeasurementFormat::Imperial) - return 1 * 256; - - // Height labels in metres - return 2 * 256; -} - -void ViewportSetSavedView() -{ - WindowBase* w = WindowGetMain(); - if (w != nullptr) - { Viewport* viewport = w->viewport; - auto& gameState = GetGameState(); + if (viewport == nullptr) + return nullptr; - gameState.SavedView = ScreenCoordsXY{ viewport->ViewWidth() / 2, viewport->ViewHeight() / 2 } + viewport->viewPos; + if (viewport->ContainsScreen(screenCoords)) + return viewport; - gameState.SavedViewZoom = viewport->zoom; - gameState.SavedViewRotation = viewport->rotation; + return nullptr; } -} + + /** + * + * rct2: 0x00688972 + * In: + * screen_x: eax + * screen_y: ebx + * Out: + * x: ax + * y: bx + * tile_element: edx ? + * viewport: edi + */ + std::optional ScreenGetMapXY(const ScreenCoordsXY& screenCoords, Viewport** viewport) + { + auto* windowMgr = GetContext()->GetUiContext()->GetWindowManager(); + + // This will get the tile location but we will need the more accuracy + WindowBase* window = windowMgr->FindFromPoint(screenCoords); + if (window == nullptr || window->viewport == nullptr) + { + return std::nullopt; + } + auto myViewport = window->viewport; + auto info = GetMapCoordinatesFromPosWindow(window, screenCoords, EnumsToFlags(ViewportInteractionItem::Terrain)); + if (info.interactionType == ViewportInteractionItem::None) + { + return std::nullopt; + } + + auto start_vp_pos = myViewport->ScreenToViewportCoord(screenCoords); + CoordsXY cursorMapPos = info.Loc.ToTileCentre(); + + // Iterates the cursor location to work out exactly where on the tile it is + for (int32_t i = 0; i < 5; i++) + { + int32_t z = TileElementHeight(cursorMapPos); + cursorMapPos = ViewportPosToMapPos(start_vp_pos, z, myViewport->rotation); + cursorMapPos.x = std::clamp(cursorMapPos.x, info.Loc.x, info.Loc.x + 31); + cursorMapPos.y = std::clamp(cursorMapPos.y, info.Loc.y, info.Loc.y + 31); + } + + if (viewport != nullptr) + *viewport = myViewport; + + return cursorMapPos; + } + + /** + * + * rct2: 0x006894D4 + */ + std::optional ScreenGetMapXYWithZ(const ScreenCoordsXY& screenCoords, int32_t z) + { + Viewport* viewport = ViewportFindFromPoint(screenCoords); + if (viewport == nullptr) + { + return std::nullopt; + } + + auto vpCoords = viewport->ScreenToViewportCoord(screenCoords); + auto mapPosition = ViewportPosToMapPos(vpCoords, z, viewport->rotation); + if (!MapIsLocationValid(mapPosition)) + { + return std::nullopt; + } + + return mapPosition; + } + + /** + * + * rct2: 0x00689604 + */ + std::optional ScreenGetMapXYQuadrant(const ScreenCoordsXY& screenCoords, uint8_t* quadrant) + { + auto mapCoords = ScreenGetMapXY(screenCoords, nullptr); + if (!mapCoords.has_value()) + return std::nullopt; + + *quadrant = MapGetTileQuadrant(*mapCoords); + return mapCoords->ToTileStart(); + } + + /** + * + * rct2: 0x0068964B + */ + std::optional ScreenGetMapXYQuadrantWithZ(const ScreenCoordsXY& screenCoords, int32_t z, uint8_t* quadrant) + { + auto mapCoords = ScreenGetMapXYWithZ(screenCoords, z); + if (!mapCoords.has_value()) + return std::nullopt; + + *quadrant = MapGetTileQuadrant(*mapCoords); + return mapCoords->ToTileStart(); + } + + /** + * + * rct2: 0x00689692 + */ + std::optional ScreenGetMapXYSide(const ScreenCoordsXY& screenCoords, uint8_t* side) + { + auto mapCoords = ScreenGetMapXY(screenCoords, nullptr); + if (!mapCoords.has_value()) + return std::nullopt; + + *side = MapGetTileSide(*mapCoords); + return mapCoords->ToTileStart(); + } + + /** + * + * rct2: 0x006896DC + */ + std::optional ScreenGetMapXYSideWithZ(const ScreenCoordsXY& screenCoords, int32_t z, uint8_t* side) + { + auto mapCoords = ScreenGetMapXYWithZ(screenCoords, z); + if (!mapCoords.has_value()) + return std::nullopt; + + *side = MapGetTileSide(*mapCoords); + return mapCoords->ToTileStart(); + } + + ScreenCoordsXY Translate3DTo2DWithZ(int32_t rotation, const CoordsXYZ& pos) + { + auto rotated = pos.Rotate(rotation); + // Use right shift to avoid issues like #9301 + return ScreenCoordsXY{ rotated.y - rotated.x, ((rotated.x + rotated.y) >> 1) - pos.z }; + } + + /** + * Get current viewport rotation. + * + * If an invalid rotation is detected and DEBUG_LEVEL_1 is enabled, an error + * will be reported. + * + * @returns rotation in range 0-3 (inclusive) + */ + uint8_t GetCurrentRotation() + { + auto* viewport = ViewportGetMain(); + if (viewport == nullptr) + { + LOG_VERBOSE("No viewport found! Will return 0."); + return 0; + } + uint8_t rotation = viewport->rotation; + uint8_t rotation_masked = rotation & 3; +#if defined(DEBUG_LEVEL_1) && DEBUG_LEVEL_1 + if (rotation != rotation_masked) + { + LOG_ERROR( + "Found wrong rotation %d! Will return %d instead.", static_cast(rotation), + static_cast(rotation_masked)); + } +#endif // DEBUG_LEVEL_1 + return rotation_masked; + } + + int32_t GetHeightMarkerOffset() + { + // Height labels in units + if (Config::Get().general.ShowHeightAsUnits) + return 0; + + // Height labels in feet + if (Config::Get().general.MeasurementFormat == MeasurementFormat::Imperial) + return 1 * 256; + + // Height labels in metres + return 2 * 256; + } + + void ViewportSetSavedView() + { + WindowBase* w = WindowGetMain(); + if (w != nullptr) + { + Viewport* viewport = w->viewport; + auto& gameState = GetGameState(); + + gameState.SavedView = ScreenCoordsXY{ viewport->ViewWidth() / 2, viewport->ViewHeight() / 2 } + viewport->viewPos; + + gameState.SavedViewZoom = viewport->zoom; + gameState.SavedViewRotation = viewport->rotation; + } + } +} // namespace OpenRCT2 ZoomLevel ZoomLevel::min() { diff --git a/src/openrct2/interface/Viewport.h b/src/openrct2/interface/Viewport.h index 0d9c50c452..3b43670f68 100644 --- a/src/openrct2/interface/Viewport.h +++ b/src/openrct2/interface/Viewport.h @@ -20,54 +20,58 @@ struct PaintSession; struct PaintStruct; struct DrawPixelInfo; struct TileElement; -struct WindowBase; struct EntityBase; struct Guest; struct Staff; struct PaintEntry; -// Flags must currenly retain their values to avoid breaking plugins. -// Values can be changed when plugins move to using named constants. -enum : uint32_t +namespace OpenRCT2 { - VIEWPORT_FLAG_NONE = 0u, + struct WindowBase; - VIEWPORT_FLAG_GRIDLINES = (1u << 7), - VIEWPORT_FLAG_UNDERGROUND_INSIDE = (1u << 0), - VIEWPORT_FLAG_HIDE_BASE = (1u << 12), - VIEWPORT_FLAG_HIDE_VERTICAL = (1u << 13), + // Flags must currenly retain their values to avoid breaking plugins. + // Values can be changed when plugins move to using named constants. + enum : uint32_t + { + VIEWPORT_FLAG_NONE = 0u, - VIEWPORT_FLAG_SOUND_ON = (1u << 10), - VIEWPORT_FLAG_LAND_OWNERSHIP = (1u << 8), - VIEWPORT_FLAG_CONSTRUCTION_RIGHTS = (1u << 9), - VIEWPORT_FLAG_HIDE_ENTITIES = (1u << 14), - VIEWPORT_FLAG_CLIP_VIEW = (1u << 17), - VIEWPORT_FLAG_HIGHLIGHT_PATH_ISSUES = (1u << 18), - VIEWPORT_FLAG_TRANSPARENT_BACKGROUND = (1u << 19), + VIEWPORT_FLAG_GRIDLINES = (1u << 7), + VIEWPORT_FLAG_UNDERGROUND_INSIDE = (1u << 0), + VIEWPORT_FLAG_HIDE_BASE = (1u << 12), + VIEWPORT_FLAG_HIDE_VERTICAL = (1u << 13), - VIEWPORT_FLAG_LAND_HEIGHTS = (1u << 4), - VIEWPORT_FLAG_TRACK_HEIGHTS = (1u << 5), - VIEWPORT_FLAG_PATH_HEIGHTS = (1u << 6), + VIEWPORT_FLAG_SOUND_ON = (1u << 10), + VIEWPORT_FLAG_LAND_OWNERSHIP = (1u << 8), + VIEWPORT_FLAG_CONSTRUCTION_RIGHTS = (1u << 9), + VIEWPORT_FLAG_HIDE_ENTITIES = (1u << 14), + VIEWPORT_FLAG_CLIP_VIEW = (1u << 17), + VIEWPORT_FLAG_HIGHLIGHT_PATH_ISSUES = (1u << 18), + VIEWPORT_FLAG_TRANSPARENT_BACKGROUND = (1u << 19), - VIEWPORT_FLAG_HIDE_RIDES = (1u << 1), - VIEWPORT_FLAG_HIDE_VEHICLES = (1u << 20), - VIEWPORT_FLAG_HIDE_VEGETATION = (1u << 21), - VIEWPORT_FLAG_HIDE_SCENERY = (1u << 2), - VIEWPORT_FLAG_HIDE_PATHS = (1u << 16), - VIEWPORT_FLAG_HIDE_SUPPORTS = (1u << 3), - VIEWPORT_FLAG_HIDE_GUESTS = (1u << 11), - VIEWPORT_FLAG_HIDE_STAFF = (1u << 23), + VIEWPORT_FLAG_LAND_HEIGHTS = (1u << 4), + VIEWPORT_FLAG_TRACK_HEIGHTS = (1u << 5), + VIEWPORT_FLAG_PATH_HEIGHTS = (1u << 6), - VIEWPORT_FLAG_INVISIBLE_RIDES = (1u << 24), - VIEWPORT_FLAG_INVISIBLE_VEHICLES = (1u << 25), - VIEWPORT_FLAG_INVISIBLE_VEGETATION = (1u << 26), - VIEWPORT_FLAG_INVISIBLE_SCENERY = (1u << 27), - VIEWPORT_FLAG_INVISIBLE_PATHS = (1u << 28), - VIEWPORT_FLAG_INVISIBLE_SUPPORTS = (1u << 29), + VIEWPORT_FLAG_HIDE_RIDES = (1u << 1), + VIEWPORT_FLAG_HIDE_VEHICLES = (1u << 20), + VIEWPORT_FLAG_HIDE_VEGETATION = (1u << 21), + VIEWPORT_FLAG_HIDE_SCENERY = (1u << 2), + VIEWPORT_FLAG_HIDE_PATHS = (1u << 16), + VIEWPORT_FLAG_HIDE_SUPPORTS = (1u << 3), + VIEWPORT_FLAG_HIDE_GUESTS = (1u << 11), + VIEWPORT_FLAG_HIDE_STAFF = (1u << 23), - VIEWPORT_FLAG_INDEPEDENT_ROTATION = (1u << 30), - VIEWPORT_FLAG_RENDERING_INHIBITED = (1u << 31), -}; + VIEWPORT_FLAG_INVISIBLE_RIDES = (1u << 24), + VIEWPORT_FLAG_INVISIBLE_VEHICLES = (1u << 25), + VIEWPORT_FLAG_INVISIBLE_VEGETATION = (1u << 26), + VIEWPORT_FLAG_INVISIBLE_SCENERY = (1u << 27), + VIEWPORT_FLAG_INVISIBLE_PATHS = (1u << 28), + VIEWPORT_FLAG_INVISIBLE_SUPPORTS = (1u << 29), + + VIEWPORT_FLAG_INDEPEDENT_ROTATION = (1u << 30), + VIEWPORT_FLAG_RENDERING_INHIBITED = (1u << 31), + }; +} // namespace OpenRCT2 enum class VisibilityKind { @@ -103,75 +107,78 @@ enum class ViewportVisibility : uint8_t UndergroundViewGhostOff = 5, }; -constexpr uint16_t ViewportInteractionItemAll = std::numeric_limits::max(); - -struct InteractionInfo +namespace OpenRCT2 { - InteractionInfo() = default; - InteractionInfo(const PaintStruct* ps); - CoordsXY Loc; - TileElement* Element{}; - EntityBase* Entity{}; - ViewportInteractionItem interactionType = ViewportInteractionItem::None; -}; + constexpr uint16_t ViewportInteractionItemAll = std::numeric_limits::max(); -constexpr int32_t kMaxViewportCount = kWindowLimitMax; + struct InteractionInfo + { + InteractionInfo() = default; + InteractionInfo(const PaintStruct* ps); + CoordsXY Loc; + TileElement* Element{}; + EntityBase* Entity{}; + ViewportInteractionItem interactionType = ViewportInteractionItem::None; + }; -/** - * A reference counter for whether something is forcing the grid lines to show. When the counter - * is decremented to 0, the grid lines are hidden. - */ -extern uint8_t gShowGridLinesRefCount; -extern uint8_t gShowLandRightsRefCount; -extern uint8_t gShowConstructionRightsRefCount; + constexpr int32_t kMaxViewportCount = kWindowLimitMax; -// rct2: 0x014234BC -extern Viewport* g_music_tracking_viewport; + /** + * A reference counter for whether something is forcing the grid lines to show. When the counter + * is decremented to 0, the grid lines are hidden. + */ + extern uint8_t gShowGridLinesRefCount; + extern uint8_t gShowLandRightsRefCount; + extern uint8_t gShowConstructionRightsRefCount; -void ViewportInitAll(); -std::optional centre_2d_coordinates(const CoordsXYZ& loc, Viewport* viewport); -void ViewportCreate(WindowBase* w, const ScreenCoordsXY& screenCoords, int32_t width, int32_t height, const Focus& focus); -void ViewportRemove(Viewport* viewport); + // rct2: 0x014234BC + extern Viewport* g_music_tracking_viewport; -void ViewportsInvalidate(int32_t x, int32_t y, int32_t z0, int32_t z1, ZoomLevel maxZoom); -void ViewportsInvalidate(const CoordsXYZ& pos, int32_t width, int32_t minHeight, int32_t maxHeight, ZoomLevel maxZoom); -void ViewportsInvalidate(const ScreenRect& screenRect, ZoomLevel maxZoom = ZoomLevel{ -1 }); -void ViewportUpdatePosition(WindowBase* window); -void ViewportUpdateSmartFollowGuest(WindowBase* window, const Guest& peep); -void ViewportRotateSingle(WindowBase* window, int32_t direction); -void ViewportRotateAll(int32_t direction); -void ViewportRender(DrawPixelInfo& dpi, const Viewport* viewport); + void ViewportInitAll(); + std::optional centre_2d_coordinates(const CoordsXYZ& loc, Viewport* viewport); + void ViewportCreate(WindowBase* w, const ScreenCoordsXY& screenCoords, int32_t width, int32_t height, const Focus& focus); + void ViewportRemove(Viewport* viewport); -CoordsXYZ ViewportAdjustForMapHeight(const ScreenCoordsXY& startCoords, uint8_t rotation); + void ViewportsInvalidate(int32_t x, int32_t y, int32_t z0, int32_t z1, ZoomLevel maxZoom); + void ViewportsInvalidate(const CoordsXYZ& pos, int32_t width, int32_t minHeight, int32_t maxHeight, ZoomLevel maxZoom); + void ViewportsInvalidate(const ScreenRect& screenRect, ZoomLevel maxZoom = ZoomLevel{ -1 }); + void ViewportUpdatePosition(WindowBase* window); + void ViewportUpdateSmartFollowGuest(WindowBase* window, const Guest& peep); + void ViewportRotateSingle(WindowBase* window, int32_t direction); + void ViewportRotateAll(int32_t direction); + void ViewportRender(DrawPixelInfo& dpi, const Viewport* viewport); -CoordsXY ViewportPosToMapPos(const ScreenCoordsXY& coords, int32_t z, uint8_t rotation); -std::optional ScreenPosToMapPos(const ScreenCoordsXY& screenCoords, int32_t* direction); + CoordsXYZ ViewportAdjustForMapHeight(const ScreenCoordsXY& startCoords, uint8_t rotation); -void ShowGridlines(); -void HideGridlines(); -void ShowLandRights(); -void HideLandRights(); -void ShowConstructionRights(); -void HideConstructionRights(); -void ViewportSetVisibility(ViewportVisibility mode); + CoordsXY ViewportPosToMapPos(const ScreenCoordsXY& coords, int32_t z, uint8_t rotation); + std::optional ScreenPosToMapPos(const ScreenCoordsXY& screenCoords, int32_t* direction); -InteractionInfo GetMapCoordinatesFromPos(const ScreenCoordsXY& screenCoords, int32_t flags); -InteractionInfo GetMapCoordinatesFromPosWindow(WindowBase* window, const ScreenCoordsXY& screenCoords, int32_t flags); + void ShowGridlines(); + void HideGridlines(); + void ShowLandRights(); + void HideLandRights(); + void ShowConstructionRights(); + void HideConstructionRights(); + void ViewportSetVisibility(ViewportVisibility mode); -InteractionInfo SetInteractionInfoFromPaintSession(PaintSession* session, uint32_t viewFlags, uint16_t filter); + InteractionInfo GetMapCoordinatesFromPos(const ScreenCoordsXY& screenCoords, int32_t flags); + InteractionInfo GetMapCoordinatesFromPosWindow(WindowBase* window, const ScreenCoordsXY& screenCoords, int32_t flags); -std::optional ScreenGetMapXY(const ScreenCoordsXY& screenCoords, Viewport** viewport); -std::optional ScreenGetMapXYWithZ(const ScreenCoordsXY& screenCoords, int32_t z); -std::optional ScreenGetMapXYQuadrant(const ScreenCoordsXY& screenCoords, uint8_t* quadrant); -std::optional ScreenGetMapXYQuadrantWithZ(const ScreenCoordsXY& screenCoords, int32_t z, uint8_t* quadrant); -std::optional ScreenGetMapXYSide(const ScreenCoordsXY& screenCoords, uint8_t* side); -std::optional ScreenGetMapXYSideWithZ(const ScreenCoordsXY& screenCoords, int32_t z, uint8_t* side); + InteractionInfo SetInteractionInfoFromPaintSession(PaintSession* session, uint32_t viewFlags, uint16_t filter); -ScreenCoordsXY Translate3DTo2DWithZ(int32_t rotation, const CoordsXYZ& pos); + std::optional ScreenGetMapXY(const ScreenCoordsXY& screenCoords, Viewport** viewport); + std::optional ScreenGetMapXYWithZ(const ScreenCoordsXY& screenCoords, int32_t z); + std::optional ScreenGetMapXYQuadrant(const ScreenCoordsXY& screenCoords, uint8_t* quadrant); + std::optional ScreenGetMapXYQuadrantWithZ(const ScreenCoordsXY& screenCoords, int32_t z, uint8_t* quadrant); + std::optional ScreenGetMapXYSide(const ScreenCoordsXY& screenCoords, uint8_t* side); + std::optional ScreenGetMapXYSideWithZ(const ScreenCoordsXY& screenCoords, int32_t z, uint8_t* side); -uint8_t GetCurrentRotation(); -int32_t GetHeightMarkerOffset(); + ScreenCoordsXY Translate3DTo2DWithZ(int32_t rotation, const CoordsXYZ& pos); -void ViewportSetSavedView(); + uint8_t GetCurrentRotation(); + int32_t GetHeightMarkerOffset(); -VisibilityKind GetPaintStructVisibility(const PaintStruct* ps, uint32_t viewFlags); + void ViewportSetSavedView(); + + VisibilityKind GetPaintStructVisibility(const PaintStruct* ps, uint32_t viewFlags); +} // namespace OpenRCT2 diff --git a/src/openrct2/interface/Widget.h b/src/openrct2/interface/Widget.h index d7a1e5a482..e89826f95a 100644 --- a/src/openrct2/interface/Widget.h +++ b/src/openrct2/interface/Widget.h @@ -9,57 +9,156 @@ #pragma once +#include "../core/StringTypes.h" +#include "../drawing/ImageId.hpp" +#include "../localisation/StringIdType.h" +#include "../world/Location.hpp" + #include -struct WindowBase; - -using WidgetIndex = uint16_t; - -constexpr WidgetIndex kWidgetIndexNull = 0xFFFF; - -enum class WindowWidgetType : uint8_t +namespace OpenRCT2 { - Empty = 0, - Frame = 1, - Resize = 2, - ImgBtn = 3, - ColourBtn = 6, - TrnBtn = 7, - Tab = 8, - FlatBtn = 9, - Button = 10, - LabelCentred = 12, // Centred text - TableHeader = 13, // Left-aligned textual button - Label = 14, // Left-aligned text - Spinner = 15, - DropdownMenu = 16, - Viewport = 17, - Groupbox = 19, - Caption = 20, - CloseBox = 21, - Scroll = 22, - Checkbox = 23, - Placeholder = 25, - ProgressBar = 29, - Custom = 28, - TextBox = 27, -}; + using WidgetIndex = uint16_t; + constexpr WidgetIndex kWidgetIndexNull = 0xFFFF; -constexpr uint8_t kCloseButtonWidth = 10; + enum class WindowWidgetType : uint8_t + { + Empty = 0, + Frame = 1, + Resize = 2, + ImgBtn = 3, + ColourBtn = 6, + TrnBtn = 7, + Tab = 8, + FlatBtn = 9, + Button = 10, + LabelCentred = 12, // Centred text + TableHeader = 13, // Left-aligned textual button + Label = 14, // Left-aligned text + Spinner = 15, + DropdownMenu = 16, + Viewport = 17, + Groupbox = 19, + Caption = 20, + CloseBox = 21, + Scroll = 22, + Checkbox = 23, + Placeholder = 25, + ProgressBar = 29, + Custom = 28, + TextBox = 27, + }; -constexpr int32_t kScrollableRowHeight = 12; -constexpr uint8_t kListRowHeight = 12; -constexpr uint8_t kTableCellHeight = 12; -constexpr uint8_t kButtonFaceHeight = 12; -constexpr uint8_t kSpinnerHeight = 12; -constexpr uint8_t kDropdownHeight = 12; + using WidgetFlags = uint32_t; + namespace WIDGET_FLAGS + { + const WidgetFlags TEXT_IS_STRING = 1 << 0; + const WidgetFlags IS_PRESSED = 1 << 2; + const WidgetFlags IS_DISABLED = 1 << 3; + const WidgetFlags TOOLTIP_IS_STRING = 1 << 4; + const WidgetFlags IS_HIDDEN = 1 << 5; + const WidgetFlags IS_HOLDABLE = 1 << 6; + } // namespace WIDGET_FLAGS -constexpr uint16_t kTextInputSize = 1024; -constexpr uint16_t kTopToolbarHeight = 27; + enum + { + SCROLL_HORIZONTAL = (1 << 0), + SCROLL_VERTICAL = (1 << 1), + SCROLL_BOTH = SCROLL_HORIZONTAL | SCROLL_VERTICAL + }; -enum -{ - SCROLL_HORIZONTAL = (1 << 0), - SCROLL_VERTICAL = (1 << 1), - SCROLL_BOTH = SCROLL_HORIZONTAL | SCROLL_VERTICAL -}; + struct Widget + { + WindowWidgetType type{}; + uint8_t colour{}; + int16_t left{}; + int16_t right{}; + int16_t top{}; + int16_t bottom{}; + union + { + uint32_t content; + ImageId image{}; + StringId text; + utf8* string; + }; + StringId tooltip{ STR_NONE }; + + // New properties + WidgetFlags flags{}; + utf8* sztooltip{}; + + int16_t width() const + { + return right - left; + } + + int16_t height() const + { + return bottom - top; + } + + int16_t midX() const + { + return (left + right) / 2; + } + + int16_t midY() const + { + return (top + bottom) / 2; + } + + int16_t textTop() const + { + if (height() >= 10) + return std::max(top, top + (height() / 2) - 5); + + return top - 1; + } + + void moveRight(int32_t amount) + { + left += amount; + right += amount; + } + + void moveDown(int32_t amount) + { + top += amount; + bottom += amount; + } + + void moveTo(ScreenCoordsXY coords) + { + moveRight(coords.x - left); + moveDown(coords.y - top); + } + + void moveToX(int16_t x) + { + moveRight(x - left); + } + + void moveToY(int16_t y) + { + moveDown(y - top); + } + + bool IsVisible() const + { + return !(flags & OpenRCT2::WIDGET_FLAGS::IS_HIDDEN); + } + }; + + constexpr uint8_t kCloseButtonWidth = 10; + + constexpr int32_t kScrollableRowHeight = 12; + constexpr uint8_t kListRowHeight = 12; + constexpr uint8_t kTableCellHeight = 12; + constexpr uint8_t kButtonFaceHeight = 12; + constexpr uint8_t kSpinnerHeight = 12; + constexpr uint8_t kDropdownHeight = 12; + + constexpr uint16_t kTextInputSize = 1024; + constexpr uint16_t kTopToolbarHeight = 27; +} // namespace OpenRCT2 diff --git a/src/openrct2/interface/Window.cpp b/src/openrct2/interface/Window.cpp index 7c2f6f67df..82e1285862 100644 --- a/src/openrct2/interface/Window.cpp +++ b/src/openrct2/interface/Window.cpp @@ -39,22 +39,23 @@ #include #include -using namespace OpenRCT2; +namespace OpenRCT2 +{ -std::list> g_window_list; -WindowBase* gWindowAudioExclusive; + std::list> g_window_list; + WindowBase* gWindowAudioExclusive; -WindowCloseModifier gLastCloseModifier = { { WindowClass::Null, 0 }, CloseWindowModifier::None }; + WindowCloseModifier gLastCloseModifier = { { WindowClass::Null, 0 }, CloseWindowModifier::None }; -uint32_t gWindowUpdateTicks; -colour_t gCurrentWindowColours[3]; + uint32_t gWindowUpdateTicks; + colour_t gCurrentWindowColours[3]; -Tool gCurrentToolId; -WidgetRef gCurrentToolWidget; + Tool gCurrentToolId; + WidgetRef gCurrentToolWidget; -// converted from uint16_t values at 0x009A41EC - 0x009A4230 -// these are percentage coordinates of the viewport to centre to, if a window is obscuring a location, the next is tried -// clang-format off + // converted from uint16_t values at 0x009A41EC - 0x009A4230 + // these are percentage coordinates of the viewport to centre to, if a window is obscuring a location, the next is tried + // clang-format off static constexpr float window_scroll_locations[][2] = { { 0.5f, 0.5f }, { 0.75f, 0.5f }, @@ -74,1262 +75,1267 @@ static constexpr float window_scroll_locations[][2] = { { 0.125f, 0.875f }, { 0.125f, 0.125f }, }; -// clang-format on + // clang-format on -namespace OpenRCT2::WindowCloseFlags -{ - static constexpr uint32_t None = 0; - static constexpr uint32_t CloseSingle = (1 << 0); -} // namespace OpenRCT2::WindowCloseFlags - -static void WindowDrawCore(DrawPixelInfo& dpi, WindowBase& w, int32_t left, int32_t top, int32_t right, int32_t bottom); -static void WindowDrawSingle(DrawPixelInfo& dpi, WindowBase& w, int32_t left, int32_t top, int32_t right, int32_t bottom); - -std::list>::iterator WindowGetIterator(const WindowBase* w) -{ - return std::find_if(g_window_list.begin(), g_window_list.end(), [w](const std::shared_ptr& w2) -> bool { - return w == w2.get(); - }); -} - -void WindowVisitEach(std::function func) -{ - for (auto& w : g_window_list) + namespace WindowCloseFlags { - if (w->flags & WF_DEAD) - continue; - func(w.get()); - } -} + static constexpr uint32_t None = 0; + static constexpr uint32_t CloseSingle = (1 << 0); + } // namespace WindowCloseFlags -void WindowSetFlagForAllViewports(uint32_t viewportFlag, bool enabled) -{ - WindowVisitEach([&](WindowBase* w) { - if (w->viewport != nullptr) - { - if (enabled) - w->viewport->flags |= viewportFlag; - else - w->viewport->flags &= ~viewportFlag; - } - }); -} + static void WindowDrawCore(DrawPixelInfo& dpi, WindowBase& w, int32_t left, int32_t top, int32_t right, int32_t bottom); + static void WindowDrawSingle(DrawPixelInfo& dpi, WindowBase& w, int32_t left, int32_t top, int32_t right, int32_t bottom); -/** - * - * rct2: 0x006ED7B0 - */ -void WindowDispatchUpdateAll() -{ - // gTooltipNotShownTicks++; - WindowVisitEach([&](WindowBase* w) { w->OnUpdate(); }); -} - -void WindowUpdateAllViewports() -{ - WindowVisitEach([&](WindowBase* w) { - if (w->viewport != nullptr && WindowIsVisible(*w)) - { - ViewportUpdatePosition(w); - } - }); -} - -/** - * - * rct2: 0x006E77A1 - */ -void WindowUpdateAll() -{ - // Remove all windows in g_window_list that have the WF_DEAD flag - g_window_list.remove_if([](auto&& w) -> bool { return w->flags & WF_DEAD; }); - - // Periodic update happens every second so 40 ticks. - if (gCurrentRealTimeTicks >= gWindowUpdateTicks) + std::list>::iterator WindowGetIterator(const WindowBase* w) { - gWindowUpdateTicks = gCurrentRealTimeTicks + kGameUpdateFPS; - - WindowVisitEach([](WindowBase* w) { w->OnPeriodicUpdate(); }); + return std::find_if(g_window_list.begin(), g_window_list.end(), [w](const std::shared_ptr& w2) -> bool { + return w == w2.get(); + }); } - // Border flash invalidation - WindowVisitEach([](WindowBase* w) { - if (w->flags & WF_WHITE_BORDER_MASK) - { - w->flags -= WF_WHITE_BORDER_ONE; - if (!(w->flags & WF_WHITE_BORDER_MASK)) - { - w->Invalidate(); - } - } - }); - - auto windowManager = GetContext()->GetUiContext()->GetWindowManager(); - windowManager->UpdateMouseWheel(); -} - -void WindowNotifyLanguageChange() -{ - WindowVisitEach([&](WindowBase* w) { w->OnLanguageChange(); }); -} - -static void WindowCloseSurplus(int32_t cap, WindowClass avoid_classification) -{ - // find the amount of windows that are currently open - auto count = static_cast(g_window_list.size()); - // difference between amount open and cap = amount to close - auto diff = count - kWindowLimitReserved - cap; - for (auto i = 0; i < diff; i++) + void WindowVisitEach(std::function func) { - // iterates through the list until it finds the newest window, or a window that can be closed - WindowBase* foundW{}; for (auto& w : g_window_list) { if (w->flags & WF_DEAD) continue; - if (!(w->flags & (WF_STICK_TO_BACK | WF_STICK_TO_FRONT | WF_NO_AUTO_CLOSE))) + func(w.get()); + } + } + + void WindowSetFlagForAllViewports(uint32_t viewportFlag, bool enabled) + { + WindowVisitEach([&](WindowBase* w) { + if (w->viewport != nullptr) { - foundW = w.get(); - break; + if (enabled) + w->viewport->flags |= viewportFlag; + else + w->viewport->flags &= ~viewportFlag; } - } - // skip window if window matches specified WindowClass (as user may be modifying via options) - if (avoid_classification != WindowClass::Null && foundW != nullptr && foundW->classification == avoid_classification) - { - continue; - } - WindowClose(*foundW); + }); } -} -/* - * Changes the maximum amount of windows allowed - */ -void WindowSetWindowLimit(int32_t value) -{ - int32_t prev = Config::Get().general.WindowLimit; - int32_t val = std::clamp(value, kWindowLimitMin, kWindowLimitMax); - Config::Get().general.WindowLimit = val; - Config::Save(); - // Checks if value decreases and then closes surplus - // windows if one sets a limit lower than the number of windows open - if (val < prev) + /** + * + * rct2: 0x006ED7B0 + */ + void WindowDispatchUpdateAll() { - WindowCloseSurplus(val, WindowClass::Options); + // gTooltipNotShownTicks++; + WindowVisitEach([&](WindowBase* w) { w->OnUpdate(); }); } -} -/** - * Closes the specified window. - * rct2: 0x006ECD4C - * - * @param window The window to close (esi). - */ -void WindowClose(WindowBase& w) -{ - w.OnClose(); - - // Remove viewport - w.RemoveViewport(); - - // Invalidate the window (area) - w.Invalidate(); - - w.flags |= WF_DEAD; -} - -template -static void WindowCloseByCondition(TPred pred, uint32_t flags = WindowCloseFlags::None) -{ - for (auto it = g_window_list.rbegin(); it != g_window_list.rend(); ++it) + void WindowUpdateAllViewports() { - auto& wnd = *(*it); - if (wnd.flags & WF_DEAD) - continue; - - if (pred(&wnd)) - { - WindowClose(wnd); - if (flags & WindowCloseFlags::CloseSingle) + WindowVisitEach([&](WindowBase* w) { + if (w->viewport != nullptr && WindowIsVisible(*w)) { - return; + ViewportUpdatePosition(w); } - } - } -} - -/** - * Closes all windows with the specified window class. - * rct2: 0x006ECCF4 - * @param cls (cl) with bit 15 set - */ -void WindowCloseByClass(WindowClass cls) -{ - WindowCloseByCondition([&](WindowBase* w) -> bool { return w->classification == cls; }); -} - -/** - * Closes all windows with specified window class and number. - * rct2: 0x006ECCF4 - * @param cls (cl) without bit 15 set - * @param number (dx) - */ -void WindowCloseByNumber(WindowClass cls, rct_windownumber number) -{ - WindowCloseByCondition([cls, number](WindowBase* w) -> bool { return w->classification == cls && w->number == number; }); -} - -// TODO: Refactor this to use variant once the new window class is done. -void WindowCloseByNumber(WindowClass cls, EntityId number) -{ - WindowCloseByNumber(cls, static_cast(number.ToUnderlying())); -} - -/** - * Closes the top-most window - * - * rct2: 0x006E403C - */ -void WindowCloseTop() -{ - WindowCloseByClass(WindowClass::Dropdown); - - if (gScreenFlags & SCREEN_FLAGS_SCENARIO_EDITOR) - { - if (GetGameState().EditorStep != EditorStep::LandscapeEditor) - return; + }); } - auto pred = [](WindowBase* w) -> bool { return !(w->flags & (WF_STICK_TO_BACK | WF_STICK_TO_FRONT)); }; - WindowCloseByCondition(pred, WindowCloseFlags::CloseSingle); -} - -/** - * Closes all open windows - * - * rct2: 0x006EE927 - */ -void WindowCloseAll() -{ - WindowCloseByClass(WindowClass::Dropdown); - WindowCloseByCondition([](WindowBase* w) -> bool { return !(w->flags & (WF_STICK_TO_BACK | WF_STICK_TO_FRONT)); }); -} - -void WindowCloseAllExceptClass(WindowClass cls) -{ - WindowCloseByClass(WindowClass::Dropdown); - WindowCloseByCondition([cls](WindowBase* w) -> bool { - return w->classification != cls && !(w->flags & (WF_STICK_TO_BACK | WF_STICK_TO_FRONT)); - }); -} - -/** - * Closes all windows, save for those having any of the passed flags. - */ -void WindowCloseAllExceptFlags(uint16_t flags) -{ - WindowCloseByCondition([flags](WindowBase* w) -> bool { return !(w->flags & flags); }); -} - -/** - * Closes all windows except the specified window number and class. - * @param number (dx) - * @param cls (cl) without bit 15 set - */ -void WindowCloseAllExceptNumberAndClass(rct_windownumber number, WindowClass cls) -{ - WindowCloseByClass(WindowClass::Dropdown); - WindowCloseByCondition([cls, number](WindowBase* w) -> bool { - return (!(w->number == number && w->classification == cls) && !(w->flags & (WF_STICK_TO_BACK | WF_STICK_TO_FRONT))); - }); -} - -/** - * Invalidates the specified window. - * rct2: 0x006EB13A - * - * @param window The window to invalidate (esi). - */ -template -static void WindowInvalidateByCondition(TPred pred) -{ - WindowVisitEach([pred](WindowBase* w) { - if (pred(w)) - { - w->Invalidate(); - } - }); -} - -/** - * Invalidates all windows with the specified window class. - * rct2: 0x006EC3AC - * @param cls (al) with bit 14 set - */ -void WindowInvalidateByClass(WindowClass cls) -{ - WindowInvalidateByCondition([cls](WindowBase* w) -> bool { return w->classification == cls; }); -} - -/** - * Invalidates all windows with the specified window class and number. - * rct2: 0x006EC3AC - */ -void WindowInvalidateByNumber(WindowClass cls, rct_windownumber number) -{ - WindowInvalidateByCondition( - [cls, number](WindowBase* w) -> bool { return w->classification == cls && w->number == number; }); -} - -// TODO: Use variant for this once the window framework is done. -void WindowInvalidateByNumber(WindowClass cls, EntityId id) -{ - WindowInvalidateByNumber(cls, static_cast(id.ToUnderlying())); -} - -/** - * Invalidates all windows. - */ -void WindowInvalidateAll() -{ - WindowVisitEach([](WindowBase* w) { w->Invalidate(); }); -} - -/** - * Invalidates the specified widget of a window. - * rct2: 0x006EC402 - */ -void WidgetInvalidate(WindowBase& w, WidgetIndex widgetIndex) -{ - if (w.widgets.empty()) + /** + * + * rct2: 0x006E77A1 + */ + void WindowUpdateAll() { - // This might be called before the window is fully created. - return; - } + // Remove all windows in g_window_list that have the WF_DEAD flag + g_window_list.remove_if([](auto&& w) -> bool { return w->flags & WF_DEAD; }); - assert(widgetIndex < w.widgets.size()); - - const auto& widget = w.widgets[widgetIndex]; - if (widget.left == -2) - return; - - GfxSetDirtyBlocks({ { w.windowPos + ScreenCoordsXY{ widget.left, widget.top } }, - { w.windowPos + ScreenCoordsXY{ widget.right + 1, widget.bottom + 1 } } }); -} - -template -static void widget_invalidate_by_condition(TPred pred) -{ - WindowVisitEach([pred](WindowBase* w) { - if (pred(w)) + // Periodic update happens every second so 40 ticks. + if (gCurrentRealTimeTicks >= gWindowUpdateTicks) { - w->Invalidate(); + gWindowUpdateTicks = gCurrentRealTimeTicks + kGameUpdateFPS; + + WindowVisitEach([](WindowBase* w) { w->OnPeriodicUpdate(); }); } - }); -} -/** - * Invalidates the specified widget of all windows that match the specified window class. - */ -void WidgetInvalidateByClass(WindowClass cls, WidgetIndex widgetIndex) -{ - WindowVisitEach([cls, widgetIndex](WindowBase* w) { - if (w->classification == cls) - { - WidgetInvalidate(*w, widgetIndex); - } - }); -} - -/** - * Invalidates the specified widget of all windows that match the specified window class and number. - * rct2: 0x006EC3AC - */ -void WidgetInvalidateByNumber(WindowClass cls, rct_windownumber number, WidgetIndex widgetIndex) -{ - WindowVisitEach([cls, number, widgetIndex](WindowBase* w) { - if (w->classification == cls && w->number == number) - { - WidgetInvalidate(*w, widgetIndex); - } - }); -} - -int32_t WindowGetScrollDataIndex(const WindowBase& w, WidgetIndex widget_index) -{ - int32_t i, result; - - result = 0; - for (i = 0; i < widget_index; i++) - { - const auto& widget = w.widgets[i]; - if (widget.type == WindowWidgetType::Scroll) - result++; - } - return result; -} - -/** - * - * rct2: 0x006ECDA4 - */ -WindowBase* WindowBringToFront(WindowBase& w) -{ - if (!(w.flags & (WF_STICK_TO_BACK | WF_STICK_TO_FRONT))) - { - auto itSourcePos = WindowGetIterator(&w); - if (itSourcePos != g_window_list.end()) - { - // Insert in front of the first non-stick-to-front window - auto itDestPos = g_window_list.begin(); - for (auto it = g_window_list.rbegin(); it != g_window_list.rend(); it++) + // Border flash invalidation + WindowVisitEach([](WindowBase* w) { + if (w->flags & WF_WHITE_BORDER_MASK) { - auto& w2 = *it; - if (!(w2->flags & WF_STICK_TO_FRONT)) + w->flags -= WF_WHITE_BORDER_ONE; + if (!(w->flags & WF_WHITE_BORDER_MASK)) { - itDestPos = it.base(); + w->Invalidate(); + } + } + }); + + auto windowManager = GetContext()->GetUiContext()->GetWindowManager(); + windowManager->UpdateMouseWheel(); + } + + void WindowNotifyLanguageChange() + { + WindowVisitEach([&](WindowBase* w) { w->OnLanguageChange(); }); + } + + static void WindowCloseSurplus(int32_t cap, WindowClass avoid_classification) + { + // find the amount of windows that are currently open + auto count = static_cast(g_window_list.size()); + // difference between amount open and cap = amount to close + auto diff = count - kWindowLimitReserved - cap; + for (auto i = 0; i < diff; i++) + { + // iterates through the list until it finds the newest window, or a window that can be closed + WindowBase* foundW{}; + for (auto& w : g_window_list) + { + if (w->flags & WF_DEAD) + continue; + if (!(w->flags & (WF_STICK_TO_BACK | WF_STICK_TO_FRONT | WF_NO_AUTO_CLOSE))) + { + foundW = w.get(); break; } } - - g_window_list.splice(itDestPos, g_window_list, itSourcePos); - w.Invalidate(); - - if (w.windowPos.x + w.width < 20) + // skip window if window matches specified WindowClass (as user may be modifying via options) + if (avoid_classification != WindowClass::Null && foundW != nullptr + && foundW->classification == avoid_classification) { - int32_t i = 20 - w.windowPos.x; - w.windowPos.x += i; - if (w.viewport != nullptr) - w.viewport->pos.x += i; - w.Invalidate(); + continue; } + WindowClose(*foundW); } } - return &w; -} -WindowBase* WindowBringToFrontByClassWithFlags(WindowClass cls, uint16_t flags) -{ - auto* windowMgr = GetContext()->GetUiContext()->GetWindowManager(); - WindowBase* w = windowMgr->FindByClass(cls); - if (w != nullptr) + /* + * Changes the maximum amount of windows allowed + */ + void WindowSetWindowLimit(int32_t value) { - w->flags |= flags; - w->Invalidate(); - w = WindowBringToFront(*w); - } - - return w; -} - -WindowBase* WindowBringToFrontByClass(WindowClass cls) -{ - return WindowBringToFrontByClassWithFlags(cls, WF_WHITE_BORDER_MASK); -} - -/** - * - * rct2: 0x006ED78A - * cls (cl) - * number (dx) - */ -WindowBase* WindowBringToFrontByNumber(WindowClass cls, rct_windownumber number) -{ - auto* windowMgr = GetContext()->GetUiContext()->GetWindowManager(); - WindowBase* w = windowMgr->FindByNumber(cls, number); - if (w != nullptr) - { - w->flags |= WF_WHITE_BORDER_MASK; - w->Invalidate(); - w = WindowBringToFront(*w); - } - - return w; -} - -/** - * - * rct2: 0x006EE65A - */ -void WindowPushOthersRight(WindowBase& window) -{ - WindowVisitEach([&window](WindowBase* w) { - if (w == &window) - return; - if (w->flags & (WF_STICK_TO_BACK | WF_STICK_TO_FRONT)) - return; - if (w->windowPos.x >= window.windowPos.x + window.width) - return; - if (w->windowPos.x + w->width <= window.windowPos.x) - return; - if (w->windowPos.y >= window.windowPos.y + window.height) - return; - if (w->windowPos.y + w->height <= window.windowPos.y) - return; - - w->Invalidate(); - if (window.windowPos.x + window.width + 13 >= ContextGetWidth()) - return; - auto push_amount = window.windowPos.x + window.width - w->windowPos.x + 3; - w->windowPos.x += push_amount; - w->Invalidate(); - if (w->viewport != nullptr) - w->viewport->pos.x += push_amount; - }); -} - -/** - * - * rct2: 0x006EE6EA - */ -void WindowPushOthersBelow(WindowBase& w1) -{ - // Enumerate through all other windows - WindowVisitEach([&w1](WindowBase* w2) { - if (&w1 == w2) - return; - // ? - if (w2->flags & (WF_STICK_TO_BACK | WF_STICK_TO_FRONT)) - return; - // Check if w2 intersects with w1 - if (w2->windowPos.x > (w1.windowPos.x + w1.width) || w2->windowPos.x + w2->width < w1.windowPos.x) - return; - if (w2->windowPos.y > (w1.windowPos.y + w1.height) || w2->windowPos.y + w2->height < w1.windowPos.y) - return; - - // Check if there is room to push it down - if (w1.windowPos.y + w1.height + 80 >= ContextGetHeight()) - return; - - // Invalidate the window's current area - w2->Invalidate(); - - int32_t push_amount = w1.windowPos.y + w1.height - w2->windowPos.y + 3; - w2->windowPos.y += push_amount; - - // Invalidate the window's new area - w2->Invalidate(); - - // Update viewport position if necessary - if (w2->viewport != nullptr) - w2->viewport->pos.y += push_amount; - }); -} - -/** - * - * rct2: 0x006EE2E4 - */ -WindowBase* WindowGetMain() -{ - for (auto& w : g_window_list) - { - if (w->flags & WF_DEAD) - continue; - if (w->classification == WindowClass::MainWindow) + int32_t prev = Config::Get().general.WindowLimit; + int32_t val = std::clamp(value, kWindowLimitMin, kWindowLimitMax); + Config::Get().general.WindowLimit = val; + Config::Save(); + // Checks if value decreases and then closes surplus + // windows if one sets a limit lower than the number of windows open + if (val < prev) { - return w.get(); + WindowCloseSurplus(val, WindowClass::Options); } } - return nullptr; -} -/** - * - * rct2: 0x006E7C9C - * @param w (esi) - * @param x (eax) - * @param y (ecx) - * @param z (edx) - */ -void WindowScrollToLocation(WindowBase& w, const CoordsXYZ& coords) -{ - WindowUnfollowSprite(w); - if (w.viewport != nullptr) + /** + * Closes the specified window. + * rct2: 0x006ECD4C + * + * @param window The window to close (esi). + */ + void WindowClose(WindowBase& w) { - int16_t height = TileElementHeight(coords); - if (coords.z < height - 16) + w.OnClose(); + + // Remove viewport + w.RemoveViewport(); + + // Invalidate the window (area) + w.Invalidate(); + + w.flags |= WF_DEAD; + } + + template + static void WindowCloseByCondition(TPred pred, uint32_t flags = WindowCloseFlags::None) + { + for (auto it = g_window_list.rbegin(); it != g_window_list.rend(); ++it) { - if (!(w.viewport->flags & VIEWPORT_FLAG_UNDERGROUND_INSIDE)) + auto& wnd = *(*it); + if (wnd.flags & WF_DEAD) + continue; + + if (pred(&wnd)) { - w.viewport->flags |= VIEWPORT_FLAG_UNDERGROUND_INSIDE; - w.Invalidate(); - } - } - else - { - if (w.viewport->flags & VIEWPORT_FLAG_UNDERGROUND_INSIDE) - { - w.viewport->flags &= ~VIEWPORT_FLAG_UNDERGROUND_INSIDE; - w.Invalidate(); - } - } - - auto screenCoords = Translate3DTo2DWithZ(w.viewport->rotation, coords); - - int32_t i = 0; - if (!(gScreenFlags & SCREEN_FLAGS_TITLE_DEMO)) - { - bool found = false; - while (!found) - { - auto x2 = w.viewport->pos.x + static_cast(w.viewport->width * window_scroll_locations[i][0]); - auto y2 = w.viewport->pos.y + static_cast(w.viewport->height * window_scroll_locations[i][1]); - - auto it = WindowGetIterator(&w); - for (; it != g_window_list.end(); it++) + WindowClose(wnd); + if (flags & WindowCloseFlags::CloseSingle) { - auto w2 = (*it).get(); - auto x1 = w2->windowPos.x - 10; - auto y1 = w2->windowPos.y - 10; - if (x2 >= x1 && x2 <= w2->width + x1 + 20) + return; + } + } + } + } + + /** + * Closes all windows with the specified window class. + * rct2: 0x006ECCF4 + * @param cls (cl) with bit 15 set + */ + void WindowCloseByClass(WindowClass cls) + { + WindowCloseByCondition([&](WindowBase* w) -> bool { return w->classification == cls; }); + } + + /** + * Closes all windows with specified window class and number. + * rct2: 0x006ECCF4 + * @param cls (cl) without bit 15 set + * @param number (dx) + */ + void WindowCloseByNumber(WindowClass cls, rct_windownumber number) + { + WindowCloseByCondition( + [cls, number](WindowBase* w) -> bool { return w->classification == cls && w->number == number; }); + } + + // TODO: Refactor this to use variant once the new window class is done. + void WindowCloseByNumber(WindowClass cls, EntityId number) + { + WindowCloseByNumber(cls, static_cast(number.ToUnderlying())); + } + + /** + * Closes the top-most window + * + * rct2: 0x006E403C + */ + void WindowCloseTop() + { + WindowCloseByClass(WindowClass::Dropdown); + + if (gScreenFlags & SCREEN_FLAGS_SCENARIO_EDITOR) + { + if (GetGameState().EditorStep != EditorStep::LandscapeEditor) + return; + } + + auto pred = [](WindowBase* w) -> bool { return !(w->flags & (WF_STICK_TO_BACK | WF_STICK_TO_FRONT)); }; + WindowCloseByCondition(pred, WindowCloseFlags::CloseSingle); + } + + /** + * Closes all open windows + * + * rct2: 0x006EE927 + */ + void WindowCloseAll() + { + WindowCloseByClass(WindowClass::Dropdown); + WindowCloseByCondition([](WindowBase* w) -> bool { return !(w->flags & (WF_STICK_TO_BACK | WF_STICK_TO_FRONT)); }); + } + + void WindowCloseAllExceptClass(WindowClass cls) + { + WindowCloseByClass(WindowClass::Dropdown); + WindowCloseByCondition([cls](WindowBase* w) -> bool { + return w->classification != cls && !(w->flags & (WF_STICK_TO_BACK | WF_STICK_TO_FRONT)); + }); + } + + /** + * Closes all windows, save for those having any of the passed flags. + */ + void WindowCloseAllExceptFlags(uint16_t flags) + { + WindowCloseByCondition([flags](WindowBase* w) -> bool { return !(w->flags & flags); }); + } + + /** + * Closes all windows except the specified window number and class. + * @param number (dx) + * @param cls (cl) without bit 15 set + */ + void WindowCloseAllExceptNumberAndClass(rct_windownumber number, WindowClass cls) + { + WindowCloseByClass(WindowClass::Dropdown); + WindowCloseByCondition([cls, number](WindowBase* w) -> bool { + return (!(w->number == number && w->classification == cls) && !(w->flags & (WF_STICK_TO_BACK | WF_STICK_TO_FRONT))); + }); + } + + /** + * Invalidates the specified window. + * rct2: 0x006EB13A + * + * @param window The window to invalidate (esi). + */ + template + static void WindowInvalidateByCondition(TPred pred) + { + WindowVisitEach([pred](WindowBase* w) { + if (pred(w)) + { + w->Invalidate(); + } + }); + } + + /** + * Invalidates all windows with the specified window class. + * rct2: 0x006EC3AC + * @param cls (al) with bit 14 set + */ + void WindowInvalidateByClass(WindowClass cls) + { + WindowInvalidateByCondition([cls](WindowBase* w) -> bool { return w->classification == cls; }); + } + + /** + * Invalidates all windows with the specified window class and number. + * rct2: 0x006EC3AC + */ + void WindowInvalidateByNumber(WindowClass cls, rct_windownumber number) + { + WindowInvalidateByCondition( + [cls, number](WindowBase* w) -> bool { return w->classification == cls && w->number == number; }); + } + + // TODO: Use variant for this once the window framework is done. + void WindowInvalidateByNumber(WindowClass cls, EntityId id) + { + WindowInvalidateByNumber(cls, static_cast(id.ToUnderlying())); + } + + /** + * Invalidates all windows. + */ + void WindowInvalidateAll() + { + WindowVisitEach([](WindowBase* w) { w->Invalidate(); }); + } + + /** + * Invalidates the specified widget of a window. + * rct2: 0x006EC402 + */ + void WidgetInvalidate(WindowBase& w, WidgetIndex widgetIndex) + { + if (w.widgets.empty()) + { + // This might be called before the window is fully created. + return; + } + + assert(widgetIndex < w.widgets.size()); + + const auto& widget = w.widgets[widgetIndex]; + if (widget.left == -2) + return; + + GfxSetDirtyBlocks({ { w.windowPos + ScreenCoordsXY{ widget.left, widget.top } }, + { w.windowPos + ScreenCoordsXY{ widget.right + 1, widget.bottom + 1 } } }); + } + + template + static void widget_invalidate_by_condition(TPred pred) + { + WindowVisitEach([pred](WindowBase* w) { + if (pred(w)) + { + w->Invalidate(); + } + }); + } + + /** + * Invalidates the specified widget of all windows that match the specified window class. + */ + void WidgetInvalidateByClass(WindowClass cls, WidgetIndex widgetIndex) + { + WindowVisitEach([cls, widgetIndex](WindowBase* w) { + if (w->classification == cls) + { + WidgetInvalidate(*w, widgetIndex); + } + }); + } + + /** + * Invalidates the specified widget of all windows that match the specified window class and number. + * rct2: 0x006EC3AC + */ + void WidgetInvalidateByNumber(WindowClass cls, rct_windownumber number, WidgetIndex widgetIndex) + { + WindowVisitEach([cls, number, widgetIndex](WindowBase* w) { + if (w->classification == cls && w->number == number) + { + WidgetInvalidate(*w, widgetIndex); + } + }); + } + + int32_t WindowGetScrollDataIndex(const WindowBase& w, WidgetIndex widget_index) + { + int32_t i, result; + + result = 0; + for (i = 0; i < widget_index; i++) + { + const auto& widget = w.widgets[i]; + if (widget.type == WindowWidgetType::Scroll) + result++; + } + return result; + } + + /** + * + * rct2: 0x006ECDA4 + */ + WindowBase* WindowBringToFront(WindowBase& w) + { + if (!(w.flags & (WF_STICK_TO_BACK | WF_STICK_TO_FRONT))) + { + auto itSourcePos = WindowGetIterator(&w); + if (itSourcePos != g_window_list.end()) + { + // Insert in front of the first non-stick-to-front window + auto itDestPos = g_window_list.begin(); + for (auto it = g_window_list.rbegin(); it != g_window_list.rend(); it++) + { + auto& w2 = *it; + if (!(w2->flags & WF_STICK_TO_FRONT)) { - if (y2 >= y1 && y2 <= w2->height + y1 + 20) - { - // window is covering this area, try the next one - i++; - found = false; - break; - } + itDestPos = it.base(); + break; } } - if (it == g_window_list.end()) + + g_window_list.splice(itDestPos, g_window_list, itSourcePos); + w.Invalidate(); + + if (w.windowPos.x + w.width < 20) { - found = true; - } - if (i >= static_cast(std::size(window_scroll_locations))) - { - i = 0; - found = true; + int32_t i = 20 - w.windowPos.x; + w.windowPos.x += i; + if (w.viewport != nullptr) + w.viewport->pos.x += i; + w.Invalidate(); } } } - // rct2: 0x006E7C76 - if (w.viewport_target_sprite.IsNull()) + return &w; + } + + WindowBase* WindowBringToFrontByClassWithFlags(WindowClass cls, uint16_t flags) + { + auto* windowMgr = GetContext()->GetUiContext()->GetWindowManager(); + WindowBase* w = windowMgr->FindByClass(cls); + if (w != nullptr) { - if (!(w.flags & WF_NO_SCROLLING)) + w->flags |= flags; + w->Invalidate(); + w = WindowBringToFront(*w); + } + + return w; + } + + WindowBase* WindowBringToFrontByClass(WindowClass cls) + { + return WindowBringToFrontByClassWithFlags(cls, WF_WHITE_BORDER_MASK); + } + + /** + * + * rct2: 0x006ED78A + * cls (cl) + * number (dx) + */ + WindowBase* WindowBringToFrontByNumber(WindowClass cls, rct_windownumber number) + { + auto* windowMgr = GetContext()->GetUiContext()->GetWindowManager(); + WindowBase* w = windowMgr->FindByNumber(cls, number); + if (w != nullptr) + { + w->flags |= WF_WHITE_BORDER_MASK; + w->Invalidate(); + w = WindowBringToFront(*w); + } + + return w; + } + + /** + * + * rct2: 0x006EE65A + */ + void WindowPushOthersRight(WindowBase& window) + { + WindowVisitEach([&window](WindowBase* w) { + if (w == &window) + return; + if (w->flags & (WF_STICK_TO_BACK | WF_STICK_TO_FRONT)) + return; + if (w->windowPos.x >= window.windowPos.x + window.width) + return; + if (w->windowPos.x + w->width <= window.windowPos.x) + return; + if (w->windowPos.y >= window.windowPos.y + window.height) + return; + if (w->windowPos.y + w->height <= window.windowPos.y) + return; + + w->Invalidate(); + if (window.windowPos.x + window.width + 13 >= ContextGetWidth()) + return; + auto push_amount = window.windowPos.x + window.width - w->windowPos.x + 3; + w->windowPos.x += push_amount; + w->Invalidate(); + if (w->viewport != nullptr) + w->viewport->pos.x += push_amount; + }); + } + + /** + * + * rct2: 0x006EE6EA + */ + void WindowPushOthersBelow(WindowBase& w1) + { + // Enumerate through all other windows + WindowVisitEach([&w1](WindowBase* w2) { + if (&w1 == w2) + return; + // ? + if (w2->flags & (WF_STICK_TO_BACK | WF_STICK_TO_FRONT)) + return; + // Check if w2 intersects with w1 + if (w2->windowPos.x > (w1.windowPos.x + w1.width) || w2->windowPos.x + w2->width < w1.windowPos.x) + return; + if (w2->windowPos.y > (w1.windowPos.y + w1.height) || w2->windowPos.y + w2->height < w1.windowPos.y) + return; + + // Check if there is room to push it down + if (w1.windowPos.y + w1.height + 80 >= ContextGetHeight()) + return; + + // Invalidate the window's current area + w2->Invalidate(); + + int32_t push_amount = w1.windowPos.y + w1.height - w2->windowPos.y + 3; + w2->windowPos.y += push_amount; + + // Invalidate the window's new area + w2->Invalidate(); + + // Update viewport position if necessary + if (w2->viewport != nullptr) + w2->viewport->pos.y += push_amount; + }); + } + + /** + * + * rct2: 0x006EE2E4 + */ + WindowBase* WindowGetMain() + { + for (auto& w : g_window_list) + { + if (w->flags & WF_DEAD) + continue; + if (w->classification == WindowClass::MainWindow) { - w.savedViewPos = screenCoords - - ScreenCoordsXY{ static_cast(w.viewport->ViewWidth() * window_scroll_locations[i][0]), - static_cast(w.viewport->ViewHeight() * window_scroll_locations[i][1]) }; - w.flags |= WF_SCROLLING_TO_LOCATION; + return w.get(); } } - } -} - -void WindowViewportGetMapCoordsByCursor( - const WindowBase& w, int32_t* map_x, int32_t* map_y, int32_t* offset_x, int32_t* offset_y) -{ - // Get mouse position to offset against. - auto mouseCoords = ContextGetCursorPositionScaled(); - - // Compute map coordinate by mouse position. - auto viewportPos = w.viewport->ScreenToViewportCoord(mouseCoords); - auto coordsXYZ = ViewportAdjustForMapHeight(viewportPos, w.viewport->rotation); - auto mapCoords = ViewportPosToMapPos(viewportPos, coordsXYZ.z, w.viewport->rotation); - *map_x = mapCoords.x; - *map_y = mapCoords.y; - - // Get viewport coordinates centring around the tile. - int32_t z = TileElementHeight(mapCoords); - - auto centreLoc = centre_2d_coordinates({ mapCoords.x, mapCoords.y, z }, w.viewport); - if (!centreLoc) - { - LOG_ERROR("Invalid location."); - return; - } - - // Rebase mouse position onto centre of window, and compensate for zoom level. - int32_t rebased_x = w.viewport->zoom.ApplyTo(w.width / 2 - mouseCoords.x); - int32_t rebased_y = w.viewport->zoom.ApplyTo(w.height / 2 - mouseCoords.y); - - // Compute cursor offset relative to tile. - *offset_x = w.viewport->zoom.ApplyTo(w.savedViewPos.x - (centreLoc->x + rebased_x)); - *offset_y = w.viewport->zoom.ApplyTo(w.savedViewPos.y - (centreLoc->y + rebased_y)); -} - -void WindowViewportCentreTileAroundCursor(WindowBase& w, int32_t map_x, int32_t map_y, int32_t offset_x, int32_t offset_y) -{ - // Get viewport coordinates centring around the tile. - int32_t z = TileElementHeight({ map_x, map_y }); - auto centreLoc = centre_2d_coordinates({ map_x, map_y, z }, w.viewport); - - if (!centreLoc.has_value()) - { - LOG_ERROR("Invalid location."); - return; - } - - // Get mouse position to offset against. - auto mouseCoords = ContextGetCursorPositionScaled(); - - // Rebase mouse position onto centre of window, and compensate for zoom level. - int32_t rebased_x = w.viewport->zoom.ApplyTo((w.width >> 1) - mouseCoords.x); - int32_t rebased_y = w.viewport->zoom.ApplyTo((w.height >> 1) - mouseCoords.y); - - // Apply offset to the viewport. - w.savedViewPos = { centreLoc->x + rebased_x + w.viewport->zoom.ApplyInversedTo(offset_x), - centreLoc->y + rebased_y + w.viewport->zoom.ApplyInversedTo(offset_y) }; -} - -/** - * For all windows with viewports, ensure they do not have a zoom level less than the minimum. - */ -void WindowCheckAllValidZoom() -{ - WindowVisitEach([](WindowBase* w) { - if (w->viewport != nullptr && w->viewport->zoom < ZoomLevel::min()) - { - WindowZoomSet(*w, ZoomLevel::min(), false); - } - }); -} - -void WindowZoomSet(WindowBase& w, ZoomLevel zoomLevel, bool atCursor) -{ - Viewport* v = w.viewport; - if (v == nullptr) - return; - - zoomLevel = std::clamp(zoomLevel, ZoomLevel::min(), ZoomLevel::max()); - if (v->zoom == zoomLevel) - return; - - // Zooming to cursor? Remember where we're pointing at the moment. - int32_t saved_map_x = 0; - int32_t saved_map_y = 0; - int32_t offset_x = 0; - int32_t offset_y = 0; - if (Config::Get().general.ZoomToCursor && atCursor) - { - WindowViewportGetMapCoordsByCursor(w, &saved_map_x, &saved_map_y, &offset_x, &offset_y); - } - - // Zoom in - while (v->zoom > zoomLevel) - { - v->zoom--; - w.savedViewPos.x += v->ViewWidth() / 2; - w.savedViewPos.y += v->ViewHeight() / 2; - } - - // Zoom out - while (v->zoom < zoomLevel) - { - v->zoom++; - w.savedViewPos.x -= v->ViewWidth() / 4; - w.savedViewPos.y -= v->ViewHeight() / 4; - } - - // Zooming to cursor? Centre around the tile we were hovering over just now. - if (Config::Get().general.ZoomToCursor && atCursor) - { - WindowViewportCentreTileAroundCursor(w, saved_map_x, saved_map_y, offset_x, offset_y); - } - - // HACK: Prevents the redraw from failing when there is - // a window on top of the viewport. - WindowBringToFront(w); - w.Invalidate(); -} - -/** - * Splits a drawing of a window into regions that can be seen and are not hidden - * by other opaque overlapping windows. - */ -void WindowDraw(DrawPixelInfo& dpi, WindowBase& w, int32_t left, int32_t top, int32_t right, int32_t bottom) -{ - if (!WindowIsVisible(w)) - return; - - // Divide the draws up for only the visible regions of the window recursively - auto itPos = WindowGetIterator(&w); - for (auto it = std::next(itPos); it != g_window_list.end(); it++) - { - // Check if this window overlaps w - auto topwindow = it->get(); - if (topwindow->windowPos.x >= right || topwindow->windowPos.y >= bottom) - continue; - if (topwindow->windowPos.x + topwindow->width <= left || topwindow->windowPos.y + topwindow->height <= top) - continue; - if (topwindow->flags & WF_TRANSPARENT) - continue; - - // A window overlaps w, split up the draw into two regions where the window starts to overlap - if (topwindow->windowPos.x > left) - { - // Split draw at topwindow.left - WindowDrawCore(dpi, w, left, top, topwindow->windowPos.x, bottom); - WindowDrawCore(dpi, w, topwindow->windowPos.x, top, right, bottom); - } - else if (topwindow->windowPos.x + topwindow->width < right) - { - // Split draw at topwindow.right - WindowDrawCore(dpi, w, left, top, topwindow->windowPos.x + topwindow->width, bottom); - WindowDrawCore(dpi, w, topwindow->windowPos.x + topwindow->width, top, right, bottom); - } - else if (topwindow->windowPos.y > top) - { - // Split draw at topwindow.top - WindowDrawCore(dpi, w, left, top, right, topwindow->windowPos.y); - WindowDrawCore(dpi, w, left, topwindow->windowPos.y, right, bottom); - } - else if (topwindow->windowPos.y + topwindow->height < bottom) - { - // Split draw at topwindow.bottom - WindowDrawCore(dpi, w, left, top, right, topwindow->windowPos.y + topwindow->height); - WindowDrawCore(dpi, w, left, topwindow->windowPos.y + topwindow->height, right, bottom); - } - - // Drawing for this region should be done now, exit - return; - } - - // No windows overlap - WindowDrawCore(dpi, w, left, top, right, bottom); -} - -/** - * Draws the given window and any other overlapping transparent windows. - */ -static void WindowDrawCore(DrawPixelInfo& dpi, WindowBase& w, int32_t left, int32_t top, int32_t right, int32_t bottom) -{ - // Clamp region - left = std::max(left, w.windowPos.x); - top = std::max(top, w.windowPos.y); - right = std::min(right, w.windowPos.x + w.width); - bottom = std::min(bottom, w.windowPos.y + w.height); - if (left >= right) - return; - if (top >= bottom) - return; - - // Draw the window and any other overlapping transparent windows - for (auto it = WindowGetIterator(&w); it != g_window_list.end(); it++) - { - auto* v = (*it).get(); - if (v->flags & WF_DEAD) - continue; - if ((&w == v || (v->flags & WF_TRANSPARENT)) && WindowIsVisible(*v)) - { - WindowDrawSingle(dpi, *v, left, top, right, bottom); - } - } -} - -static void WindowDrawSingle(DrawPixelInfo& dpi, WindowBase& w, int32_t left, int32_t top, int32_t right, int32_t bottom) -{ - assert(dpi.zoom_level == ZoomLevel{ 0 }); - // Copy dpi so we can crop it - DrawPixelInfo copy = dpi; - - // Clamp left to 0 - int32_t overflow = left - copy.x; - if (overflow > 0) - { - copy.x += overflow; - copy.width -= overflow; - if (copy.width <= 0) - return; - copy.pitch += overflow; - copy.bits += overflow; - } - - // Clamp width to right - overflow = copy.x + copy.width - right; - if (overflow > 0) - { - copy.width -= overflow; - if (copy.width <= 0) - return; - copy.pitch += overflow; - } - - // Clamp top to 0 - overflow = top - copy.y; - if (overflow > 0) - { - copy.y += overflow; - copy.height -= overflow; - if (copy.height <= 0) - return; - copy.bits += copy.LineStride() * overflow; - } - - // Clamp height to bottom - overflow = copy.y + copy.height - bottom; - if (overflow > 0) - { - copy.height -= overflow; - if (copy.height <= 0) - return; - } - - // Invalidate modifies the window colours so first get the correct - // colour before setting the global variables for the string painting - w.OnPrepareDraw(); - - // Text colouring - gCurrentWindowColours[0] = w.colours[0].colour; - gCurrentWindowColours[1] = w.colours[1].colour; - gCurrentWindowColours[2] = w.colours[2].colour; - - w.OnDraw(copy); -} - -bool isToolActive(WindowClass cls) -{ - return InputTestFlag(INPUT_FLAG_TOOL_ACTIVE) && gCurrentToolWidget.window_classification == cls; -} - -bool isToolActive(WindowClass cls, rct_windownumber number) -{ - return isToolActive(cls) && gCurrentToolWidget.window_number == number; -} - -bool isToolActive(WindowClass cls, WidgetIndex widgetIndex) -{ - return isToolActive(cls) && gCurrentToolWidget.widget_index == widgetIndex; -} - -bool isToolActive(WindowClass cls, WidgetIndex widgetIndex, rct_windownumber number) -{ - return isToolActive(cls, widgetIndex) && gCurrentToolWidget.window_number == number; -} - -bool isToolActive(const WindowBase& w, WidgetIndex widgetIndex) -{ - return isToolActive(w.classification, widgetIndex, w.number); -} - -/** - * - * rct2: 0x006EE212 - * - * @param tool (al) - * @param widgetIndex (dx) - * @param w (esi) - */ -bool ToolSet(const WindowBase& w, WidgetIndex widgetIndex, Tool tool) -{ - if (InputTestFlag(INPUT_FLAG_TOOL_ACTIVE)) - { - if (w.classification == gCurrentToolWidget.window_classification && w.number == gCurrentToolWidget.window_number - && widgetIndex == gCurrentToolWidget.widget_index) - { - ToolCancel(); - return true; - } - - ToolCancel(); - } - - InputSetFlag(INPUT_FLAG_TOOL_ACTIVE, true); - InputSetFlag(INPUT_FLAG_4, false); - InputSetFlag(INPUT_FLAG_6, false); - gCurrentToolId = tool; - gCurrentToolWidget.window_classification = w.classification; - gCurrentToolWidget.window_number = w.number; - gCurrentToolWidget.widget_index = widgetIndex; - return false; -} - -/** - * - * rct2: 0x006EE281 - */ -void ToolCancel() -{ - if (InputTestFlag(INPUT_FLAG_TOOL_ACTIVE)) - { - InputSetFlag(INPUT_FLAG_TOOL_ACTIVE, false); - - MapInvalidateSelectionRect(); - MapInvalidateMapSelectionTiles(); - - // Reset map selection - gMapSelectFlags = 0; - - if (gCurrentToolWidget.widget_index != kWidgetIndexNull) - { - // Invalidate tool widget - WidgetInvalidateByNumber( - gCurrentToolWidget.window_classification, gCurrentToolWidget.window_number, gCurrentToolWidget.widget_index); - - // Abort tool event - auto* windowMgr = GetContext()->GetUiContext()->GetWindowManager(); - WindowBase* w = windowMgr->FindByNumber(gCurrentToolWidget.window_classification, gCurrentToolWidget.window_number); - if (w != nullptr) - w->OnToolAbort(gCurrentToolWidget.widget_index); - } - } -} - -/** - * rct2: 0x0066B905 - */ -void WindowResizeGui(int32_t width, int32_t height) -{ - WindowResizeGuiScenarioEditor(width, height); - if (gScreenFlags & SCREEN_FLAGS_EDITOR) - return; - - auto* windowMgr = GetContext()->GetUiContext()->GetWindowManager(); - WindowBase* titleWind = windowMgr->FindByClass(WindowClass::TitleMenu); - if (titleWind != nullptr) - { - titleWind->windowPos.x = (width - titleWind->width) / 2; - titleWind->windowPos.y = height - 182; - } - - WindowBase* versionWind = windowMgr->FindByClass(WindowClass::TitleVersion); - if (versionWind != nullptr) - versionWind->windowPos.y = height - 30; - - WindowBase* exitWind = windowMgr->FindByClass(WindowClass::TitleExit); - if (exitWind != nullptr) - { - exitWind->windowPos.x = width - 40; - exitWind->windowPos.y = height - 64; - } - - WindowBase* optionsWind = windowMgr->FindByClass(WindowClass::TitleOptions); - if (optionsWind != nullptr) - { - optionsWind->windowPos.x = width - 80; - } - - // Keep options window centred after a resize - WindowBase* optionsWindow = windowMgr->FindByClass(WindowClass::Options); - if (optionsWindow != nullptr) - { - optionsWindow->windowPos.x = (ContextGetWidth() - optionsWindow->width) / 2; - optionsWindow->windowPos.y = (ContextGetHeight() - optionsWindow->height) / 2; - } - - // Keep progress bar window centred after a resize - WindowBase* ProgressWindow = windowMgr->FindByClass(WindowClass::ProgressWindow); - if (ProgressWindow != nullptr) - { - ProgressWindow->windowPos.x = (ContextGetWidth() - ProgressWindow->width) / 2; - ProgressWindow->windowPos.y = (ContextGetHeight() - ProgressWindow->height) / 2; - } - - GfxInvalidateScreen(); -} - -/** - * rct2: 0x0066F0DD - */ -void WindowResizeGuiScenarioEditor(int32_t width, int32_t height) -{ - auto* mainWind = WindowGetMain(); - if (mainWind != nullptr) - { - Viewport* viewport = mainWind->viewport; - mainWind->width = width; - mainWind->height = height; - viewport->width = width; - viewport->height = height; - if (!mainWind->widgets.empty() && mainWind->widgets[WC_MAIN_WINDOW__0].type == WindowWidgetType::Viewport) - { - mainWind->widgets[WC_MAIN_WINDOW__0].right = width; - mainWind->widgets[WC_MAIN_WINDOW__0].bottom = height; - } - } - - auto* windowMgr = GetContext()->GetUiContext()->GetWindowManager(); - - WindowBase* topWind = windowMgr->FindByClass(WindowClass::TopToolbar); - if (topWind != nullptr) - { - topWind->width = std::max(640, width); - } - - WindowBase* bottomWind = windowMgr->FindByClass(WindowClass::BottomToolbar); - if (bottomWind != nullptr) - { - bottomWind->windowPos.y = height - 32; - bottomWind->width = std::max(640, width); - } -} - -/** - * - * rct2: 0x006CBCC3 - */ -void WindowCloseConstructionWindows() -{ - WindowCloseByClass(WindowClass::RideConstruction); - WindowCloseByClass(WindowClass::Footpath); - WindowCloseByClass(WindowClass::TrackDesignList); - WindowCloseByClass(WindowClass::TrackDesignPlace); -} - -/** - * Update zoom based volume attenuation for ride music and clear music list. - * rct2: 0x006BC348 - */ -void WindowUpdateViewportRideMusic() -{ - RideAudio::ClearAllViewportInstances(); - g_music_tracking_viewport = nullptr; - - for (auto it = g_window_list.rbegin(); it != g_window_list.rend(); it++) - { - auto w = it->get(); - auto viewport = w->viewport; - if (viewport == nullptr || !(viewport->flags & VIEWPORT_FLAG_SOUND_ON)) - continue; - - g_music_tracking_viewport = viewport; - gWindowAudioExclusive = w; - - if (viewport->zoom <= ZoomLevel{ 0 }) - Audio::gVolumeAdjustZoom = 0; - else if (viewport->zoom == ZoomLevel{ 1 }) - Audio::gVolumeAdjustZoom = 30; - else - Audio::gVolumeAdjustZoom = 60; - break; - } -} - -/** - * - * rct2: 0x006EE3C3 - */ -void TextinputCancel() -{ - WindowCloseByClass(WindowClass::Textinput); -} - -bool WindowIsVisible(WindowBase& w) -{ - // w->visibility is used to prevent repeat calculations within an iteration by caching the result - - if (w.visibility == VisibilityCache::Visible) - return true; - if (w.visibility == VisibilityCache::Covered) - return false; - - // only consider viewports, consider the main window always visible - if (w.viewport == nullptr || w.classification == WindowClass::MainWindow) - { - // default to previous behaviour - w.visibility = VisibilityCache::Visible; - return true; - } - - // start from the window above the current - auto itPos = WindowGetIterator(&w); - for (auto it = std::next(itPos); it != g_window_list.end(); it++) - { - auto& w_other = *(*it); - if (w_other.flags & WF_DEAD) - continue; - - // if covered by a higher window, no rendering needed - if (w_other.windowPos.x <= w.windowPos.x && w_other.windowPos.y <= w.windowPos.y - && w_other.windowPos.x + w_other.width >= w.windowPos.x + w.width - && w_other.windowPos.y + w_other.height >= w.windowPos.y + w.height) - { - w.visibility = VisibilityCache::Covered; - w.viewport->visibility = VisibilityCache::Covered; - return false; - } - } - - // default to previous behaviour - w.visibility = VisibilityCache::Visible; - w.viewport->visibility = VisibilityCache::Visible; - return true; -} - -/** - * - * rct2: 0x006E7499 - * left (ax) - * top (bx) - * right (dx) - * bottom (bp) - */ -void WindowDrawAll(DrawPixelInfo& dpi, int32_t left, int32_t top, int32_t right, int32_t bottom) -{ - auto windowDPI = dpi.Crop({ left, top }, { right - left, bottom - top }); - WindowVisitEach([&windowDPI, left, top, right, bottom](WindowBase* w) { - if (w->flags & WF_TRANSPARENT) - return; - if (right <= w->windowPos.x || bottom <= w->windowPos.y) - return; - if (left >= w->windowPos.x + w->width || top >= w->windowPos.y + w->height) - return; - WindowDraw(windowDPI, *w, left, top, right, bottom); - }); -} - -Viewport* WindowGetPreviousViewport(Viewport* current) -{ - bool foundPrevious = (current == nullptr); - for (auto it = g_window_list.rbegin(); it != g_window_list.rend(); it++) - { - auto& w = **it; - if (w.flags & WF_DEAD) - continue; - if (w.viewport != nullptr) - { - if (foundPrevious) - { - return w.viewport; - } - if (w.viewport == current) - { - foundPrevious = true; - } - } - } - return nullptr; -} - -void WindowResetVisibilities() -{ - // reset window visibility status to unknown - WindowVisitEach([](WindowBase* w) { - w->visibility = VisibilityCache::Unknown; - if (w->viewport != nullptr) - { - w->viewport->visibility = VisibilityCache::Unknown; - } - }); -} - -void WindowInitAll() -{ - WindowCloseAllExceptFlags(0); -} - -void WindowFollowSprite(WindowBase& w, EntityId spriteIndex) -{ - if (spriteIndex.ToUnderlying() < MAX_ENTITIES || spriteIndex.IsNull()) - { - w.viewport_smart_follow_sprite = spriteIndex; - } -} - -void WindowUnfollowSprite(WindowBase& w) -{ - w.viewport_smart_follow_sprite = EntityId::GetNull(); - w.viewport_target_sprite = EntityId::GetNull(); -} - -Viewport* WindowGetViewport(WindowBase* w) -{ - if (w == nullptr) - { return nullptr; } - return w->viewport; -} + /** + * + * rct2: 0x006E7C9C + * @param w (esi) + * @param x (eax) + * @param y (ecx) + * @param z (edx) + */ + void WindowScrollToLocation(WindowBase& w, const CoordsXYZ& coords) + { + WindowUnfollowSprite(w); + if (w.viewport != nullptr) + { + int16_t height = TileElementHeight(coords); + if (coords.z < height - 16) + { + if (!(w.viewport->flags & VIEWPORT_FLAG_UNDERGROUND_INSIDE)) + { + w.viewport->flags |= VIEWPORT_FLAG_UNDERGROUND_INSIDE; + w.Invalidate(); + } + } + else + { + if (w.viewport->flags & VIEWPORT_FLAG_UNDERGROUND_INSIDE) + { + w.viewport->flags &= ~VIEWPORT_FLAG_UNDERGROUND_INSIDE; + w.Invalidate(); + } + } + + auto screenCoords = Translate3DTo2DWithZ(w.viewport->rotation, coords); + + int32_t i = 0; + if (!(gScreenFlags & SCREEN_FLAGS_TITLE_DEMO)) + { + bool found = false; + while (!found) + { + auto x2 = w.viewport->pos.x + static_cast(w.viewport->width * window_scroll_locations[i][0]); + auto y2 = w.viewport->pos.y + static_cast(w.viewport->height * window_scroll_locations[i][1]); + + auto it = WindowGetIterator(&w); + for (; it != g_window_list.end(); it++) + { + auto w2 = (*it).get(); + auto x1 = w2->windowPos.x - 10; + auto y1 = w2->windowPos.y - 10; + if (x2 >= x1 && x2 <= w2->width + x1 + 20) + { + if (y2 >= y1 && y2 <= w2->height + y1 + 20) + { + // window is covering this area, try the next one + i++; + found = false; + break; + } + } + } + if (it == g_window_list.end()) + { + found = true; + } + if (i >= static_cast(std::size(window_scroll_locations))) + { + i = 0; + found = true; + } + } + } + // rct2: 0x006E7C76 + if (w.viewport_target_sprite.IsNull()) + { + if (!(w.flags & WF_NO_SCROLLING)) + { + w.savedViewPos = screenCoords + - ScreenCoordsXY{ static_cast(w.viewport->ViewWidth() * window_scroll_locations[i][0]), + static_cast(w.viewport->ViewHeight() * window_scroll_locations[i][1]) }; + w.flags |= WF_SCROLLING_TO_LOCATION; + } + } + } + } + + void WindowViewportGetMapCoordsByCursor( + const WindowBase& w, int32_t* map_x, int32_t* map_y, int32_t* offset_x, int32_t* offset_y) + { + // Get mouse position to offset against. + auto mouseCoords = ContextGetCursorPositionScaled(); + + // Compute map coordinate by mouse position. + auto viewportPos = w.viewport->ScreenToViewportCoord(mouseCoords); + auto coordsXYZ = ViewportAdjustForMapHeight(viewportPos, w.viewport->rotation); + auto mapCoords = ViewportPosToMapPos(viewportPos, coordsXYZ.z, w.viewport->rotation); + *map_x = mapCoords.x; + *map_y = mapCoords.y; + + // Get viewport coordinates centring around the tile. + int32_t z = TileElementHeight(mapCoords); + + auto centreLoc = centre_2d_coordinates({ mapCoords.x, mapCoords.y, z }, w.viewport); + if (!centreLoc) + { + LOG_ERROR("Invalid location."); + return; + } + + // Rebase mouse position onto centre of window, and compensate for zoom level. + int32_t rebased_x = w.viewport->zoom.ApplyTo(w.width / 2 - mouseCoords.x); + int32_t rebased_y = w.viewport->zoom.ApplyTo(w.height / 2 - mouseCoords.y); + + // Compute cursor offset relative to tile. + *offset_x = w.viewport->zoom.ApplyTo(w.savedViewPos.x - (centreLoc->x + rebased_x)); + *offset_y = w.viewport->zoom.ApplyTo(w.savedViewPos.y - (centreLoc->y + rebased_y)); + } + + void WindowViewportCentreTileAroundCursor(WindowBase& w, int32_t map_x, int32_t map_y, int32_t offset_x, int32_t offset_y) + { + // Get viewport coordinates centring around the tile. + int32_t z = TileElementHeight({ map_x, map_y }); + auto centreLoc = centre_2d_coordinates({ map_x, map_y, z }, w.viewport); + + if (!centreLoc.has_value()) + { + LOG_ERROR("Invalid location."); + return; + } + + // Get mouse position to offset against. + auto mouseCoords = ContextGetCursorPositionScaled(); + + // Rebase mouse position onto centre of window, and compensate for zoom level. + int32_t rebased_x = w.viewport->zoom.ApplyTo((w.width >> 1) - mouseCoords.x); + int32_t rebased_y = w.viewport->zoom.ApplyTo((w.height >> 1) - mouseCoords.y); + + // Apply offset to the viewport. + w.savedViewPos = { centreLoc->x + rebased_x + w.viewport->zoom.ApplyInversedTo(offset_x), + centreLoc->y + rebased_y + w.viewport->zoom.ApplyInversedTo(offset_y) }; + } + + /** + * For all windows with viewports, ensure they do not have a zoom level less than the minimum. + */ + void WindowCheckAllValidZoom() + { + WindowVisitEach([](WindowBase* w) { + if (w->viewport != nullptr && w->viewport->zoom < ZoomLevel::min()) + { + WindowZoomSet(*w, ZoomLevel::min(), false); + } + }); + } + + void WindowZoomSet(WindowBase& w, ZoomLevel zoomLevel, bool atCursor) + { + Viewport* v = w.viewport; + if (v == nullptr) + return; + + zoomLevel = std::clamp(zoomLevel, ZoomLevel::min(), ZoomLevel::max()); + if (v->zoom == zoomLevel) + return; + + // Zooming to cursor? Remember where we're pointing at the moment. + int32_t saved_map_x = 0; + int32_t saved_map_y = 0; + int32_t offset_x = 0; + int32_t offset_y = 0; + if (Config::Get().general.ZoomToCursor && atCursor) + { + WindowViewportGetMapCoordsByCursor(w, &saved_map_x, &saved_map_y, &offset_x, &offset_y); + } + + // Zoom in + while (v->zoom > zoomLevel) + { + v->zoom--; + w.savedViewPos.x += v->ViewWidth() / 2; + w.savedViewPos.y += v->ViewHeight() / 2; + } + + // Zoom out + while (v->zoom < zoomLevel) + { + v->zoom++; + w.savedViewPos.x -= v->ViewWidth() / 4; + w.savedViewPos.y -= v->ViewHeight() / 4; + } + + // Zooming to cursor? Centre around the tile we were hovering over just now. + if (Config::Get().general.ZoomToCursor && atCursor) + { + WindowViewportCentreTileAroundCursor(w, saved_map_x, saved_map_y, offset_x, offset_y); + } + + // HACK: Prevents the redraw from failing when there is + // a window on top of the viewport. + WindowBringToFront(w); + w.Invalidate(); + } + + /** + * Splits a drawing of a window into regions that can be seen and are not hidden + * by other opaque overlapping windows. + */ + void WindowDraw(DrawPixelInfo& dpi, WindowBase& w, int32_t left, int32_t top, int32_t right, int32_t bottom) + { + if (!WindowIsVisible(w)) + return; + + // Divide the draws up for only the visible regions of the window recursively + auto itPos = WindowGetIterator(&w); + for (auto it = std::next(itPos); it != g_window_list.end(); it++) + { + // Check if this window overlaps w + auto topwindow = it->get(); + if (topwindow->windowPos.x >= right || topwindow->windowPos.y >= bottom) + continue; + if (topwindow->windowPos.x + topwindow->width <= left || topwindow->windowPos.y + topwindow->height <= top) + continue; + if (topwindow->flags & WF_TRANSPARENT) + continue; + + // A window overlaps w, split up the draw into two regions where the window starts to overlap + if (topwindow->windowPos.x > left) + { + // Split draw at topwindow.left + WindowDrawCore(dpi, w, left, top, topwindow->windowPos.x, bottom); + WindowDrawCore(dpi, w, topwindow->windowPos.x, top, right, bottom); + } + else if (topwindow->windowPos.x + topwindow->width < right) + { + // Split draw at topwindow.right + WindowDrawCore(dpi, w, left, top, topwindow->windowPos.x + topwindow->width, bottom); + WindowDrawCore(dpi, w, topwindow->windowPos.x + topwindow->width, top, right, bottom); + } + else if (topwindow->windowPos.y > top) + { + // Split draw at topwindow.top + WindowDrawCore(dpi, w, left, top, right, topwindow->windowPos.y); + WindowDrawCore(dpi, w, left, topwindow->windowPos.y, right, bottom); + } + else if (topwindow->windowPos.y + topwindow->height < bottom) + { + // Split draw at topwindow.bottom + WindowDrawCore(dpi, w, left, top, right, topwindow->windowPos.y + topwindow->height); + WindowDrawCore(dpi, w, left, topwindow->windowPos.y + topwindow->height, right, bottom); + } + + // Drawing for this region should be done now, exit + return; + } + + // No windows overlap + WindowDrawCore(dpi, w, left, top, right, bottom); + } + + /** + * Draws the given window and any other overlapping transparent windows. + */ + static void WindowDrawCore(DrawPixelInfo& dpi, WindowBase& w, int32_t left, int32_t top, int32_t right, int32_t bottom) + { + // Clamp region + left = std::max(left, w.windowPos.x); + top = std::max(top, w.windowPos.y); + right = std::min(right, w.windowPos.x + w.width); + bottom = std::min(bottom, w.windowPos.y + w.height); + if (left >= right) + return; + if (top >= bottom) + return; + + // Draw the window and any other overlapping transparent windows + for (auto it = WindowGetIterator(&w); it != g_window_list.end(); it++) + { + auto* v = (*it).get(); + if (v->flags & WF_DEAD) + continue; + if ((&w == v || (v->flags & WF_TRANSPARENT)) && WindowIsVisible(*v)) + { + WindowDrawSingle(dpi, *v, left, top, right, bottom); + } + } + } + + static void WindowDrawSingle(DrawPixelInfo& dpi, WindowBase& w, int32_t left, int32_t top, int32_t right, int32_t bottom) + { + assert(dpi.zoom_level == ZoomLevel{ 0 }); + // Copy dpi so we can crop it + DrawPixelInfo copy = dpi; + + // Clamp left to 0 + int32_t overflow = left - copy.x; + if (overflow > 0) + { + copy.x += overflow; + copy.width -= overflow; + if (copy.width <= 0) + return; + copy.pitch += overflow; + copy.bits += overflow; + } + + // Clamp width to right + overflow = copy.x + copy.width - right; + if (overflow > 0) + { + copy.width -= overflow; + if (copy.width <= 0) + return; + copy.pitch += overflow; + } + + // Clamp top to 0 + overflow = top - copy.y; + if (overflow > 0) + { + copy.y += overflow; + copy.height -= overflow; + if (copy.height <= 0) + return; + copy.bits += copy.LineStride() * overflow; + } + + // Clamp height to bottom + overflow = copy.y + copy.height - bottom; + if (overflow > 0) + { + copy.height -= overflow; + if (copy.height <= 0) + return; + } + + // Invalidate modifies the window colours so first get the correct + // colour before setting the global variables for the string painting + w.OnPrepareDraw(); + + // Text colouring + gCurrentWindowColours[0] = w.colours[0].colour; + gCurrentWindowColours[1] = w.colours[1].colour; + gCurrentWindowColours[2] = w.colours[2].colour; + + w.OnDraw(copy); + } + + bool isToolActive(WindowClass cls) + { + return InputTestFlag(INPUT_FLAG_TOOL_ACTIVE) && gCurrentToolWidget.window_classification == cls; + } + + bool isToolActive(WindowClass cls, rct_windownumber number) + { + return isToolActive(cls) && gCurrentToolWidget.window_number == number; + } + + bool isToolActive(WindowClass cls, WidgetIndex widgetIndex) + { + return isToolActive(cls) && gCurrentToolWidget.widget_index == widgetIndex; + } + + bool isToolActive(WindowClass cls, WidgetIndex widgetIndex, rct_windownumber number) + { + return isToolActive(cls, widgetIndex) && gCurrentToolWidget.window_number == number; + } + + bool isToolActive(const WindowBase& w, WidgetIndex widgetIndex) + { + return isToolActive(w.classification, widgetIndex, w.number); + } + + /** + * + * rct2: 0x006EE212 + * + * @param tool (al) + * @param widgetIndex (dx) + * @param w (esi) + */ + bool ToolSet(const WindowBase& w, WidgetIndex widgetIndex, Tool tool) + { + if (InputTestFlag(INPUT_FLAG_TOOL_ACTIVE)) + { + if (w.classification == gCurrentToolWidget.window_classification && w.number == gCurrentToolWidget.window_number + && widgetIndex == gCurrentToolWidget.widget_index) + { + ToolCancel(); + return true; + } + + ToolCancel(); + } + + InputSetFlag(INPUT_FLAG_TOOL_ACTIVE, true); + InputSetFlag(INPUT_FLAG_4, false); + InputSetFlag(INPUT_FLAG_6, false); + gCurrentToolId = tool; + gCurrentToolWidget.window_classification = w.classification; + gCurrentToolWidget.window_number = w.number; + gCurrentToolWidget.widget_index = widgetIndex; + return false; + } + + /** + * + * rct2: 0x006EE281 + */ + void ToolCancel() + { + if (InputTestFlag(INPUT_FLAG_TOOL_ACTIVE)) + { + InputSetFlag(INPUT_FLAG_TOOL_ACTIVE, false); + + MapInvalidateSelectionRect(); + MapInvalidateMapSelectionTiles(); + + // Reset map selection + gMapSelectFlags = 0; + + if (gCurrentToolWidget.widget_index != kWidgetIndexNull) + { + // Invalidate tool widget + WidgetInvalidateByNumber( + gCurrentToolWidget.window_classification, gCurrentToolWidget.window_number, + gCurrentToolWidget.widget_index); + + // Abort tool event + auto* windowMgr = GetContext()->GetUiContext()->GetWindowManager(); + WindowBase* w = windowMgr->FindByNumber( + gCurrentToolWidget.window_classification, gCurrentToolWidget.window_number); + if (w != nullptr) + w->OnToolAbort(gCurrentToolWidget.widget_index); + } + } + } + + /** + * rct2: 0x0066B905 + */ + void WindowResizeGui(int32_t width, int32_t height) + { + WindowResizeGuiScenarioEditor(width, height); + if (gScreenFlags & SCREEN_FLAGS_EDITOR) + return; + + auto* windowMgr = GetContext()->GetUiContext()->GetWindowManager(); + WindowBase* titleWind = windowMgr->FindByClass(WindowClass::TitleMenu); + if (titleWind != nullptr) + { + titleWind->windowPos.x = (width - titleWind->width) / 2; + titleWind->windowPos.y = height - 182; + } + + WindowBase* versionWind = windowMgr->FindByClass(WindowClass::TitleVersion); + if (versionWind != nullptr) + versionWind->windowPos.y = height - 30; + + WindowBase* exitWind = windowMgr->FindByClass(WindowClass::TitleExit); + if (exitWind != nullptr) + { + exitWind->windowPos.x = width - 40; + exitWind->windowPos.y = height - 64; + } + + WindowBase* optionsWind = windowMgr->FindByClass(WindowClass::TitleOptions); + if (optionsWind != nullptr) + { + optionsWind->windowPos.x = width - 80; + } + + // Keep options window centred after a resize + WindowBase* optionsWindow = windowMgr->FindByClass(WindowClass::Options); + if (optionsWindow != nullptr) + { + optionsWindow->windowPos.x = (ContextGetWidth() - optionsWindow->width) / 2; + optionsWindow->windowPos.y = (ContextGetHeight() - optionsWindow->height) / 2; + } + + // Keep progress bar window centred after a resize + WindowBase* ProgressWindow = windowMgr->FindByClass(WindowClass::ProgressWindow); + if (ProgressWindow != nullptr) + { + ProgressWindow->windowPos.x = (ContextGetWidth() - ProgressWindow->width) / 2; + ProgressWindow->windowPos.y = (ContextGetHeight() - ProgressWindow->height) / 2; + } + + GfxInvalidateScreen(); + } + + /** + * rct2: 0x0066F0DD + */ + void WindowResizeGuiScenarioEditor(int32_t width, int32_t height) + { + auto* mainWind = WindowGetMain(); + if (mainWind != nullptr) + { + Viewport* viewport = mainWind->viewport; + mainWind->width = width; + mainWind->height = height; + viewport->width = width; + viewport->height = height; + if (!mainWind->widgets.empty() && mainWind->widgets[WC_MAIN_WINDOW__0].type == WindowWidgetType::Viewport) + { + mainWind->widgets[WC_MAIN_WINDOW__0].right = width; + mainWind->widgets[WC_MAIN_WINDOW__0].bottom = height; + } + } + + auto* windowMgr = GetContext()->GetUiContext()->GetWindowManager(); + + WindowBase* topWind = windowMgr->FindByClass(WindowClass::TopToolbar); + if (topWind != nullptr) + { + topWind->width = std::max(640, width); + } + + WindowBase* bottomWind = windowMgr->FindByClass(WindowClass::BottomToolbar); + if (bottomWind != nullptr) + { + bottomWind->windowPos.y = height - 32; + bottomWind->width = std::max(640, width); + } + } + + /** + * + * rct2: 0x006CBCC3 + */ + void WindowCloseConstructionWindows() + { + WindowCloseByClass(WindowClass::RideConstruction); + WindowCloseByClass(WindowClass::Footpath); + WindowCloseByClass(WindowClass::TrackDesignList); + WindowCloseByClass(WindowClass::TrackDesignPlace); + } + + /** + * Update zoom based volume attenuation for ride music and clear music list. + * rct2: 0x006BC348 + */ + void WindowUpdateViewportRideMusic() + { + RideAudio::ClearAllViewportInstances(); + g_music_tracking_viewport = nullptr; + + for (auto it = g_window_list.rbegin(); it != g_window_list.rend(); it++) + { + auto w = it->get(); + auto viewport = w->viewport; + if (viewport == nullptr || !(viewport->flags & VIEWPORT_FLAG_SOUND_ON)) + continue; + + g_music_tracking_viewport = viewport; + gWindowAudioExclusive = w; + + if (viewport->zoom <= ZoomLevel{ 0 }) + Audio::gVolumeAdjustZoom = 0; + else if (viewport->zoom == ZoomLevel{ 1 }) + Audio::gVolumeAdjustZoom = 30; + else + Audio::gVolumeAdjustZoom = 60; + break; + } + } + + /** + * + * rct2: 0x006EE3C3 + */ + void TextinputCancel() + { + WindowCloseByClass(WindowClass::Textinput); + } + + bool WindowIsVisible(WindowBase& w) + { + // w->visibility is used to prevent repeat calculations within an iteration by caching the result + + if (w.visibility == VisibilityCache::Visible) + return true; + if (w.visibility == VisibilityCache::Covered) + return false; + + // only consider viewports, consider the main window always visible + if (w.viewport == nullptr || w.classification == WindowClass::MainWindow) + { + // default to previous behaviour + w.visibility = VisibilityCache::Visible; + return true; + } + + // start from the window above the current + auto itPos = WindowGetIterator(&w); + for (auto it = std::next(itPos); it != g_window_list.end(); it++) + { + auto& w_other = *(*it); + if (w_other.flags & WF_DEAD) + continue; + + // if covered by a higher window, no rendering needed + if (w_other.windowPos.x <= w.windowPos.x && w_other.windowPos.y <= w.windowPos.y + && w_other.windowPos.x + w_other.width >= w.windowPos.x + w.width + && w_other.windowPos.y + w_other.height >= w.windowPos.y + w.height) + { + w.visibility = VisibilityCache::Covered; + w.viewport->visibility = VisibilityCache::Covered; + return false; + } + } + + // default to previous behaviour + w.visibility = VisibilityCache::Visible; + w.viewport->visibility = VisibilityCache::Visible; + return true; + } + + /** + * + * rct2: 0x006E7499 + * left (ax) + * top (bx) + * right (dx) + * bottom (bp) + */ + void WindowDrawAll(DrawPixelInfo& dpi, int32_t left, int32_t top, int32_t right, int32_t bottom) + { + auto windowDPI = dpi.Crop({ left, top }, { right - left, bottom - top }); + WindowVisitEach([&windowDPI, left, top, right, bottom](WindowBase* w) { + if (w->flags & WF_TRANSPARENT) + return; + if (right <= w->windowPos.x || bottom <= w->windowPos.y) + return; + if (left >= w->windowPos.x + w->width || top >= w->windowPos.y + w->height) + return; + WindowDraw(windowDPI, *w, left, top, right, bottom); + }); + } + + Viewport* WindowGetPreviousViewport(Viewport* current) + { + bool foundPrevious = (current == nullptr); + for (auto it = g_window_list.rbegin(); it != g_window_list.rend(); it++) + { + auto& w = **it; + if (w.flags & WF_DEAD) + continue; + if (w.viewport != nullptr) + { + if (foundPrevious) + { + return w.viewport; + } + if (w.viewport == current) + { + foundPrevious = true; + } + } + } + return nullptr; + } + + void WindowResetVisibilities() + { + // reset window visibility status to unknown + WindowVisitEach([](WindowBase* w) { + w->visibility = VisibilityCache::Unknown; + if (w->viewport != nullptr) + { + w->viewport->visibility = VisibilityCache::Unknown; + } + }); + } + + void WindowInitAll() + { + WindowCloseAllExceptFlags(0); + } + + void WindowFollowSprite(WindowBase& w, EntityId spriteIndex) + { + if (spriteIndex.ToUnderlying() < MAX_ENTITIES || spriteIndex.IsNull()) + { + w.viewport_smart_follow_sprite = spriteIndex; + } + } + + void WindowUnfollowSprite(WindowBase& w) + { + w.viewport_smart_follow_sprite = EntityId::GetNull(); + w.viewport_target_sprite = EntityId::GetNull(); + } + + Viewport* WindowGetViewport(WindowBase* w) + { + if (w == nullptr) + { + return nullptr; + } + + return w->viewport; + } +} // namespace OpenRCT2 diff --git a/src/openrct2/interface/Window.h b/src/openrct2/interface/Window.h index 10995d9afe..668e9445d8 100644 --- a/src/openrct2/interface/Window.h +++ b/src/openrct2/interface/Window.h @@ -11,303 +11,202 @@ #include "../Identifiers.h" #include "../core/EnumUtils.hpp" -#include "../drawing/ImageId.hpp" -#include "../localisation/Formatter.h" -#include "../ride/RideTypes.h" #include "../windows/TileInspectorGlobals.h" #include "../world/Location.hpp" -#include "../world/ScenerySelection.h" #include "Colour.h" #include "Widget.h" #include "WindowClasses.h" #include "ZoomLevel.h" #include -#include #include #include -#include #include struct DrawPixelInfo; -struct WindowBase; struct TrackDesignFileRef; struct ScenarioIndexEntry; -struct WindowCloseModifier; enum class VisibilityCache : uint8_t; enum class CursorID : uint8_t; enum class CloseWindowModifier : uint8_t; -using rct_windownumber = int16_t; - namespace OpenRCT2 { + using rct_windownumber = int16_t; + + struct WindowBase; + struct WindowCloseModifier; + enum class RideConstructionState : uint8_t; -} -struct WindowIdentifier -{ - WindowClass classification; - rct_windownumber number; -}; - -struct WidgetIdentifier -{ - WindowIdentifier window; - WidgetIndex widget_index; -}; - -extern WindowCloseModifier gLastCloseModifier; - -using WidgetFlags = uint32_t; -namespace OpenRCT2::WIDGET_FLAGS -{ - const WidgetFlags TEXT_IS_STRING = 1 << 0; - const WidgetFlags IS_PRESSED = 1 << 2; - const WidgetFlags IS_DISABLED = 1 << 3; - const WidgetFlags TOOLTIP_IS_STRING = 1 << 4; - const WidgetFlags IS_HIDDEN = 1 << 5; - const WidgetFlags IS_HOLDABLE = 1 << 6; -} // namespace OpenRCT2::WIDGET_FLAGS - -enum class WindowWidgetType : uint8_t; - -struct Widget -{ - WindowWidgetType type{}; - uint8_t colour{}; - int16_t left{}; - int16_t right{}; - int16_t top{}; - int16_t bottom{}; - union + struct WindowIdentifier { - uint32_t content; - ImageId image{}; - StringId text; - utf8* string; + WindowClass classification; + rct_windownumber number; }; - StringId tooltip{ STR_NONE }; - // New properties - WidgetFlags flags{}; - utf8* sztooltip{}; - - int16_t width() const + struct WidgetIdentifier { - return right - left; - } + WindowIdentifier window; + WidgetIndex widget_index; + }; - int16_t height() const + extern WindowCloseModifier gLastCloseModifier; + + /** + * Viewport structure + */ + struct Viewport { - return bottom - top; - } + int32_t width{}; + int32_t height{}; + ScreenCoordsXY pos{}; + ScreenCoordsXY viewPos{}; + uint32_t flags{}; + ZoomLevel zoom{}; + uint8_t rotation{}; + VisibilityCache visibility{}; - int16_t midX() const - { - return (left + right) / 2; - } - - int16_t midY() const - { - return (top + bottom) / 2; - } - - int16_t textTop() const - { - if (height() >= 10) - return std::max(top, top + (height() / 2) - 5); - - return top - 1; - } - - void moveRight(int32_t amount) - { - left += amount; - right += amount; - } - - void moveDown(int32_t amount) - { - top += amount; - bottom += amount; - } - - void moveTo(ScreenCoordsXY coords) - { - moveRight(coords.x - left); - moveDown(coords.y - top); - } - - void moveToX(int16_t x) - { - moveRight(x - left); - } - - void moveToY(int16_t y) - { - moveDown(y - top); - } - - bool IsVisible() const - { - return !(flags & OpenRCT2::WIDGET_FLAGS::IS_HIDDEN); - } -}; - -/** - * Viewport structure - */ -struct Viewport -{ - int32_t width{}; - int32_t height{}; - ScreenCoordsXY pos{}; - ScreenCoordsXY viewPos{}; - uint32_t flags{}; - ZoomLevel zoom{}; - uint8_t rotation{}; - VisibilityCache visibility{}; - - [[nodiscard]] constexpr int32_t ViewWidth() const - { - return zoom.ApplyTo(width); - } - - [[nodiscard]] constexpr int32_t ViewHeight() const - { - return zoom.ApplyTo(height); - } - - // Use this function on coordinates that are relative to the viewport zoom i.e. a peeps x, y position after transforming - // from its x, y, z - [[nodiscard]] constexpr bool Contains(const ScreenCoordsXY& vpos) const - { - return ( - vpos.y >= viewPos.y && vpos.y < viewPos.y + ViewHeight() && vpos.x >= viewPos.x - && vpos.x < viewPos.x + ViewWidth()); - } - - // Use this function on coordinates that are relative to the screen that is been drawn i.e. the cursor position - [[nodiscard]] constexpr bool ContainsScreen(const ScreenCoordsXY& sPos) const - { - return (sPos.x >= pos.x && sPos.x < pos.x + width && sPos.y >= pos.y && sPos.y < pos.y + height); - } - - [[nodiscard]] ScreenCoordsXY ScreenToViewportCoord(const ScreenCoordsXY& screenCoord) const; - - void Invalidate() const; -}; - -struct Focus -{ - using CoordinateFocus = CoordsXYZ; - using EntityFocus = EntityId; - - ZoomLevel zoom{}; - std::variant data; - - template - constexpr explicit Focus(T newValue, ZoomLevel newZoom = {}) - { - data = newValue; - zoom = newZoom; - } - - CoordsXYZ GetPos() const; - - constexpr bool operator==(const Focus& other) const - { - if (zoom != other.zoom) + [[nodiscard]] constexpr int32_t ViewWidth() const { - return false; + return zoom.ApplyTo(width); } - return data == other.data; - } - constexpr bool operator!=(const Focus& other) const + + [[nodiscard]] constexpr int32_t ViewHeight() const + { + return zoom.ApplyTo(height); + } + + // Use this function on coordinates that are relative to the viewport zoom i.e. a peeps x, y position after transforming + // from its x, y, z + [[nodiscard]] constexpr bool Contains(const ScreenCoordsXY& vpos) const + { + return ( + vpos.y >= viewPos.y && vpos.y < viewPos.y + ViewHeight() && vpos.x >= viewPos.x + && vpos.x < viewPos.x + ViewWidth()); + } + + // Use this function on coordinates that are relative to the screen that is been drawn i.e. the cursor position + [[nodiscard]] constexpr bool ContainsScreen(const ScreenCoordsXY& sPos) const + { + return (sPos.x >= pos.x && sPos.x < pos.x + width && sPos.y >= pos.y && sPos.y < pos.y + height); + } + + [[nodiscard]] ScreenCoordsXY ScreenToViewportCoord(const ScreenCoordsXY& screenCoord) const; + + void Invalidate() const; + }; + + struct Focus { - return !(*this == other); - } -}; + using CoordinateFocus = CoordsXYZ; + using EntityFocus = EntityId; -struct WindowCloseModifier -{ - WindowIdentifier window; - CloseWindowModifier modifier; -}; + ZoomLevel zoom{}; + std::variant data; -struct WindowBase; + template + constexpr explicit Focus(T newValue, ZoomLevel newZoom = {}) + { + data = newValue; + zoom = newZoom; + } + + CoordsXYZ GetPos() const; + + constexpr bool operator==(const Focus& other) const + { + if (zoom != other.zoom) + { + return false; + } + return data == other.data; + } + constexpr bool operator!=(const Focus& other) const + { + return !(*this == other); + } + }; + + struct WindowCloseModifier + { + WindowIdentifier window; + CloseWindowModifier modifier; + }; + + struct WindowBase; #define RCT_WINDOW_RIGHT(w) ((w)->windowPos.x + (w)->width) #define RCT_WINDOW_BOTTOM(w) ((w)->windowPos.y + (w)->height) -enum WINDOW_FLAGS -{ - /* - WF_TIMEOUT_SHL = 0, - WF_TIMEOUT_MASK = 7, - WF_DRAGGING = 1 << 3, - WF_SCROLLER_UP = 1 << 4, - WF_SCROLLER_DOWN = 1 << 5, - WF_SCROLLER_MIDDLE = 1 << 6, - WF_DISABLE_VP_SCROLL = 1 << 9, - */ + enum WINDOW_FLAGS + { + /* + WF_TIMEOUT_SHL = 0, + WF_TIMEOUT_MASK = 7, + WF_DRAGGING = 1 << 3, + WF_SCROLLER_UP = 1 << 4, + WF_SCROLLER_DOWN = 1 << 5, + WF_SCROLLER_MIDDLE = 1 << 6, + WF_DISABLE_VP_SCROLL = 1 << 9, + */ - WF_STICK_TO_BACK = (1 << 0), - WF_STICK_TO_FRONT = (1 << 1), - WF_NO_SCROLLING = (1 << 2), // User is unable to scroll this viewport - WF_SCROLLING_TO_LOCATION = (1 << 3), - WF_TRANSPARENT = (1 << 4), - WF_NO_BACKGROUND = (1 << 5), // Instead of half transparency, completely remove the window background - WF_DEAD = (1u << 6), // Window is closed and will be deleted in the next update. - WF_7 = (1 << 7), - WF_RESIZABLE = (1 << 8), - WF_NO_AUTO_CLOSE = (1 << 9), // Don't auto close this window if too many windows are open - WF_10 = (1 << 10), - WF_WHITE_BORDER_ONE = (1 << 12), - WF_WHITE_BORDER_MASK = (1 << 12) | (1 << 13), + WF_STICK_TO_BACK = (1 << 0), + WF_STICK_TO_FRONT = (1 << 1), + WF_NO_SCROLLING = (1 << 2), // User is unable to scroll this viewport + WF_SCROLLING_TO_LOCATION = (1 << 3), + WF_TRANSPARENT = (1 << 4), + WF_NO_BACKGROUND = (1 << 5), // Instead of half transparency, completely remove the window background + WF_DEAD = (1u << 6), // Window is closed and will be deleted in the next update. + WF_7 = (1 << 7), + WF_RESIZABLE = (1 << 8), + WF_NO_AUTO_CLOSE = (1 << 9), // Don't auto close this window if too many windows are open + WF_10 = (1 << 10), + WF_WHITE_BORDER_ONE = (1 << 12), + WF_WHITE_BORDER_MASK = (1 << 12) | (1 << 13), - WF_NO_SNAPPING = (1 << 15), + WF_NO_SNAPPING = (1 << 15), - // Create only flags - WF_AUTO_POSITION = (1 << 16), - WF_CENTRE_SCREEN = (1 << 17), -}; + // Create only flags + WF_AUTO_POSITION = (1 << 16), + WF_CENTRE_SCREEN = (1 << 17), + }; -enum -{ - WV_PARK_AWARDS, - WV_PARK_RATING, - WV_PARK_OBJECTIVE, - WV_PARK_GUESTS, - WV_FINANCES_RESEARCH, - WV_RIDE_RESEARCH, - WV_MAZE_CONSTRUCTION, - WV_NETWORK_PASSWORD, - WV_EDITOR_BOTTOM_TOOLBAR, - WV_CHANGELOG, - WV_NEW_VERSION_INFO, - WV_FINANCE_MARKETING, - WV_CONTRIBUTORS, -}; + enum + { + WV_PARK_AWARDS, + WV_PARK_RATING, + WV_PARK_OBJECTIVE, + WV_PARK_GUESTS, + WV_FINANCES_RESEARCH, + WV_RIDE_RESEARCH, + WV_MAZE_CONSTRUCTION, + WV_NETWORK_PASSWORD, + WV_EDITOR_BOTTOM_TOOLBAR, + WV_CHANGELOG, + WV_NEW_VERSION_INFO, + WV_FINANCE_MARKETING, + WV_CONTRIBUTORS, + }; -enum WindowDetail -{ - WD_BANNER, - WD_NEW_CAMPAIGN, - WD_DEMOLISH_RIDE, - WD_REFURBISH_RIDE, - WD_SIGN, - WD_SIGN_SMALL, + enum WindowDetail + { + WD_BANNER, + WD_NEW_CAMPAIGN, + WD_DEMOLISH_RIDE, + WD_REFURBISH_RIDE, + WD_SIGN, + WD_SIGN_SMALL, - WD_PLAYER, + WD_PLAYER, - WD_VEHICLE, - WD_TRACK, + WD_VEHICLE, + WD_TRACK, - WD_NULL = 255, -}; + WD_NULL = 255, + }; +} // namespace OpenRCT2 #define validate_global_widx(wc, widx) \ static_assert(widx == wc##__##widx, "Global WIDX of " #widx " doesn't match actual value.") @@ -444,105 +343,109 @@ enum class Tool Bulldozer = 27, }; -struct WidgetRef +namespace OpenRCT2 { - WindowClass window_classification; - rct_windownumber window_number; - WidgetIndex widget_index; -}; + struct WidgetRef + { + WindowClass window_classification; + rct_windownumber window_number; + WidgetIndex widget_index; + }; -extern Tool gCurrentToolId; -extern WidgetRef gCurrentToolWidget; + extern Tool gCurrentToolId; + extern WidgetRef gCurrentToolWidget; -using modal_callback = void (*)(int32_t result); -using CloseCallback = void (*)(); + using modal_callback = void (*)(int32_t result); + using CloseCallback = void (*)(); -constexpr int8_t kWindowLimitMin = 4; -constexpr int8_t kWindowLimitMax = 64; -constexpr int8_t kWindowLimitReserved = 4; // Used to reserve room for the main viewport, toolbars, etc. + constexpr int8_t kWindowLimitMin = 4; + constexpr int8_t kWindowLimitMax = 64; + constexpr int8_t kWindowLimitReserved = 4; // Used to reserve room for the main viewport, toolbars, etc. -extern WindowBase* gWindowAudioExclusive; + extern WindowBase* gWindowAudioExclusive; -extern uint32_t gWindowUpdateTicks; + extern uint32_t gWindowUpdateTicks; -extern colour_t gCurrentWindowColours[3]; + extern colour_t gCurrentWindowColours[3]; -std::list>::iterator WindowGetIterator(const WindowBase* w); -void WindowVisitEach(std::function func); + std::list>::iterator WindowGetIterator(const WindowBase* w); + void WindowVisitEach(std::function func); -void WindowSetFlagForAllViewports(uint32_t viewportFlag, bool enabled); + void WindowSetFlagForAllViewports(uint32_t viewportFlag, bool enabled); -void WindowDispatchUpdateAll(); -void WindowUpdateAllViewports(); -void WindowUpdateAll(); -void WindowNotifyLanguageChange(); + void WindowDispatchUpdateAll(); + void WindowUpdateAllViewports(); + void WindowUpdateAll(); + void WindowNotifyLanguageChange(); -void WindowSetWindowLimit(int32_t value); + void WindowSetWindowLimit(int32_t value); -WindowBase* WindowBringToFront(WindowBase& w); -WindowBase* WindowBringToFrontByClass(WindowClass cls); -WindowBase* WindowBringToFrontByClassWithFlags(WindowClass cls, uint16_t flags); -WindowBase* WindowBringToFrontByNumber(WindowClass cls, rct_windownumber number); + WindowBase* WindowBringToFront(WindowBase& w); + WindowBase* WindowBringToFrontByClass(WindowClass cls); + WindowBase* WindowBringToFrontByClassWithFlags(WindowClass cls, uint16_t flags); + WindowBase* WindowBringToFrontByNumber(WindowClass cls, rct_windownumber number); -void WindowClose(WindowBase& window); -void WindowCloseByClass(WindowClass cls); -void WindowCloseByNumber(WindowClass cls, rct_windownumber number); -void WindowCloseByNumber(WindowClass cls, EntityId number); -void WindowCloseTop(); -void WindowCloseAll(); -void WindowCloseAllExceptClass(WindowClass cls); -void WindowCloseAllExceptFlags(uint16_t flags); -void WindowCloseAllExceptNumberAndClass(rct_windownumber number, WindowClass cls); -void WindowInvalidateByClass(WindowClass cls); -void WindowInvalidateByNumber(WindowClass cls, rct_windownumber number); -void WindowInvalidateByNumber(WindowClass cls, EntityId id); -void WindowInvalidateAll(); -void WidgetInvalidate(WindowBase& w, WidgetIndex widgetIndex); -void WidgetInvalidateByClass(WindowClass cls, WidgetIndex widgetIndex); -void WidgetInvalidateByNumber(WindowClass cls, rct_windownumber number, WidgetIndex widgetIndex); + void WindowClose(WindowBase& window); + void WindowCloseByClass(WindowClass cls); + void WindowCloseByNumber(WindowClass cls, rct_windownumber number); + void WindowCloseByNumber(WindowClass cls, EntityId number); + void WindowCloseTop(); + void WindowCloseAll(); + void WindowCloseAllExceptClass(WindowClass cls); + void WindowCloseAllExceptFlags(uint16_t flags); + void WindowCloseAllExceptNumberAndClass(rct_windownumber number, WindowClass cls); + void WindowInvalidateByClass(WindowClass cls); + void WindowInvalidateByNumber(WindowClass cls, rct_windownumber number); + void WindowInvalidateByNumber(WindowClass cls, EntityId id); + void WindowInvalidateAll(); + void WidgetInvalidate(WindowBase& w, WidgetIndex widgetIndex); + void WidgetInvalidateByClass(WindowClass cls, WidgetIndex widgetIndex); + void WidgetInvalidateByNumber(WindowClass cls, rct_windownumber number, WidgetIndex widgetIndex); -int32_t WindowGetScrollDataIndex(const WindowBase& w, WidgetIndex widget_index); + int32_t WindowGetScrollDataIndex(const WindowBase& w, WidgetIndex widget_index); -void WindowPushOthersRight(WindowBase& w); -void WindowPushOthersBelow(WindowBase& w1); + void WindowPushOthersRight(WindowBase& w); + void WindowPushOthersBelow(WindowBase& w1); -WindowBase* WindowGetMain(); + WindowBase* WindowGetMain(); -void WindowScrollToLocation(WindowBase& w, const CoordsXYZ& coords); -void WindowViewportGetMapCoordsByCursor( - const WindowBase& w, int32_t* map_x, int32_t* map_y, int32_t* offset_x, int32_t* offset_y); -void WindowViewportCentreTileAroundCursor(WindowBase& w, int32_t map_x, int32_t map_y, int32_t offset_x, int32_t offset_y); -void WindowCheckAllValidZoom(); -void WindowZoomSet(WindowBase& w, ZoomLevel zoomLevel, bool atCursor); + void WindowScrollToLocation(WindowBase& w, const CoordsXYZ& coords); + void WindowViewportGetMapCoordsByCursor( + const WindowBase& w, int32_t* map_x, int32_t* map_y, int32_t* offset_x, int32_t* offset_y); + void WindowViewportCentreTileAroundCursor(WindowBase& w, int32_t map_x, int32_t map_y, int32_t offset_x, int32_t offset_y); + void WindowCheckAllValidZoom(); + void WindowZoomSet(WindowBase& w, ZoomLevel zoomLevel, bool atCursor); -void WindowDrawAll(DrawPixelInfo& dpi, int32_t left, int32_t top, int32_t right, int32_t bottom); -void WindowDraw(DrawPixelInfo& dpi, WindowBase& w, int32_t left, int32_t top, int32_t right, int32_t bottom); + void WindowDrawAll(DrawPixelInfo& dpi, int32_t left, int32_t top, int32_t right, int32_t bottom); + void WindowDraw(DrawPixelInfo& dpi, WindowBase& w, int32_t left, int32_t top, int32_t right, int32_t bottom); -bool isToolActive(WindowClass cls); -bool isToolActive(WindowClass cls, rct_windownumber number); -bool isToolActive(WindowClass cls, WidgetIndex widgetIndex); -bool isToolActive(WindowClass cls, WidgetIndex widgetIndex, rct_windownumber number); -bool isToolActive(const WindowBase& w, WidgetIndex widgetIndex); -bool ToolSet(const WindowBase& w, WidgetIndex widgetIndex, Tool tool); -void ToolCancel(); + bool isToolActive(WindowClass cls); + bool isToolActive(WindowClass cls, rct_windownumber number); + bool isToolActive(WindowClass cls, WidgetIndex widgetIndex); + bool isToolActive(WindowClass cls, WidgetIndex widgetIndex, rct_windownumber number); + bool isToolActive(const WindowBase& w, WidgetIndex widgetIndex); + bool ToolSet(const WindowBase& w, WidgetIndex widgetIndex, Tool tool); + void ToolCancel(); -void WindowCloseConstructionWindows(); + void WindowCloseConstructionWindows(); -void WindowUpdateViewportRideMusic(); + void WindowUpdateViewportRideMusic(); -Viewport* WindowGetViewport(WindowBase* window); + Viewport* WindowGetViewport(WindowBase* window); -// Open window functions -void WindowResizeGui(int32_t width, int32_t height); -void WindowResizeGuiScenarioEditor(int32_t width, int32_t height); + // Open window functions + void WindowResizeGui(int32_t width, int32_t height); + void WindowResizeGuiScenarioEditor(int32_t width, int32_t height); -void TextinputCancel(); + void TextinputCancel(); -bool WindowIsVisible(WindowBase& w); + bool WindowIsVisible(WindowBase& w); -Viewport* WindowGetPreviousViewport(Viewport* current); -void WindowResetVisibilities(); -void WindowInitAll(); + Viewport* WindowGetPreviousViewport(Viewport* current); + void WindowResetVisibilities(); + void WindowInitAll(); -void WindowFollowSprite(WindowBase& w, EntityId spriteIndex); -void WindowUnfollowSprite(WindowBase& w); + void WindowFollowSprite(WindowBase& w, EntityId spriteIndex); + void WindowUnfollowSprite(WindowBase& w); + +} // namespace OpenRCT2 diff --git a/src/openrct2/interface/Window_internal.cpp b/src/openrct2/interface/Window_internal.cpp index 443e97c382..bded773da8 100644 --- a/src/openrct2/interface/Window_internal.cpp +++ b/src/openrct2/interface/Window_internal.cpp @@ -5,33 +5,36 @@ #include "Cursors.h" #include "Viewport.h" -void WindowBase::SetLocation(const CoordsXYZ& coords) +namespace OpenRCT2 { - WindowScrollToLocation(*this, coords); - flags &= ~WF_SCROLLING_TO_LOCATION; -} + void WindowBase::SetLocation(const CoordsXYZ& coords) + { + WindowScrollToLocation(*this, coords); + flags &= ~WF_SCROLLING_TO_LOCATION; + } -void WindowBase::Invalidate() -{ - GfxSetDirtyBlocks({ windowPos, windowPos + ScreenCoordsXY{ width, height } }); -} + void WindowBase::Invalidate() + { + GfxSetDirtyBlocks({ windowPos, windowPos + ScreenCoordsXY{ width, height } }); + } -void WindowBase::RemoveViewport() -{ - if (viewport == nullptr) - return; + void WindowBase::RemoveViewport() + { + if (viewport == nullptr) + return; - ViewportRemove(viewport); - viewport = nullptr; -} + ViewportRemove(viewport); + viewport = nullptr; + } -void WindowBase::SetWidgets(const std::span newWidgets) -{ - widgets.clear(); - widgets.insert(widgets.end(), newWidgets.begin(), newWidgets.end()); -} + void WindowBase::SetWidgets(const std::span newWidgets) + { + widgets.clear(); + widgets.insert(widgets.end(), newWidgets.begin(), newWidgets.end()); + } -CursorID WindowBase::OnCursor(WidgetIndex, const ScreenCoordsXY&, CursorID) -{ - return CursorID::Arrow; -} + CursorID WindowBase::OnCursor(WidgetIndex, const ScreenCoordsXY&, CursorID) + { + return CursorID::Arrow; + } +} // namespace OpenRCT2 diff --git a/src/openrct2/interface/Window_internal.h b/src/openrct2/interface/Window_internal.h index 9a9c9c217d..471989b5eb 100644 --- a/src/openrct2/interface/Window_internal.h +++ b/src/openrct2/interface/Window_internal.h @@ -9,12 +9,14 @@ #pragma once +#include "../localisation/Formatter.h" #include "Colour.h" #include "ScrollArea.h" #include "Window.h" #include #include +#include #include enum class TileInspectorPage : int16_t; @@ -28,155 +30,158 @@ struct RCTObjectEntry; #pragma GCC diagnostic ignored "-Wsuggest-final-types" #endif -/** - * Window structure - * size: 0x4C0 - */ -struct WindowBase +namespace OpenRCT2 { - Viewport* viewport{}; - uint64_t disabled_widgets{}; - uint64_t pressed_widgets{}; - uint64_t hold_down_widgets{}; - std::vector widgets{}; - ScreenCoordsXY windowPos; - int16_t width{}; - int16_t height{}; - int16_t min_width{}; - int16_t max_width{}; - int16_t min_height{}; - int16_t max_height{}; - union + /** + * Window structure + * size: 0x4C0 + */ + struct WindowBase { - rct_windownumber number{}; - RideId rideId; + Viewport* viewport{}; + uint64_t disabled_widgets{}; + uint64_t pressed_widgets{}; + uint64_t hold_down_widgets{}; + std::vector widgets{}; + ScreenCoordsXY windowPos; + int16_t width{}; + int16_t height{}; + int16_t min_width{}; + int16_t max_width{}; + int16_t min_height{}; + int16_t max_height{}; + union + { + rct_windownumber number{}; + RideId rideId; + }; + uint16_t flags{}; + OpenRCT2::ScrollArea scrolls[3]; + uint16_t no_list_items{}; // 0 for no items + int16_t selected_list_item{}; // -1 for none selected + std::optional focus; + union + { + int16_t page{}; + TileInspectorPage tileInspectorPage; + }; + uint16_t frame_no{}; // updated every tic for motion in windows sprites + uint16_t list_information_type{}; // 0 for none + int16_t picked_peep_frame; // Animation frame of picked peep in staff window and guest window + int16_t selected_tab{}; + EntityId viewport_target_sprite{ EntityId::GetNull() }; + ScreenCoordsXY savedViewPos{}; + WindowClass classification{}; + ColourWithFlags colours[6]{}; + VisibilityCache visibility{}; + EntityId viewport_smart_follow_sprite{ EntityId::GetNull() }; // Handles setting viewport target sprite etc + + void SetLocation(const CoordsXYZ& coords); + void Invalidate(); + void RemoveViewport(); + void SetWidgets(const std::span newWidgets); + + WindowBase() = default; + WindowBase(WindowBase&) = delete; + virtual ~WindowBase() = default; + + WindowBase& operator=(const WindowBase&) = delete; + + // Events + virtual void OnOpen() + { + } + virtual bool CanClose() + { + return true; + } + virtual void OnClose() + { + } + virtual void OnResize() + { + } + virtual void OnUpdate() + { + } + virtual void OnPeriodicUpdate() + { + } + virtual void OnPrepareDraw() + { + } + virtual void OnDraw(DrawPixelInfo& dpi) + { + } + virtual void OnDrawWidget(WidgetIndex widgetIndex, DrawPixelInfo& dpi) + { + } + virtual OpenRCT2String OnTooltip(WidgetIndex widgetIndex, StringId fallback) + { + return { fallback, {} }; + } + virtual void OnMouseDown(WidgetIndex widgetIndex) + { + } + virtual void OnMouseUp(WidgetIndex widgetIndex) + { + } + virtual void OnDropdown(WidgetIndex widgetIndex, int32_t selectedIndex) + { + } + virtual void OnTextInput(WidgetIndex widgetIndex, std::string_view text) + { + } + virtual ScreenSize OnScrollGetSize(int32_t scrollIndex) + { + return {}; + } + virtual void OnScrollSelect(int32_t scrollIndex, int32_t scrollAreaType) + { + } + virtual void OnScrollMouseDrag(int32_t scrollIndex, const ScreenCoordsXY& screenCoords) + { + } + virtual void OnScrollMouseOver(int32_t scrollIndex, const ScreenCoordsXY& screenCoords) + { + } + virtual void OnScrollMouseDown(int32_t scrollIndex, const ScreenCoordsXY& screenCoords) + { + } + virtual void OnScrollDraw(int32_t scrollIndex, DrawPixelInfo& dpi) + { + } + virtual void OnToolUpdate(WidgetIndex widgetIndex, const ScreenCoordsXY& screenCoords) + { + } + virtual void OnToolDown(WidgetIndex widgetIndex, const ScreenCoordsXY& screenCoords) + { + } + virtual void OnToolDrag(WidgetIndex widgetIndex, const ScreenCoordsXY& screenCoords) + { + } + virtual void OnToolUp(WidgetIndex widgetIndex, const ScreenCoordsXY&) + { + } + virtual void OnToolAbort(WidgetIndex widgetIndex) + { + } + virtual void OnViewportRotate() + { + } + virtual void OnMoved(const ScreenCoordsXY&) + { + } + virtual CursorID OnCursor(WidgetIndex, const ScreenCoordsXY&, CursorID); + virtual void OnLanguageChange() + { + } }; - uint16_t flags{}; - OpenRCT2::ScrollArea scrolls[3]; - uint16_t no_list_items{}; // 0 for no items - int16_t selected_list_item{}; // -1 for none selected - std::optional focus; - union - { - int16_t page{}; - TileInspectorPage tileInspectorPage; - }; - uint16_t frame_no{}; // updated every tic for motion in windows sprites - uint16_t list_information_type{}; // 0 for none - int16_t picked_peep_frame; // Animation frame of picked peep in staff window and guest window - int16_t selected_tab{}; - EntityId viewport_target_sprite{ EntityId::GetNull() }; - ScreenCoordsXY savedViewPos{}; - WindowClass classification{}; - ColourWithFlags colours[6]{}; - VisibilityCache visibility{}; - EntityId viewport_smart_follow_sprite{ EntityId::GetNull() }; // Handles setting viewport target sprite etc - - void SetLocation(const CoordsXYZ& coords); - void Invalidate(); - void RemoveViewport(); - void SetWidgets(const std::span newWidgets); - - WindowBase() = default; - WindowBase(WindowBase&) = delete; - virtual ~WindowBase() = default; - - WindowBase& operator=(const WindowBase&) = delete; - - // Events - virtual void OnOpen() - { - } - virtual bool CanClose() - { - return true; - } - virtual void OnClose() - { - } - virtual void OnResize() - { - } - virtual void OnUpdate() - { - } - virtual void OnPeriodicUpdate() - { - } - virtual void OnPrepareDraw() - { - } - virtual void OnDraw(DrawPixelInfo& dpi) - { - } - virtual void OnDrawWidget(WidgetIndex widgetIndex, DrawPixelInfo& dpi) - { - } - virtual OpenRCT2String OnTooltip(WidgetIndex widgetIndex, StringId fallback) - { - return { fallback, {} }; - } - virtual void OnMouseDown(WidgetIndex widgetIndex) - { - } - virtual void OnMouseUp(WidgetIndex widgetIndex) - { - } - virtual void OnDropdown(WidgetIndex widgetIndex, int32_t selectedIndex) - { - } - virtual void OnTextInput(WidgetIndex widgetIndex, std::string_view text) - { - } - virtual ScreenSize OnScrollGetSize(int32_t scrollIndex) - { - return {}; - } - virtual void OnScrollSelect(int32_t scrollIndex, int32_t scrollAreaType) - { - } - virtual void OnScrollMouseDrag(int32_t scrollIndex, const ScreenCoordsXY& screenCoords) - { - } - virtual void OnScrollMouseOver(int32_t scrollIndex, const ScreenCoordsXY& screenCoords) - { - } - virtual void OnScrollMouseDown(int32_t scrollIndex, const ScreenCoordsXY& screenCoords) - { - } - virtual void OnScrollDraw(int32_t scrollIndex, DrawPixelInfo& dpi) - { - } - virtual void OnToolUpdate(WidgetIndex widgetIndex, const ScreenCoordsXY& screenCoords) - { - } - virtual void OnToolDown(WidgetIndex widgetIndex, const ScreenCoordsXY& screenCoords) - { - } - virtual void OnToolDrag(WidgetIndex widgetIndex, const ScreenCoordsXY& screenCoords) - { - } - virtual void OnToolUp(WidgetIndex widgetIndex, const ScreenCoordsXY&) - { - } - virtual void OnToolAbort(WidgetIndex widgetIndex) - { - } - virtual void OnViewportRotate() - { - } - virtual void OnMoved(const ScreenCoordsXY&) - { - } - virtual CursorID OnCursor(WidgetIndex, const ScreenCoordsXY&, CursorID); - virtual void OnLanguageChange() - { - } -}; #ifdef __WARN_SUGGEST_FINAL_METHODS__ #pragma GCC diagnostic pop #endif -// rct2: 0x01420078 -extern std::list> g_window_list; + // rct2: 0x01420078 + extern std::list> g_window_list; +} // namespace OpenRCT2 diff --git a/src/openrct2/network/NetworkPlayer.cpp b/src/openrct2/network/NetworkPlayer.cpp index ab381be97c..4eb0eeca31 100644 --- a/src/openrct2/network/NetworkPlayer.cpp +++ b/src/openrct2/network/NetworkPlayer.cpp @@ -39,13 +39,13 @@ void NetworkPlayer::Write(NetworkPacket& packet) void NetworkPlayer::IncrementNumCommands() { CommandsRan++; - WindowInvalidateByNumber(WindowClass::Player, Id); + OpenRCT2::WindowInvalidateByNumber(WindowClass::Player, Id); } void NetworkPlayer::AddMoneySpent(money64 cost) { MoneySpent += cost; - WindowInvalidateByNumber(WindowClass::Player, Id); + OpenRCT2::WindowInvalidateByNumber(WindowClass::Player, Id); } #endif diff --git a/src/openrct2/paint/Paint.Entity.cpp b/src/openrct2/paint/Paint.Entity.cpp index d0ef7259c7..7da6bc92d3 100644 --- a/src/openrct2/paint/Paint.Entity.cpp +++ b/src/openrct2/paint/Paint.Entity.cpp @@ -32,6 +32,7 @@ #include +using namespace OpenRCT2; using namespace OpenRCT2::Drawing; /** diff --git a/src/openrct2/windows/Intent.h b/src/openrct2/windows/Intent.h index 0f6068c608..3439d33442 100644 --- a/src/openrct2/windows/Intent.h +++ b/src/openrct2/windows/Intent.h @@ -17,112 +17,115 @@ #include #include -enum IntentAction +namespace OpenRCT2 { - INTENT_ACTION_MAP, - INTENT_ACTION_NEW_RIDE_OF_TYPE, - INTENT_ACTION_REFRESH_CAMPAIGN_RIDE_LIST, - INTENT_ACTION_REFRESH_NEW_RIDES, - INTENT_ACTION_REFRESH_RIDE_LIST, - INTENT_ACTION_UPDATE_MAZE_CONSTRUCTION, - INTENT_ACTION_RIDE_CONSTRUCTION_FOCUS, - INTENT_ACTION_RIDE_CONSTRUCTION_UPDATE_PIECES, - INTENT_ACTION_RIDE_CONSTRUCTION_UPDATE_ACTIVE_ELEMENTS, - INTENT_ACTION_INIT_SCENERY, - INTENT_ACTION_SET_DEFAULT_SCENERY_CONFIG, - INTENT_ACTION_REFRESH_SCENERY, - INTENT_ACTION_INVALIDATE_TICKER_NEWS, - INTENT_ACTION_REFRESH_GUEST_LIST, - INTENT_ACTION_CLEAR_TILE_INSPECTOR_CLIPBOARD, - INTENT_ACTION_REFRESH_STAFF_LIST, - INTENT_ACTION_INVALIDATE_VEHICLE_WINDOW, - INTENT_ACTION_RIDE_PAINT_RESET_VEHICLE, - INTENT_ACTION_UPDATE_CLIMATE, - INTENT_ACTION_UPDATE_GUEST_COUNT, - INTENT_ACTION_UPDATE_PARK_RATING, - INTENT_ACTION_UPDATE_DATE, - INTENT_ACTION_UPDATE_CASH, - INTENT_ACTION_UPDATE_BANNER, - INTENT_ACTION_UPDATE_RESEARCH, - INTENT_ACTION_UPDATE_VEHICLE_SOUNDS, - INTENT_ACTION_SET_MAP_TOOLTIP, - INTENT_ACTION_NEW_SCENERY, - INTENT_ACTION_TILE_MODIFY, - INTENT_ACTION_PROGRESS_OPEN, - INTENT_ACTION_PROGRESS_SET, - INTENT_ACTION_PROGRESS_CLOSE, - INTENT_ACTION_REMOVE_PROVISIONAL_ELEMENTS, - INTENT_ACTION_RESTORE_PROVISIONAL_ELEMENTS, - INTENT_ACTION_REMOVE_PROVISIONAL_FOOTPATH, - INTENT_ACTION_REMOVE_PROVISIONAL_TRACK_PIECE, - - INTENT_ACTION_NULL = 255, -}; - -// The maximum amount of data the Intent can hold, 8 should be sufficient, raise this if needed. -static constexpr size_t kIntentMaxDataSlots = 8; - -using IntentData = std::variant; -using IntentDataEntry = std::pair; -using IntentDataStorage = sfl::static_vector; - -class Intent -{ - WindowClass _Class{ WindowClass::Null }; - WindowDetail _WindowDetail{ WD_NULL }; - IntentAction _Action{ INTENT_ACTION_NULL }; - IntentDataStorage _Data; - -public: - explicit Intent(WindowClass windowClass); - explicit Intent(WindowDetail windowDetail); - explicit Intent(IntentAction windowclass); - - WindowClass GetWindowClass() const; - WindowDetail GetWindowDetail() const; - IntentAction GetAction() const; - - void* GetPointerExtra(uint32_t key) const; - std::string GetStringExtra(uint32_t key) const; - uint32_t GetUIntExtra(uint32_t key) const; - int32_t GetSIntExtra(uint32_t key) const; - CloseCallback GetCloseCallbackExtra(uint32_t key) const; - - Intent* PutExtra(uint32_t key, uint32_t value); - Intent* PutExtra(uint32_t key, void* value); - Intent* PutExtra(uint32_t key, int32_t value); - Intent* PutExtra(uint32_t key, std::string value); - Intent* PutExtra(uint32_t key, CloseCallback value); - - template - Intent* PutExtra(uint32_t key, const TIdentifier& value) + enum IntentAction { - const auto val = value.ToUnderlying(); - return PutExtra(key, static_cast(val)); - } -}; + INTENT_ACTION_MAP, + INTENT_ACTION_NEW_RIDE_OF_TYPE, + INTENT_ACTION_REFRESH_CAMPAIGN_RIDE_LIST, + INTENT_ACTION_REFRESH_NEW_RIDES, + INTENT_ACTION_REFRESH_RIDE_LIST, + INTENT_ACTION_UPDATE_MAZE_CONSTRUCTION, + INTENT_ACTION_RIDE_CONSTRUCTION_FOCUS, + INTENT_ACTION_RIDE_CONSTRUCTION_UPDATE_PIECES, + INTENT_ACTION_RIDE_CONSTRUCTION_UPDATE_ACTIVE_ELEMENTS, + INTENT_ACTION_INIT_SCENERY, + INTENT_ACTION_SET_DEFAULT_SCENERY_CONFIG, + INTENT_ACTION_REFRESH_SCENERY, + INTENT_ACTION_INVALIDATE_TICKER_NEWS, + INTENT_ACTION_REFRESH_GUEST_LIST, + INTENT_ACTION_CLEAR_TILE_INSPECTOR_CLIPBOARD, + INTENT_ACTION_REFRESH_STAFF_LIST, + INTENT_ACTION_INVALIDATE_VEHICLE_WINDOW, + INTENT_ACTION_RIDE_PAINT_RESET_VEHICLE, + INTENT_ACTION_UPDATE_CLIMATE, + INTENT_ACTION_UPDATE_GUEST_COUNT, + INTENT_ACTION_UPDATE_PARK_RATING, + INTENT_ACTION_UPDATE_DATE, + INTENT_ACTION_UPDATE_CASH, + INTENT_ACTION_UPDATE_BANNER, + INTENT_ACTION_UPDATE_RESEARCH, + INTENT_ACTION_UPDATE_VEHICLE_SOUNDS, + INTENT_ACTION_SET_MAP_TOOLTIP, + INTENT_ACTION_NEW_SCENERY, + INTENT_ACTION_TILE_MODIFY, + INTENT_ACTION_PROGRESS_OPEN, + INTENT_ACTION_PROGRESS_SET, + INTENT_ACTION_PROGRESS_CLOSE, + INTENT_ACTION_REMOVE_PROVISIONAL_ELEMENTS, + INTENT_ACTION_RESTORE_PROVISIONAL_ELEMENTS, + INTENT_ACTION_REMOVE_PROVISIONAL_FOOTPATH, + INTENT_ACTION_REMOVE_PROVISIONAL_TRACK_PIECE, -enum -{ - INTENT_EXTRA_GUEST_LIST_FILTER, - INTENT_EXTRA_RIDE_ID, - INTENT_EXTRA_PATH, - INTENT_EXTRA_PEEP, - INTENT_EXTRA_LOADSAVE_TYPE, - INTENT_EXTRA_CALLBACK, - INTENT_EXTRA_TRACK_DESIGN, - INTENT_EXTRA_RIDE_TYPE, - INTENT_EXTRA_RIDE_ENTRY_INDEX, - INTENT_EXTRA_TILE_ELEMENT, - INTENT_EXTRA_VEHICLE, - INTENT_EXTRA_MESSAGE, - INTENT_EXTRA_LIST, - INTENT_EXTRA_LIST_COUNT, - INTENT_EXTRA_PAGE, - INTENT_EXTRA_BANNER_INDEX, - INTENT_EXTRA_FORMATTER, - INTENT_EXTRA_SCENERY_GROUP_ENTRY_INDEX, - INTENT_EXTRA_PROGRESS_OFFSET, - INTENT_EXTRA_PROGRESS_TOTAL, - INTENT_EXTRA_STRING_ID, -}; + INTENT_ACTION_NULL = 255, + }; + + // The maximum amount of data the Intent can hold, 8 should be sufficient, raise this if needed. + static constexpr size_t kIntentMaxDataSlots = 8; + + using IntentData = std::variant; + using IntentDataEntry = std::pair; + using IntentDataStorage = sfl::static_vector; + + class Intent + { + WindowClass _Class{ WindowClass::Null }; + WindowDetail _WindowDetail{ WD_NULL }; + IntentAction _Action{ INTENT_ACTION_NULL }; + IntentDataStorage _Data; + + public: + explicit Intent(WindowClass windowClass); + explicit Intent(WindowDetail windowDetail); + explicit Intent(IntentAction windowclass); + + WindowClass GetWindowClass() const; + WindowDetail GetWindowDetail() const; + IntentAction GetAction() const; + + void* GetPointerExtra(uint32_t key) const; + std::string GetStringExtra(uint32_t key) const; + uint32_t GetUIntExtra(uint32_t key) const; + int32_t GetSIntExtra(uint32_t key) const; + CloseCallback GetCloseCallbackExtra(uint32_t key) const; + + Intent* PutExtra(uint32_t key, uint32_t value); + Intent* PutExtra(uint32_t key, void* value); + Intent* PutExtra(uint32_t key, int32_t value); + Intent* PutExtra(uint32_t key, std::string value); + Intent* PutExtra(uint32_t key, CloseCallback value); + + template + Intent* PutExtra(uint32_t key, const TIdentifier& value) + { + const auto val = value.ToUnderlying(); + return PutExtra(key, static_cast(val)); + } + }; + + enum + { + INTENT_EXTRA_GUEST_LIST_FILTER, + INTENT_EXTRA_RIDE_ID, + INTENT_EXTRA_PATH, + INTENT_EXTRA_PEEP, + INTENT_EXTRA_LOADSAVE_TYPE, + INTENT_EXTRA_CALLBACK, + INTENT_EXTRA_TRACK_DESIGN, + INTENT_EXTRA_RIDE_TYPE, + INTENT_EXTRA_RIDE_ENTRY_INDEX, + INTENT_EXTRA_TILE_ELEMENT, + INTENT_EXTRA_VEHICLE, + INTENT_EXTRA_MESSAGE, + INTENT_EXTRA_LIST, + INTENT_EXTRA_LIST_COUNT, + INTENT_EXTRA_PAGE, + INTENT_EXTRA_BANNER_INDEX, + INTENT_EXTRA_FORMATTER, + INTENT_EXTRA_SCENERY_GROUP_ENTRY_INDEX, + INTENT_EXTRA_PROGRESS_OFFSET, + INTENT_EXTRA_PROGRESS_TOTAL, + INTENT_EXTRA_STRING_ID, + }; +} // namespace OpenRCT2