1
0
mirror of https://github.com/OpenRCT2/OpenRCT2 synced 2025-12-23 15:52:55 +01:00
Files
OpenRCT2/src/openrct2/interface/Window.cpp
Duncan d2aca03ff6 Fix #15271. Use formatter to pass description args to text input (#15272)
* Fix #15271. Use formatter to pass description args to text input

Originally passed the variables via global vars which were not updated to 32bit during recent refactors. This removes the global and makes the interface cleaner and corrects the type

* Fix size of arguments
2021-08-24 19:12:05 +01:00

2245 lines
64 KiB
C++

/*****************************************************************************
* Copyright (c) 2014-2020 OpenRCT2 developers
*
* For a complete list of all authors, please refer to contributors.md
* Interested in contributing? Visit https://github.com/OpenRCT2/OpenRCT2
*
* OpenRCT2 is licensed under the GNU General Public License version 3.
*****************************************************************************/
#include "Window.h"
#include "../Context.h"
#include "../Editor.h"
#include "../Game.h"
#include "../Input.h"
#include "../OpenRCT2.h"
#include "../audio/audio.h"
#include "../config/Config.h"
#include "../core/Guard.hpp"
#include "../drawing/Drawing.h"
#include "../interface/Cursors.h"
#include "../localisation/Localisation.h"
#include "../localisation/StringIds.h"
#include "../platform/platform.h"
#include "../ride/RideAudio.h"
#include "../scenario/Scenario.h"
#include "../sprites.h"
#include "../ui/UiContext.h"
#include "../ui/WindowManager.h"
#include "../world/Map.h"
#include "Viewport.h"
#include "Widget.h"
#include "Window_internal.h"
#include <algorithm>
#include <cmath>
#include <functional>
#include <iterator>
#include <list>
std::list<std::shared_ptr<rct_window>> g_window_list;
rct_window* gWindowAudioExclusive;
widget_identifier gCurrentTextBox = { { 255, 0 }, 0 };
char gTextBoxInput[TEXT_INPUT_SIZE] = { 0 };
int32_t gMaxTextBoxInputLength = 0;
int32_t gTextBoxFrameNo = 0;
bool gUsingWidgetTextBox = false;
TextInputSession* gTextInput;
uint16_t gWindowUpdateTicks;
uint16_t gWindowMapFlashingFlags;
colour_t gCurrentWindowColours[4];
// 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 const float window_scroll_locations[][2] = {
{ 0.5f, 0.5f },
{ 0.75f, 0.5f },
{ 0.25f, 0.5f },
{ 0.5f, 0.75f },
{ 0.5f, 0.25f },
{ 0.75f, 0.75f },
{ 0.75f, 0.25f },
{ 0.25f, 0.75f },
{ 0.25f, 0.25f },
{ 0.125f, 0.5f },
{ 0.875f, 0.5f },
{ 0.5f, 0.125f },
{ 0.5f, 0.875f },
{ 0.875f, 0.125f },
{ 0.875f, 0.875f },
{ 0.125f, 0.875f },
{ 0.125f, 0.125f },
};
// clang-format on
namespace WindowCloseFlags
{
static constexpr uint32_t None = 0;
static constexpr uint32_t IterateReverse = (1 << 0);
static constexpr uint32_t CloseSingle = (1 << 1);
} // namespace WindowCloseFlags
static void window_draw_core(rct_drawpixelinfo* dpi, rct_window* w, int32_t left, int32_t top, int32_t right, int32_t bottom);
static void window_draw_single(rct_drawpixelinfo* dpi, rct_window* w, int32_t left, int32_t top, int32_t right, int32_t bottom);
std::list<std::shared_ptr<rct_window>>::iterator window_get_iterator(const rct_window* w)
{
return std::find_if(g_window_list.begin(), g_window_list.end(), [w](const std::shared_ptr<rct_window>& w2) -> bool {
return w == w2.get();
});
}
void window_visit_each(std::function<void(rct_window*)> func)
{
auto windowList = g_window_list;
for (auto& w : windowList)
{
func(w.get());
}
}
/**
*
* rct2: 0x006ED7B0
*/
void window_dispatch_update_all()
{
// gTooltipNotShownTicks++;
window_visit_each([&](rct_window* w) { window_event_update_call(w); });
}
void window_update_all_viewports()
{
window_visit_each([&](rct_window* w) {
if (w->viewport != nullptr && window_is_visible(w))
{
viewport_update_position(w);
}
});
}
/**
*
* rct2: 0x006E77A1
*/
void window_update_all()
{
// window_update_all_viewports();
// 1000 tick update
gWindowUpdateTicks += gCurrentDeltaTime;
if (gWindowUpdateTicks >= 1000)
{
gWindowUpdateTicks = 0;
window_visit_each([](rct_window* w) { window_event_periodic_update_call(w); });
}
// Border flash invalidation
window_visit_each([](rct_window* 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 = OpenRCT2::GetContext()->GetUiContext()->GetWindowManager();
windowManager->UpdateMouseWheel();
}
static void window_close_surplus(int32_t cap, int8_t avoid_classification)
{
// find the amount of windows that are currently open
auto count = static_cast<int32_t>(g_window_list.size());
// difference between amount open and cap = amount to close
auto diff = count - WINDOW_LIMIT_RESERVED - 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
rct_window* foundW{};
for (auto& w : g_window_list)
{
if (!(w->flags & (WF_STICK_TO_BACK | WF_STICK_TO_FRONT | WF_NO_AUTO_CLOSE)))
{
foundW = w.get();
break;
}
}
// skip window if window matches specified rct_windowclass (as user may be modifying via options)
if (avoid_classification != -1 && foundW != nullptr && foundW->classification == avoid_classification)
{
continue;
}
window_close(foundW);
}
}
/*
* Changes the maximum amount of windows allowed
*/
void window_set_window_limit(int32_t value)
{
int32_t prev = gConfigGeneral.window_limit;
int32_t val = std::clamp(value, WINDOW_LIMIT_MIN, WINDOW_LIMIT_MAX);
gConfigGeneral.window_limit = val;
config_save_default();
// Checks if value decreases and then closes surplus
// windows if one sets a limit lower than the number of windows open
if (val < prev)
{
window_close_surplus(val, WC_OPTIONS);
}
}
/**
* Closes the specified window.
* rct2: 0x006ECD4C
*
* @param window The window to close (esi).
*/
void window_close(rct_window* w)
{
auto itWindow = window_get_iterator(w);
if (itWindow == g_window_list.end())
return;
// Explicit copy of the shared ptr to keep the memory valid.
std::shared_ptr<rct_window> window = *itWindow;
window_event_close_call(window.get());
// Remove viewport
window->RemoveViewport();
// Invalidate the window (area)
window->Invalidate();
// The window list may have been modified in the close event
itWindow = window_get_iterator(w);
if (itWindow != g_window_list.end())
g_window_list.erase(itWindow);
}
template<typename _TPred> static void window_close_by_condition(_TPred pred, uint32_t flags = WindowCloseFlags::None)
{
bool listUpdated;
do
{
listUpdated = false;
auto closeSingle = [&](std::shared_ptr<rct_window> window) -> bool {
if (!pred(window.get()))
{
return false;
}
// Keep track of current amount, if a new window is created upon closing
// we need to break this current iteration and restart.
size_t previousCount = g_window_list.size();
window_close(window.get());
if ((flags & WindowCloseFlags::CloseSingle) != 0)
{
// Only close a single one.
return true;
}
if (previousCount >= g_window_list.size())
{
// A new window was created during the close event.
return true;
}
// Keep closing windows.
return false;
};
// The closest to something like for_each_if is using find_if in order to avoid duplicate code
// to change the loop direction.
auto windowList = g_window_list;
if ((flags & WindowCloseFlags::IterateReverse) != 0)
listUpdated = std::find_if(windowList.rbegin(), windowList.rend(), closeSingle) != windowList.rend();
else
listUpdated = std::find_if(windowList.begin(), windowList.end(), closeSingle) != windowList.end();
// If requested to close only a single window and a new window was created during close
// we ignore it.
if ((flags & WindowCloseFlags::CloseSingle) != 0)
break;
} while (listUpdated);
}
/**
* Closes all windows with the specified window class.
* rct2: 0x006ECCF4
* @param cls (cl) with bit 15 set
*/
void window_close_by_class(rct_windowclass cls)
{
window_close_by_condition([&](rct_window* 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 window_close_by_number(rct_windowclass cls, rct_windownumber number)
{
window_close_by_condition([cls, number](rct_window* w) -> bool { return w->classification == cls && w->number == number; });
}
/**
* Finds the first window with the specified window class.
* rct2: 0x006EA8A0
* @param cls (cl) with bit 15 set
* @returns the window or NULL if no window was found.
*/
rct_window* window_find_by_class(rct_windowclass cls)
{
for (auto& w : g_window_list)
{
if (w->classification == cls)
{
return w.get();
}
}
return nullptr;
}
/**
* Finds the first window with the specified window class and number.
* rct2: 0x006EA8A0
* @param cls (cl) without bit 15 set
* @param number (dx)
* @returns the window or NULL if no window was found.
*/
rct_window* window_find_by_number(rct_windowclass cls, rct_windownumber number)
{
for (auto& w : g_window_list)
{
if (w->classification == cls && w->number == number)
{
return w.get();
}
}
return nullptr;
}
/**
* Closes the top-most window
*
* rct2: 0x006E403C
*/
void window_close_top()
{
window_close_by_class(WC_DROPDOWN);
if (gScreenFlags & SCREEN_FLAGS_SCENARIO_EDITOR)
{
if (gEditorStep != EditorStep::LandscapeEditor)
return;
}
auto pred = [](rct_window* w) -> bool { return !(w->flags & (WF_STICK_TO_BACK | WF_STICK_TO_FRONT)); };
window_close_by_condition(pred, WindowCloseFlags::CloseSingle | WindowCloseFlags::IterateReverse);
}
/**
* Closes all open windows
*
* rct2: 0x006EE927
*/
void window_close_all()
{
window_close_by_class(WC_DROPDOWN);
window_close_by_condition([](rct_window* w) -> bool { return !(w->flags & (WF_STICK_TO_BACK | WF_STICK_TO_FRONT)); });
}
void window_close_all_except_class(rct_windowclass cls)
{
window_close_by_class(WC_DROPDOWN);
window_close_by_condition([cls](rct_window* 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 window_close_all_except_flags(uint16_t flags)
{
window_close_by_condition([flags](rct_window* w) -> bool { return !(w->flags & flags); });
}
/**
*
* rct2: 0x006EA845
*/
rct_window* window_find_from_point(const ScreenCoordsXY& screenCoords)
{
for (auto it = g_window_list.rbegin(); it != g_window_list.rend(); it++)
{
auto& w = *it;
if (screenCoords.x < w->windowPos.x || screenCoords.x >= w->windowPos.x + w->width || screenCoords.y < w->windowPos.y
|| screenCoords.y >= w->windowPos.y + w->height)
continue;
if (w->flags & WF_NO_BACKGROUND)
{
auto widgetIndex = window_find_widget_from_point(w.get(), screenCoords);
if (widgetIndex == -1)
continue;
}
return w.get();
}
return nullptr;
}
/**
*
* rct2: 0x006EA594
* x (ax)
* y (bx)
* returns widget_index (edx)
* EDI NEEDS TO BE SET TO w->widgets[widget_index] AFTER
*/
rct_widgetindex window_find_widget_from_point(rct_window* w, const ScreenCoordsXY& screenCoords)
{
// Invalidate the window
window_event_invalidate_call(w);
// Find the widget at point x, y
rct_widgetindex widget_index = -1;
for (int32_t i = 0;; i++)
{
rct_widget* widget = &w->widgets[i];
if (widget->type == WindowWidgetType::Last)
{
break;
}
else if (widget->type != WindowWidgetType::Empty && widget->IsVisible())
{
if (screenCoords.x >= w->windowPos.x + widget->left && screenCoords.x <= w->windowPos.x + widget->right
&& screenCoords.y >= w->windowPos.y + widget->top && screenCoords.y <= w->windowPos.y + widget->bottom)
{
widget_index = i;
}
}
}
// Return next widget if a dropdown
if (widget_index != -1)
if (w->widgets[widget_index].type == WindowWidgetType::DropdownMenu)
widget_index++;
// Return the widget index
return widget_index;
}
/**
* Invalidates the specified window.
* rct2: 0x006EB13A
*
* @param window The window to invalidate (esi).
*/
template<typename _TPred> static void window_invalidate_by_condition(_TPred pred)
{
window_visit_each([pred](rct_window* w) {
if (pred(w))
{
w->Invalidate();
}
});
}
/**
* Invalidates all windows with the specified window class.
* rct2: 0x006EC3AC
* @param cls (al) with bit 14 set
*/
void window_invalidate_by_class(rct_windowclass cls)
{
window_invalidate_by_condition([cls](rct_window* w) -> bool { return w->classification == cls; });
}
/**
* Invalidates all windows with the specified window class and number.
* rct2: 0x006EC3AC
*/
void window_invalidate_by_number(rct_windowclass cls, rct_windownumber number)
{
window_invalidate_by_condition(
[cls, number](rct_window* w) -> bool { return w->classification == cls && w->number == number; });
}
/**
* Invalidates all windows.
*/
void window_invalidate_all()
{
window_visit_each([](rct_window* w) { w->Invalidate(); });
}
/**
* Invalidates the specified widget of a window.
* rct2: 0x006EC402
*/
void widget_invalidate(rct_window* w, rct_widgetindex widgetIndex)
{
rct_widget* widget;
assert(w != nullptr);
#ifdef DEBUG
for (int32_t i = 0; i <= widgetIndex; i++)
{
assert(w->widgets[i].type != WindowWidgetType::Last);
}
#endif
widget = &w->widgets[widgetIndex];
if (widget->left == -2)
return;
gfx_set_dirty_blocks({ { w->windowPos + ScreenCoordsXY{ widget->left, widget->top } },
{ w->windowPos + ScreenCoordsXY{ widget->right + 1, widget->bottom + 1 } } });
}
template<typename _TPred> static void widget_invalidate_by_condition(_TPred pred)
{
window_visit_each([pred](rct_window* w) {
if (pred(w))
{
w->Invalidate();
}
});
}
/**
* Invalidates the specified widget of all windows that match the specified window class.
*/
void widget_invalidate_by_class(rct_windowclass cls, rct_widgetindex widgetIndex)
{
window_visit_each([cls, widgetIndex](rct_window* w) {
if (w->classification == cls)
{
widget_invalidate(w, widgetIndex);
}
});
}
/**
* Invalidates the specified widget of all windows that match the specified window class and number.
* rct2: 0x006EC3AC
*/
void widget_invalidate_by_number(rct_windowclass cls, rct_windownumber number, rct_widgetindex widgetIndex)
{
window_visit_each([cls, number, widgetIndex](rct_window* w) {
if (w->classification == cls && w->number == number)
{
widget_invalidate(w, widgetIndex);
}
});
}
/**
*
* rct2: 0x006EAE4E
*
* @param w The window (esi).
*/
void window_update_scroll_widgets(rct_window* w)
{
int32_t scrollIndex, width, height, scrollPositionChanged;
rct_widgetindex widgetIndex;
rct_scroll* scroll;
rct_widget* widget;
widgetIndex = 0;
scrollIndex = 0;
assert(w != nullptr);
for (widget = w->widgets; widget->type != WindowWidgetType::Last; widget++, widgetIndex++)
{
if (widget->type != WindowWidgetType::Scroll)
continue;
scroll = &w->scrolls[scrollIndex];
width = 0;
height = 0;
window_get_scroll_size(w, scrollIndex, &width, &height);
if (height == 0)
{
scroll->v_top = 0;
}
else if (width == 0)
{
scroll->h_left = 0;
}
width++;
height++;
scrollPositionChanged = 0;
if ((widget->content & SCROLL_HORIZONTAL) && width != scroll->h_right)
{
scrollPositionChanged = 1;
scroll->h_right = width;
}
if ((widget->content & SCROLL_VERTICAL) && height != scroll->v_bottom)
{
scrollPositionChanged = 1;
scroll->v_bottom = height;
}
if (scrollPositionChanged)
{
WidgetScrollUpdateThumbs(w, widgetIndex);
w->Invalidate();
}
scrollIndex++;
}
}
int32_t window_get_scroll_data_index(rct_window* w, rct_widgetindex widget_index)
{
int32_t i, result;
result = 0;
assert(w != nullptr);
for (i = 0; i < widget_index; i++)
{
if (w->widgets[i].type == WindowWidgetType::Scroll)
result++;
}
return result;
}
/**
*
* rct2: 0x006ECDA4
*/
rct_window* window_bring_to_front(rct_window* w)
{
if (!(w->flags & (WF_STICK_TO_BACK | WF_STICK_TO_FRONT)))
{
auto itSourcePos = window_get_iterator(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))
{
itDestPos = it.base();
break;
}
}
g_window_list.splice(itDestPos, g_window_list, itSourcePos);
w->Invalidate();
if (w->windowPos.x + w->width < 20)
{
int32_t i = 20 - w->windowPos.x;
w->windowPos.x += i;
if (w->viewport != nullptr)
w->viewport->pos.x += i;
w->Invalidate();
}
}
}
return w;
}
rct_window* window_bring_to_front_by_class_with_flags(rct_windowclass cls, uint16_t flags)
{
rct_window* w;
w = window_find_by_class(cls);
if (w != nullptr)
{
w->flags |= flags;
w->Invalidate();
w = window_bring_to_front(w);
}
return w;
}
rct_window* window_bring_to_front_by_class(rct_windowclass cls)
{
return window_bring_to_front_by_class_with_flags(cls, WF_WHITE_BORDER_MASK);
}
/**
*
* rct2: 0x006ED78A
* cls (cl)
* number (dx)
*/
rct_window* window_bring_to_front_by_number(rct_windowclass cls, rct_windownumber number)
{
rct_window* w;
w = window_find_by_number(cls, number);
if (w != nullptr)
{
w->flags |= WF_WHITE_BORDER_MASK;
w->Invalidate();
w = window_bring_to_front(w);
}
return w;
}
/**
*
* rct2: 0x006EE65A
*/
void window_push_others_right(rct_window* window)
{
window_visit_each([window](rct_window* 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 >= context_get_width())
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 window_push_others_below(rct_window* w1)
{
// Enumerate through all other windows
window_visit_each([w1](rct_window* 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 >= context_get_height())
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
*/
rct_window* window_get_main()
{
for (auto& w : g_window_list)
{
if (w->classification == WC_MAIN_WINDOW)
{
return w.get();
}
}
return nullptr;
}
/**
*
* rct2: 0x006E7C9C
* @param w (esi)
* @param x (eax)
* @param y (ecx)
* @param z (edx)
*/
void window_scroll_to_location(rct_window* w, const CoordsXYZ& coords)
{
assert(w != nullptr);
window_unfollow_sprite(w);
if (w->viewport)
{
int16_t height = tile_element_height(coords);
if (coords.z < height - 16)
{
if (!(w->viewport->flags & 1 << 0))
{
w->viewport->flags |= 1 << 0;
w->Invalidate();
}
}
else
{
if (w->viewport->flags & 1 << 0)
{
w->viewport->flags &= ~(1 << 0);
w->Invalidate();
}
}
auto screenCoords = translate_3d_to_2d_with_z(get_current_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<int32_t>(w->viewport->width * window_scroll_locations[i][0]);
auto y2 = w->viewport->pos.y + static_cast<int32_t>(w->viewport->height * window_scroll_locations[i][1]);
auto it = window_get_iterator(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<int32_t>(std::size(window_scroll_locations)))
{
i = 0;
found = true;
}
}
}
// rct2: 0x006E7C76
if (w->viewport_target_sprite == SPRITE_INDEX_NULL)
{
if (!(w->flags & WF_NO_SCROLLING))
{
w->savedViewPos = screenCoords
- ScreenCoordsXY{ static_cast<int32_t>(w->viewport->view_width * window_scroll_locations[i][0]),
static_cast<int32_t>(w->viewport->view_height * window_scroll_locations[i][1]) };
w->flags |= WF_SCROLLING_TO_LOCATION;
}
}
}
}
/**
*
* rct2: 0x00688956
*/
static void call_event_viewport_rotate_on_all_windows()
{
window_visit_each([](rct_window* w) { window_event_viewport_rotate_call(w); });
}
/**
*
* rct2: 0x0068881A
* direction can be used to alter the camera rotation:
* 1: clockwise
* -1: anti-clockwise
*/
void window_rotate_camera(rct_window* w, int32_t direction)
{
rct_viewport* 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
rct_viewport* other;
auto mapXYCoords = screen_get_map_xy(windowPos, &other);
CoordsXYZ coords{};
// other != viewport probably triggers on viewports in ride or guest window?
// naoXYCoords is nullopt if middle of viewport is obstructed by another window?
if (!mapXYCoords || other != viewport)
{
auto viewPos = ScreenCoordsXY{ (viewport->view_width >> 1), (viewport->view_height >> 1) } + viewport->viewPos;
coords = viewport_adjust_for_map_height(viewPos);
}
else
{
coords.x = mapXYCoords->x;
coords.y = mapXYCoords->y;
coords.z = tile_element_height(coords);
}
gCurrentRotation = (get_current_rotation() + direction) & 3;
auto centreLoc = centre_2d_coordinates(coords, viewport);
if (centreLoc)
{
w->savedViewPos = *centreLoc;
viewport->viewPos = *centreLoc;
}
w->Invalidate();
call_event_viewport_rotate_on_all_windows();
reset_all_sprite_quadrant_placements();
}
void window_viewport_get_map_coords_by_cursor(
rct_window* w, int32_t* map_x, int32_t* map_y, int32_t* offset_x, int32_t* offset_y)
{
// Get mouse position to offset against.
auto mouseCoords = context_get_cursor_position_scaled();
// Compute map coordinate by mouse position.
auto viewportPos = w->viewport->ScreenToViewportCoord(mouseCoords);
auto coordsXYZ = viewport_adjust_for_map_height(viewportPos);
auto mapCoords = viewport_coord_to_map_coord(viewportPos, coordsXYZ.z);
*map_x = mapCoords.x;
*map_y = mapCoords.y;
// Get viewport coordinates centring around the tile.
int32_t z = tile_element_height(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->width / 2) - mouseCoords.x) * w->viewport->zoom;
int32_t rebased_y = ((w->height / 2) - mouseCoords.y) * w->viewport->zoom;
// Compute cursor offset relative to tile.
*offset_x = (w->savedViewPos.x - (centreLoc->x + rebased_x)) * w->viewport->zoom;
*offset_y = (w->savedViewPos.y - (centreLoc->y + rebased_y)) * w->viewport->zoom;
}
void window_viewport_centre_tile_around_cursor(rct_window* w, int32_t map_x, int32_t map_y, int32_t offset_x, int32_t offset_y)
{
// Get viewport coordinates centring around the tile.
int32_t z = tile_element_height({ map_x, map_y });
auto centreLoc = centre_2d_coordinates({ map_x, map_y, z }, w->viewport);
if (!centreLoc)
{
log_error("Invalid location.");
return;
}
// Get mouse position to offset against.
auto mouseCoords = context_get_cursor_position_scaled();
// Rebase mouse position onto centre of window, and compensate for zoom level.
int32_t rebased_x = ((w->width >> 1) - mouseCoords.x) * w->viewport->zoom;
int32_t rebased_y = ((w->height >> 1) - mouseCoords.y) * w->viewport->zoom;
// Apply offset to the viewport.
w->savedViewPos = { centreLoc->x + rebased_x + (offset_x / w->viewport->zoom),
centreLoc->y + rebased_y + (offset_y / w->viewport->zoom) };
}
/**
* For all windows with viewports, ensure they do not have a zoom level less than the minimum.
*/
void window_check_all_valid_zoom()
{
window_visit_each([](rct_window* w) {
if (w->viewport != nullptr && w->viewport->zoom < ZoomLevel::min())
{
window_zoom_set(w, ZoomLevel::min(), false);
}
});
}
void window_zoom_set(rct_window* w, ZoomLevel zoomLevel, bool atCursor)
{
rct_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 (gConfigGeneral.zoom_to_cursor && atCursor)
{
window_viewport_get_map_coords_by_cursor(w, &saved_map_x, &saved_map_y, &offset_x, &offset_y);
}
// Zoom in
while (v->zoom > zoomLevel)
{
v->zoom--;
w->savedViewPos.x += v->view_width / 4;
w->savedViewPos.y += v->view_height / 4;
v->view_width /= 2;
v->view_height /= 2;
}
// Zoom out
while (v->zoom < zoomLevel)
{
v->zoom++;
w->savedViewPos.x -= v->view_width / 2;
w->savedViewPos.y -= v->view_height / 2;
v->view_width *= 2;
v->view_height *= 2;
}
// Zooming to cursor? Centre around the tile we were hovering over just now.
if (gConfigGeneral.zoom_to_cursor && atCursor)
{
window_viewport_centre_tile_around_cursor(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.
window_bring_to_front(w);
w->Invalidate();
}
/**
*
* rct2: 0x006887A6
*/
void window_zoom_in(rct_window* w, bool atCursor)
{
window_zoom_set(w, w->viewport->zoom - 1, atCursor);
}
/**
*
* rct2: 0x006887E0
*/
void window_zoom_out(rct_window* w, bool atCursor)
{
window_zoom_set(w, w->viewport->zoom + 1, atCursor);
}
void main_window_zoom(bool zoomIn, bool atCursor)
{
if (gScreenFlags & SCREEN_FLAGS_TITLE_DEMO)
return;
if (!(gScreenFlags & SCREEN_FLAGS_SCENARIO_EDITOR) || gEditorStep == EditorStep::LandscapeEditor)
{
if (!(gScreenFlags & SCREEN_FLAGS_TRACK_MANAGER))
{
rct_window* mainWindow = window_get_main();
if (mainWindow != nullptr)
window_zoom_set(mainWindow, mainWindow->viewport->zoom + (zoomIn ? -1 : 1), atCursor);
}
}
}
/**
* Splits a drawing of a window into regions that can be seen and are not hidden
* by other opaque overlapping windows.
*/
void window_draw(rct_drawpixelinfo* dpi, rct_window* w, int32_t left, int32_t top, int32_t right, int32_t bottom)
{
if (!window_is_visible(w))
return;
// Divide the draws up for only the visible regions of the window recursively
auto itPos = window_get_iterator(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
window_draw_core(dpi, w, left, top, topwindow->windowPos.x, bottom);
window_draw_core(dpi, w, topwindow->windowPos.x, top, right, bottom);
}
else if (topwindow->windowPos.x + topwindow->width < right)
{
// Split draw at topwindow.right
window_draw_core(dpi, w, left, top, topwindow->windowPos.x + topwindow->width, bottom);
window_draw_core(dpi, w, topwindow->windowPos.x + topwindow->width, top, right, bottom);
}
else if (topwindow->windowPos.y > top)
{
// Split draw at topwindow.top
window_draw_core(dpi, w, left, top, right, topwindow->windowPos.y);
window_draw_core(dpi, w, left, topwindow->windowPos.y, right, bottom);
}
else if (topwindow->windowPos.y + topwindow->height < bottom)
{
// Split draw at topwindow.bottom
window_draw_core(dpi, w, left, top, right, topwindow->windowPos.y + topwindow->height);
window_draw_core(dpi, w, left, topwindow->windowPos.y + topwindow->height, right, bottom);
}
// Drawing for this region should be done now, exit
return;
}
// No windows overlap
window_draw_core(dpi, w, left, top, right, bottom);
}
/**
* Draws the given window and any other overlapping transparent windows.
*/
static void window_draw_core(rct_drawpixelinfo* dpi, rct_window* w, int32_t left, int32_t top, int32_t right, int32_t bottom)
{
// Clamp region
left = std::max<int32_t>(left, w->windowPos.x);
top = std::max<int32_t>(top, w->windowPos.y);
right = std::min<int32_t>(right, w->windowPos.x + w->width);
bottom = std::min<int32_t>(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 = window_get_iterator(w); it != g_window_list.end(); it++)
{
auto v = (*it).get();
if ((w == v || (v->flags & WF_TRANSPARENT)) && window_is_visible(v))
{
window_draw_single(dpi, v, left, top, right, bottom);
}
}
}
static void window_draw_single(rct_drawpixelinfo* dpi, rct_window* w, int32_t left, int32_t top, int32_t right, int32_t bottom)
{
// Copy dpi so we can crop it
rct_drawpixelinfo copy = *dpi;
dpi = &copy;
// Clamp left to 0
int32_t overflow = left - dpi->x;
if (overflow > 0)
{
dpi->x += overflow;
dpi->width -= overflow;
if (dpi->width <= 0)
return;
dpi->pitch += overflow;
dpi->bits += overflow;
}
// Clamp width to right
overflow = dpi->x + dpi->width - right;
if (overflow > 0)
{
dpi->width -= overflow;
if (dpi->width <= 0)
return;
dpi->pitch += overflow;
}
// Clamp top to 0
overflow = top - dpi->y;
if (overflow > 0)
{
dpi->y += overflow;
dpi->height -= overflow;
if (dpi->height <= 0)
return;
dpi->bits += (dpi->width + dpi->pitch) * overflow;
}
// Clamp height to bottom
overflow = dpi->y + dpi->height - bottom;
if (overflow > 0)
{
dpi->height -= overflow;
if (dpi->height <= 0)
return;
}
// Invalidate modifies the window colours so first get the correct
// colour before setting the global variables for the string painting
window_event_invalidate_call(w);
// Text colouring
gCurrentWindowColours[0] = NOT_TRANSLUCENT(w->colours[0]);
gCurrentWindowColours[1] = NOT_TRANSLUCENT(w->colours[1]);
gCurrentWindowColours[2] = NOT_TRANSLUCENT(w->colours[2]);
gCurrentWindowColours[3] = NOT_TRANSLUCENT(w->colours[3]);
window_event_paint_call(w, dpi);
}
/**
*
* rct2: 0x00685BE1
*
* @param dpi (edi)
* @param w (esi)
*/
void window_draw_viewport(rct_drawpixelinfo* dpi, rct_window* w)
{
viewport_render(dpi, w->viewport, dpi->x, dpi->y, dpi->x + dpi->width, dpi->y + dpi->height);
}
void window_set_position(rct_window* w, const ScreenCoordsXY& screenCoords)
{
window_move_position(w, screenCoords - w->windowPos);
}
void window_move_position(rct_window* w, const ScreenCoordsXY& deltaCoords)
{
if (deltaCoords.x == 0 && deltaCoords.y == 0)
return;
// Invalidate old region
w->Invalidate();
// Translate window and viewport
w->windowPos += deltaCoords;
if (w->viewport != nullptr)
{
w->viewport->pos += deltaCoords;
}
// Invalidate new region
w->Invalidate();
}
void window_resize(rct_window* w, int32_t dw, int32_t dh)
{
if (dw == 0 && dh == 0)
return;
// Invalidate old region
w->Invalidate();
// Clamp new size to minimum and maximum
w->width = std::clamp<int32_t>(w->width + dw, w->min_width, w->max_width);
w->height = std::clamp<int32_t>(w->height + dh, w->min_height, w->max_height);
window_event_resize_call(w);
window_event_invalidate_call(w);
// Update scroll widgets
for (int32_t i = 0; i < 3; i++)
{
w->scrolls[i].h_right = WINDOW_SCROLL_UNDEFINED;
w->scrolls[i].v_bottom = WINDOW_SCROLL_UNDEFINED;
}
window_update_scroll_widgets(w);
// Invalidate new region
w->Invalidate();
}
void window_set_resize(rct_window* w, int32_t minWidth, int32_t minHeight, int32_t maxWidth, int32_t maxHeight)
{
w->min_width = minWidth;
w->min_height = minHeight;
w->max_width = maxWidth;
w->max_height = maxHeight;
// Clamp width and height to minimum and maximum
int32_t width = std::clamp<int32_t>(w->width, std::min(minWidth, maxWidth), std::max(minWidth, maxWidth));
int32_t height = std::clamp<int32_t>(w->height, std::min(minHeight, maxHeight), std::max(minHeight, maxHeight));
// Resize window if size has changed
if (w->width != width || w->height != height)
{
w->Invalidate();
w->width = width;
w->height = height;
w->Invalidate();
}
}
/**
*
* rct2: 0x006EE212
*
* @param tool (al)
* @param widgetIndex (dx)
* @param w (esi)
*/
bool tool_set(rct_window* w, rct_widgetindex widgetIndex, Tool tool)
{
if (input_test_flag(INPUT_FLAG_TOOL_ACTIVE))
{
if (w->classification == gCurrentToolWidget.window_classification && w->number == gCurrentToolWidget.window_number
&& widgetIndex == gCurrentToolWidget.widget_index)
{
tool_cancel();
return true;
}
else
{
tool_cancel();
}
}
input_set_flag(INPUT_FLAG_TOOL_ACTIVE, true);
input_set_flag(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 tool_cancel()
{
if (input_test_flag(INPUT_FLAG_TOOL_ACTIVE))
{
input_set_flag(INPUT_FLAG_TOOL_ACTIVE, false);
map_invalidate_selection_rect();
map_invalidate_map_selection_tiles();
// Reset map selection
gMapSelectFlags = 0;
if (gCurrentToolWidget.widget_index != -1)
{
// Invalidate tool widget
widget_invalidate_by_number(
gCurrentToolWidget.window_classification, gCurrentToolWidget.window_number, gCurrentToolWidget.widget_index);
// Abort tool event
rct_window* w = window_find_by_number(gCurrentToolWidget.window_classification, gCurrentToolWidget.window_number);
if (w != nullptr)
window_event_tool_abort_call(w, gCurrentToolWidget.widget_index);
}
}
}
void window_event_close_call(rct_window* w)
{
if (w->event_handlers == nullptr)
w->OnClose();
else if (w->event_handlers->close != nullptr)
w->event_handlers->close(w);
}
void window_event_mouse_up_call(rct_window* w, rct_widgetindex widgetIndex)
{
if (w->event_handlers == nullptr)
w->OnMouseUp(widgetIndex);
else if (w->event_handlers->mouse_up != nullptr)
w->event_handlers->mouse_up(w, widgetIndex);
}
void window_event_resize_call(rct_window* w)
{
if (w->event_handlers == nullptr)
w->OnResize();
else if (w->event_handlers->resize != nullptr)
w->event_handlers->resize(w);
}
void window_event_mouse_down_call(rct_window* w, rct_widgetindex widgetIndex)
{
if (w->event_handlers == nullptr)
w->OnMouseDown(widgetIndex);
else if (w->event_handlers->mouse_down != nullptr)
w->event_handlers->mouse_down(w, widgetIndex, &w->widgets[widgetIndex]);
}
void window_event_dropdown_call(rct_window* w, rct_widgetindex widgetIndex, int32_t dropdownIndex)
{
if (w->event_handlers == nullptr)
{
if (dropdownIndex != -1)
{
w->OnDropdown(widgetIndex, dropdownIndex);
}
}
else if (w->event_handlers->dropdown != nullptr)
{
w->event_handlers->dropdown(w, widgetIndex, dropdownIndex);
}
}
void window_event_unknown_05_call(rct_window* w)
{
if (w->event_handlers != nullptr)
if (w->event_handlers->unknown_05 != nullptr)
w->event_handlers->unknown_05(w);
}
void window_event_update_call(rct_window* w)
{
if (w->event_handlers == nullptr)
w->OnUpdate();
else if (w->event_handlers->update != nullptr)
w->event_handlers->update(w);
}
void window_event_periodic_update_call(rct_window* w)
{
if (w->event_handlers == nullptr)
w->OnPeriodicUpdate();
else if (w->event_handlers->periodic_update != nullptr)
w->event_handlers->periodic_update(w);
}
void window_event_unknown_08_call(rct_window* w)
{
if (w->event_handlers != nullptr)
if (w->event_handlers->unknown_08 != nullptr)
w->event_handlers->unknown_08(w);
}
void window_event_tool_update_call(rct_window* w, rct_widgetindex widgetIndex, const ScreenCoordsXY& screenCoords)
{
if (w->event_handlers != nullptr)
if (w->event_handlers->tool_update != nullptr)
w->event_handlers->tool_update(w, widgetIndex, screenCoords);
}
void window_event_tool_down_call(rct_window* w, rct_widgetindex widgetIndex, const ScreenCoordsXY& screenCoords)
{
if (w->event_handlers == nullptr)
w->OnToolDown(widgetIndex, screenCoords);
else if (w->event_handlers->tool_down != nullptr)
w->event_handlers->tool_down(w, widgetIndex, screenCoords);
}
void window_event_tool_drag_call(rct_window* w, rct_widgetindex widgetIndex, const ScreenCoordsXY& screenCoords)
{
if (w->event_handlers != nullptr)
if (w->event_handlers->tool_drag != nullptr)
w->event_handlers->tool_drag(w, widgetIndex, screenCoords);
}
void window_event_tool_up_call(rct_window* w, rct_widgetindex widgetIndex, const ScreenCoordsXY& screenCoords)
{
if (w->event_handlers != nullptr)
if (w->event_handlers->tool_up != nullptr)
w->event_handlers->tool_up(w, widgetIndex, screenCoords);
}
void window_event_tool_abort_call(rct_window* w, rct_widgetindex widgetIndex)
{
if (w->event_handlers == nullptr)
w->OnToolAbort(widgetIndex);
else if (w->event_handlers->tool_abort != nullptr)
w->event_handlers->tool_abort(w, widgetIndex);
}
void window_event_unknown_0E_call(rct_window* w)
{
if (w->event_handlers != nullptr)
if (w->event_handlers->unknown_0E != nullptr)
w->event_handlers->unknown_0E(w);
}
void window_get_scroll_size(rct_window* w, int32_t scrollIndex, int32_t* width, int32_t* height)
{
if (w->event_handlers == nullptr)
{
auto size = w->OnScrollGetSize(scrollIndex);
if (width != nullptr)
*width = size.width;
if (height != nullptr)
*height = size.height;
}
else if (w->event_handlers->get_scroll_size != nullptr)
{
w->event_handlers->get_scroll_size(w, scrollIndex, width, height);
}
}
void window_event_scroll_mousedown_call(rct_window* w, int32_t scrollIndex, const ScreenCoordsXY& screenCoords)
{
if (w->event_handlers == nullptr)
w->OnScrollMouseDown(scrollIndex, screenCoords);
else if (w->event_handlers->scroll_mousedown != nullptr)
w->event_handlers->scroll_mousedown(w, scrollIndex, screenCoords);
}
void window_event_scroll_mousedrag_call(rct_window* w, int32_t scrollIndex, const ScreenCoordsXY& screenCoords)
{
if (w->event_handlers == nullptr)
w->OnScrollMouseDrag(scrollIndex, screenCoords);
else if (w->event_handlers->scroll_mousedrag != nullptr)
w->event_handlers->scroll_mousedrag(w, scrollIndex, screenCoords);
}
void window_event_scroll_mouseover_call(rct_window* w, int32_t scrollIndex, const ScreenCoordsXY& screenCoords)
{
if (w->event_handlers == nullptr)
w->OnScrollMouseOver(scrollIndex, screenCoords);
else if (w->event_handlers->scroll_mouseover != nullptr)
w->event_handlers->scroll_mouseover(w, scrollIndex, screenCoords);
}
void window_event_textinput_call(rct_window* w, rct_widgetindex widgetIndex, char* text)
{
if (w->event_handlers == nullptr)
{
if (text != nullptr)
{
w->OnTextInput(widgetIndex, text);
}
}
else if (w->event_handlers->text_input != nullptr)
{
w->event_handlers->text_input(w, widgetIndex, text);
}
}
void window_event_viewport_rotate_call(rct_window* w)
{
if (w->event_handlers == nullptr)
w->OnViewportRotate();
else if (w->event_handlers != nullptr)
if (w->event_handlers->viewport_rotate != nullptr)
w->event_handlers->viewport_rotate(w);
}
void window_event_unknown_15_call(rct_window* w, int32_t scrollIndex, int32_t scrollAreaType)
{
if (w->event_handlers != nullptr)
if (w->event_handlers->unknown_15 != nullptr)
w->event_handlers->unknown_15(w, scrollIndex, scrollAreaType);
}
OpenRCT2String window_event_tooltip_call(rct_window* w, const rct_widgetindex widgetIndex, const rct_string_id fallback)
{
if (w->event_handlers == nullptr)
{
return w->OnTooltip(widgetIndex, fallback);
}
else if (w->event_handlers->tooltip != nullptr)
{
return w->event_handlers->tooltip(w, widgetIndex, fallback);
}
else
{
return { fallback, {} };
}
}
CursorID window_event_cursor_call(rct_window* w, rct_widgetindex widgetIndex, const ScreenCoordsXY& screenCoords)
{
CursorID cursorId = CursorID::Arrow;
if (w->event_handlers != nullptr)
if (w->event_handlers->cursor != nullptr)
w->event_handlers->cursor(w, widgetIndex, screenCoords, &cursorId);
return cursorId;
}
void window_event_moved_call(rct_window* w, const ScreenCoordsXY& screenCoords)
{
if (w->event_handlers != nullptr)
if (w->event_handlers->moved != nullptr)
w->event_handlers->moved(w, screenCoords);
}
void window_event_invalidate_call(rct_window* w)
{
if (w->event_handlers == nullptr)
w->OnPrepareDraw();
else if (w->event_handlers->invalidate != nullptr)
w->event_handlers->invalidate(w);
}
void window_event_paint_call(rct_window* w, rct_drawpixelinfo* dpi)
{
if (w->event_handlers == nullptr)
w->OnDraw(*dpi);
else if (w->event_handlers->paint != nullptr)
w->event_handlers->paint(w, dpi);
}
void window_event_scroll_paint_call(rct_window* w, rct_drawpixelinfo* dpi, int32_t scrollIndex)
{
if (w->event_handlers == nullptr)
w->OnScrollDraw(scrollIndex, *dpi);
else if (w->event_handlers->scroll_paint != nullptr)
w->event_handlers->scroll_paint(w, dpi, scrollIndex);
}
/**
*
* rct2: 0x006ED710
* Called after a window resize to move windows if they
* are going to be out of sight.
*/
void window_relocate_windows(int32_t width, int32_t height)
{
int32_t new_location = 8;
window_visit_each([width, height, &new_location](rct_window* w) {
// Work out if the window requires moving
if (w->windowPos.x + 10 < width)
{
if (w->flags & (WF_STICK_TO_BACK | WF_STICK_TO_FRONT))
{
if (w->windowPos.y - 22 < height)
{
return;
}
}
if (w->windowPos.y + 10 < height)
{
return;
}
}
// Calculate the new locations
auto newWinPos = w->windowPos;
w->windowPos = { new_location, new_location + TOP_TOOLBAR_HEIGHT + 1 };
// Move the next new location so windows are not directly on top
new_location += 8;
// Adjust the viewport if required.
if (w->viewport != nullptr)
{
w->viewport->pos -= newWinPos - w->windowPos;
}
});
}
/**
* rct2: 0x0066B905
*/
void window_resize_gui(int32_t width, int32_t height)
{
window_resize_gui_scenario_editor(width, height);
if (gScreenFlags & SCREEN_FLAGS_EDITOR)
return;
rct_window* titleWind = window_find_by_class(WC_TITLE_MENU);
if (titleWind != nullptr)
{
titleWind->windowPos.x = (width - titleWind->width) / 2;
titleWind->windowPos.y = height - 182;
}
rct_window* exitWind = window_find_by_class(WC_TITLE_EXIT);
if (exitWind != nullptr)
{
exitWind->windowPos.x = width - 40;
exitWind->windowPos.y = height - 64;
}
rct_window* optionsWind = window_find_by_class(WC_TITLE_OPTIONS);
if (optionsWind != nullptr)
{
optionsWind->windowPos.x = width - 80;
}
gfx_invalidate_screen();
}
/**
* rct2: 0x0066F0DD
*/
void window_resize_gui_scenario_editor(int32_t width, int32_t height)
{
rct_window* mainWind = window_get_main();
if (mainWind != nullptr)
{
rct_viewport* viewport = mainWind->viewport;
mainWind->width = width;
mainWind->height = height;
viewport->width = width;
viewport->height = height;
viewport->view_width = width * viewport->zoom;
viewport->view_height = height * viewport->zoom;
if (mainWind->widgets != nullptr && 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;
}
}
rct_window* topWind = window_find_by_class(WC_TOP_TOOLBAR);
if (topWind != nullptr)
{
topWind->width = std::max(640, width);
}
rct_window* bottomWind = window_find_by_class(WC_BOTTOM_TOOLBAR);
if (bottomWind != nullptr)
{
bottomWind->windowPos.y = height - 32;
bottomWind->width = std::max(640, width);
}
}
/* Based on rct2: 0x6987ED and another version from window_park */
void window_align_tabs(rct_window* w, rct_widgetindex start_tab_id, rct_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++)
{
if (!(w->disabled_widgets & (1LL << i)))
{
w->widgets[i].left = x;
w->widgets[i].right = x + tab_width;
x += tab_width + 1;
}
}
}
/**
*
* rct2: 0x006CBCC3
*/
void window_close_construction_windows()
{
window_close_by_class(WC_RIDE_CONSTRUCTION);
window_close_by_class(WC_FOOTPATH);
window_close_by_class(WC_TRACK_DESIGN_LIST);
window_close_by_class(WC_TRACK_DESIGN_PLACE);
}
/**
* Update zoom based volume attenuation for ride music and clear music list.
* rct2: 0x006BC348
*/
void window_update_viewport_ride_music()
{
OpenRCT2::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 <= 0)
OpenRCT2::Audio::gVolumeAdjustZoom = 0;
else if (viewport->zoom == 1)
OpenRCT2::Audio::gVolumeAdjustZoom = 30;
else
OpenRCT2::Audio::gVolumeAdjustZoom = 60;
break;
}
}
static void window_snap_left(rct_window* w, int32_t proximity)
{
auto mainWindow = window_get_main();
auto wBottom = w->windowPos.y + w->height;
auto wLeftProximity = w->windowPos.x - (proximity * 2);
auto wRightProximity = w->windowPos.x + (proximity * 2);
auto rightMost = INT32_MIN;
window_visit_each([&](rct_window* w2) {
if (w2 == w || w2 == mainWindow)
return;
auto right = w2->windowPos.x + w2->width;
if (wBottom < w2->windowPos.y || w->windowPos.y > w2->windowPos.y + w2->height)
return;
if (right < wLeftProximity || right > wRightProximity)
return;
rightMost = std::max(rightMost, right);
});
if (0 >= wLeftProximity && 0 <= wRightProximity)
rightMost = std::max(rightMost, 0);
if (rightMost != INT32_MIN)
w->windowPos.x = rightMost;
}
static void window_snap_top(rct_window* w, int32_t proximity)
{
auto mainWindow = window_get_main();
auto wRight = w->windowPos.x + w->width;
auto wTopProximity = w->windowPos.y - (proximity * 2);
auto wBottomProximity = w->windowPos.y + (proximity * 2);
auto bottomMost = INT32_MIN;
window_visit_each([&](rct_window* w2) {
if (w2 == w || w2 == mainWindow)
return;
auto bottom = w2->windowPos.y + w2->height;
if (wRight < w2->windowPos.x || w->windowPos.x > w2->windowPos.x + w2->width)
return;
if (bottom < wTopProximity || bottom > wBottomProximity)
return;
bottomMost = std::max(bottomMost, bottom);
});
if (0 >= wTopProximity && 0 <= wBottomProximity)
bottomMost = std::max(bottomMost, 0);
if (bottomMost != INT32_MIN)
w->windowPos.y = bottomMost;
}
static void window_snap_right(rct_window* w, int32_t proximity)
{
auto mainWindow = window_get_main();
auto wRight = w->windowPos.x + w->width;
auto wBottom = w->windowPos.y + w->height;
auto wLeftProximity = wRight - (proximity * 2);
auto wRightProximity = wRight + (proximity * 2);
auto leftMost = INT32_MAX;
window_visit_each([&](rct_window* w2) {
if (w2 == w || w2 == mainWindow)
return;
if (wBottom < w2->windowPos.y || w->windowPos.y > w2->windowPos.y + w2->height)
return;
if (w2->windowPos.x < wLeftProximity || w2->windowPos.x > wRightProximity)
return;
leftMost = std::min<int32_t>(leftMost, w2->windowPos.x);
});
auto screenWidth = context_get_width();
if (screenWidth >= wLeftProximity && screenWidth <= wRightProximity)
leftMost = std::min(leftMost, screenWidth);
if (leftMost != INT32_MAX)
w->windowPos.x = leftMost - w->width;
}
static void window_snap_bottom(rct_window* w, int32_t proximity)
{
auto mainWindow = window_get_main();
auto wRight = w->windowPos.x + w->width;
auto wBottom = w->windowPos.y + w->height;
auto wTopProximity = wBottom - (proximity * 2);
auto wBottomProximity = wBottom + (proximity * 2);
auto topMost = INT32_MAX;
window_visit_each([&](rct_window* w2) {
if (w2 == w || w2 == mainWindow)
return;
if (wRight < w2->windowPos.x || w->windowPos.x > w2->windowPos.x + w2->width)
return;
if (w2->windowPos.y < wTopProximity || w2->windowPos.y > wBottomProximity)
return;
topMost = std::min<int32_t>(topMost, w2->windowPos.y);
});
auto screenHeight = context_get_height();
if (screenHeight >= wTopProximity && screenHeight <= wBottomProximity)
topMost = std::min(topMost, screenHeight);
if (topMost != INT32_MAX)
w->windowPos.y = topMost - w->height;
}
void window_move_and_snap(rct_window* w, ScreenCoordsXY newWindowCoords, int32_t snapProximity)
{
auto originalPos = w->windowPos;
int32_t minY = (gScreenFlags & SCREEN_FLAGS_TITLE_DEMO) ? 1 : TOP_TOOLBAR_HEIGHT + 2;
newWindowCoords.y = std::clamp(newWindowCoords.y, minY, context_get_height() - 34);
if (snapProximity > 0)
{
w->windowPos = newWindowCoords;
window_snap_right(w, snapProximity);
window_snap_bottom(w, snapProximity);
window_snap_left(w, snapProximity);
window_snap_top(w, snapProximity);
if (w->windowPos == originalPos)
return;
newWindowCoords = w->windowPos;
w->windowPos = originalPos;
}
window_set_position(w, newWindowCoords);
}
int32_t window_can_resize(rct_window* w)
{
return (w->flags & WF_RESIZABLE) && (w->min_width != w->max_width || w->min_height != w->max_height);
}
/**
*
* rct2: 0x006EE3C3
*/
void textinput_cancel()
{
window_close_by_class(WC_TEXTINPUT);
}
void window_start_textbox(
rct_window* call_w, rct_widgetindex call_widget, rct_string_id existing_text, char* existing_args, int32_t maxLength)
{
if (gUsingWidgetTextBox)
window_cancel_textbox();
gUsingWidgetTextBox = true;
gCurrentTextBox.window.classification = call_w->classification;
gCurrentTextBox.window.number = call_w->number;
gCurrentTextBox.widget_index = call_widget;
gTextBoxFrameNo = 0;
gMaxTextBoxInputLength = maxLength;
window_close_by_class(WC_TEXTINPUT);
// Clear the text input buffer
std::fill_n(gTextBoxInput, maxLength, 0x00);
// Enter in the text input buffer any existing
// text.
if (existing_text != STR_NONE)
format_string(gTextBoxInput, TEXT_INPUT_SIZE, existing_text, &existing_args);
// In order to prevent strings that exceed the maxLength
// from crashing the game.
gTextBoxInput[maxLength - 1] = '\0';
gTextInput = context_start_text_input(gTextBoxInput, maxLength);
}
void window_cancel_textbox()
{
if (gUsingWidgetTextBox)
{
rct_window* w = window_find_by_number(gCurrentTextBox.window.classification, gCurrentTextBox.window.number);
if (w != nullptr)
{
window_event_textinput_call(w, gCurrentTextBox.widget_index, nullptr);
}
gCurrentTextBox.window.classification = WC_NULL;
gCurrentTextBox.window.number = 0;
context_stop_text_input();
gUsingWidgetTextBox = false;
if (w != nullptr)
{
widget_invalidate(w, gCurrentTextBox.widget_index);
}
gCurrentTextBox.widget_index = static_cast<uint16_t>(WindowWidgetType::Last);
}
}
void window_update_textbox_caret()
{
gTextBoxFrameNo++;
if (gTextBoxFrameNo > 30)
gTextBoxFrameNo = 0;
}
void window_update_textbox()
{
if (gUsingWidgetTextBox)
{
gTextBoxFrameNo = 0;
rct_window* w = window_find_by_number(gCurrentTextBox.window.classification, gCurrentTextBox.window.number);
widget_invalidate(w, gCurrentTextBox.widget_index);
window_event_textinput_call(w, gCurrentTextBox.widget_index, gTextBoxInput);
}
}
bool window_is_visible(rct_window* w)
{
// w->visibility is used to prevent repeat calculations within an iteration by caching the result
if (w == nullptr)
return false;
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 == WC_MAIN_WINDOW)
{
// default to previous behaviour
w->visibility = VisibilityCache::Visible;
return true;
}
// start from the window above the current
auto itPos = window_get_iterator(w);
for (auto it = std::next(itPos); it != g_window_list.end(); it++)
{
auto& w_other = *(*it);
// 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 window_draw_all(rct_drawpixelinfo* dpi, int32_t left, int32_t top, int32_t right, int32_t bottom)
{
auto windowDPI = dpi->Crop({ left, top }, { right - left, bottom - top });
window_visit_each([&windowDPI, left, top, right, bottom](rct_window* 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;
window_draw(&windowDPI, w, left, top, right, bottom);
});
}
rct_viewport* window_get_previous_viewport(rct_viewport* current)
{
bool foundPrevious = (current == nullptr);
for (auto it = g_window_list.rbegin(); it != g_window_list.rend(); it++)
{
auto& w = **it;
if (w.viewport != nullptr)
{
if (foundPrevious)
{
return w.viewport;
}
if (w.viewport == current)
{
foundPrevious = true;
}
}
}
return nullptr;
}
void window_reset_visibilities()
{
// reset window visibility status to unknown
window_visit_each([](rct_window* w) {
w->visibility = VisibilityCache::Unknown;
if (w->viewport != nullptr)
{
w->viewport->visibility = VisibilityCache::Unknown;
}
});
}
void window_init_all()
{
window_close_all_except_flags(0);
}
void window_follow_sprite(rct_window* w, size_t spriteIndex)
{
if (spriteIndex < MAX_ENTITIES || spriteIndex == SPRITE_INDEX_NULL)
{
w->viewport_smart_follow_sprite = static_cast<uint16_t>(spriteIndex);
}
}
void window_unfollow_sprite(rct_window* w)
{
w->viewport_smart_follow_sprite = SPRITE_INDEX_NULL;
w->viewport_target_sprite = SPRITE_INDEX_NULL;
}
rct_viewport* window_get_viewport(rct_window* w)
{
if (w == nullptr)
{
return nullptr;
}
return w->viewport;
}
rct_window* window_get_listening()
{
for (auto it = g_window_list.rbegin(); it != g_window_list.rend(); it++)
{
auto& w = **it;
auto viewport = w.viewport;
if (viewport != nullptr)
{
if (viewport->flags & VIEWPORT_FLAG_SOUND_ON)
{
return &w;
}
}
}
return nullptr;
}
rct_windowclass window_get_classification(rct_window* window)
{
return window->classification;
}
/**
*
* rct2: 0x006EAF26
*/
void WidgetScrollUpdateThumbs(rct_window* w, rct_widgetindex widget_index)
{
rct_widget* widget = &w->widgets[widget_index];
rct_scroll* scroll = &w->scrolls[window_get_scroll_data_index(w, widget_index)];
if (scroll->flags & HSCROLLBAR_VISIBLE)
{
int32_t view_size = widget->width() - 21;
if (scroll->flags & VSCROLLBAR_VISIBLE)
view_size -= 11;
int32_t x = scroll->h_left * view_size;
if (scroll->h_right != 0)
x /= scroll->h_right;
scroll->h_thumb_left = x + 11;
x = widget->width() - 2;
if (scroll->flags & VSCROLLBAR_VISIBLE)
x -= 11;
x += scroll->h_left;
if (scroll->h_right != 0)
x = (x * view_size) / scroll->h_right;
x += 11;
view_size += 10;
scroll->h_thumb_right = std::min(x, view_size);
if (scroll->h_thumb_right - scroll->h_thumb_left < 20)
{
double barPosition = (scroll->h_thumb_right * 1.0) / view_size;
scroll->h_thumb_left = static_cast<uint16_t>(std::lround(scroll->h_thumb_left - (20 * barPosition)));
scroll->h_thumb_right = static_cast<uint16_t>(std::lround(scroll->h_thumb_right + (20 * (1 - barPosition))));
}
}
if (scroll->flags & VSCROLLBAR_VISIBLE)
{
int32_t view_size = widget->height() - 21;
if (scroll->flags & HSCROLLBAR_VISIBLE)
view_size -= 11;
int32_t y = scroll->v_top * view_size;
if (scroll->v_bottom != 0)
y /= scroll->v_bottom;
scroll->v_thumb_top = y + 11;
y = widget->height() - 2;
if (scroll->flags & HSCROLLBAR_VISIBLE)
y -= 11;
y += scroll->v_top;
if (scroll->v_bottom != 0)
y = (y * view_size) / scroll->v_bottom;
y += 11;
view_size += 10;
scroll->v_thumb_bottom = std::min(y, view_size);
if (scroll->v_thumb_bottom - scroll->v_thumb_top < 20)
{
double barPosition = (scroll->v_thumb_bottom * 1.0) / view_size;
scroll->v_thumb_top = static_cast<uint16_t>(std::lround(scroll->v_thumb_top - (20 * barPosition)));
scroll->v_thumb_bottom = static_cast<uint16_t>(std::lround(scroll->v_thumb_bottom + (20 * (1 - barPosition))));
}
}
}