OpenLoco/src/openloco/Window.cpp

1529 lines
44 KiB
C++

#include "Window.h"
#include "Config.h"
#include "Console.h"
#include "Input.h"
#include "Ui.h"
#include "Widget.h"
#include "graphics/colours.h"
#include "interop/interop.hpp"
#include "localisation/FormatArguments.hpp"
#include "map/tile.h"
#include "map/tilemgr.h"
#include "things/thingmgr.h"
#include "ui/Rect.h"
#include "ui/scrollview.h"
#include <cassert>
#include <cinttypes>
using namespace openloco;
using namespace openloco::interop;
using namespace openloco::map;
namespace openloco::ui
{
template<typename T>
static bool isInteropEvent(T e)
{
return (uint32_t)e < 0x004D7000;
}
window::window(gfx::point_t position, gfx::ui_size_t size)
: x(position.x)
, y(position.y)
, width(size.width)
, height(size.height)
, min_width(size.width)
, max_width(size.width)
, min_height(size.height)
, max_height(size.height)
{
}
bool window::canResize()
{
return (this->flags & window_flags::resizable) && (this->min_width != this->max_width || this->min_height != this->max_height);
}
void window::capSize(int32_t minWidth, int32_t minHeight, int32_t maxWidth, int32_t maxHeight)
{
auto w = this->width;
auto h = this->height;
auto shouldInvalidateBefore = false;
auto shouldInvalidateAfter = false;
if (w < minWidth)
{
w = minWidth;
shouldInvalidateAfter = true;
}
if (h < minHeight)
{
h = minHeight;
shouldInvalidateAfter = true;
}
if (w > maxWidth)
{
shouldInvalidateBefore = true;
w = maxWidth;
}
if (h > maxHeight)
{
shouldInvalidateBefore = true;
h = maxHeight;
}
if (shouldInvalidateBefore)
{
invalidate();
}
this->width = w;
this->height = h;
this->min_width = minWidth;
this->min_height = minHeight;
this->max_width = maxWidth;
this->max_height = maxHeight;
if (shouldInvalidateAfter)
{
invalidate();
}
}
bool window::isEnabled(int8_t widget_index)
{
return (this->enabled_widgets & (1ULL << widget_index)) != 0;
}
bool window::isDisabled(int8_t widget_index)
{
return (this->disabled_widgets & (1ULL << widget_index)) != 0;
}
bool window::isActivated(widget_index index)
{
return (this->activated_widgets & (1ULL << index)) != 0;
}
bool window::isHoldable(ui::widget_index index)
{
return (this->holdable_widgets & (1ULL << index)) != 0;
}
// 0x0045A0B3
void window::drawViewports(gfx::drawpixelinfo_t* dpi)
{
if (viewports[0] != nullptr)
viewports[0]->render(dpi);
if (viewports[1] != nullptr)
viewports[1]->render(dpi);
}
// 0x0045FCE6
// Input:
// regs.ax: x
// regs.bx: y
// regs.bp: z
// Output:
// {x: regs.ax, y: regs.bx}
std::optional<map::map_pos> screenGetMapXyWithZ(const xy32& mouse, const int16_t z)
{
window* w = WindowManager::findAt(mouse.x, mouse.y);
if (w == nullptr)
{
return std::nullopt;
}
viewport* vp = w->viewports[0];
if (vp == nullptr)
{
return std::nullopt;
}
if (vp->containsUi(mouse))
{
viewport_pos vpos = vp->uiToMap(mouse);
map::map_pos position = viewportCoordToMapCoord(vpos.x, vpos.y, z, WindowManager::getCurrentRotation());
if (position.x <= 0x2FFF && position.y <= 0x2FFF)
{
return position;
}
}
return std::nullopt;
}
// 0x0045FD41
// Input:
// regs.ax: x
// regs.bx: y
// regs.bp: z
// regs.edx: rotation
// Output:
// {x: regs.ax, y: regs.bx}
// Note: in the original code: regs.dx: x/2 (probably not used anywhere)
map::map_pos viewportCoordToMapCoord(int16_t x, int16_t y, int16_t z, int32_t rotation)
{
map::map_pos ret{};
switch (rotation)
{
case 0:
ret.x = -x / 2 + y + z;
ret.y = x / 2 + y + z;
break;
case 1:
ret.x = -x / 2 - y - z;
ret.y = -x / 2 + y + z;
break;
case 2:
ret.x = x / 2 - y - z;
ret.y = -x / 2 - y - z;
break;
case 3:
ret.x = x / 2 + y + z;
ret.y = x / 2 - y - z;
break;
}
return ret;
}
// 0x004C641F
// regs.dl: underground
// regs.esi: w
// regs.edi: vp
static void viewportSetUndergroundFlag(bool underground, ui::window* w, ui::viewport* vp)
{
if (w->type == WindowType::main)
return;
bool shouldInvalidate;
if (!underground)
{
shouldInvalidate = !(vp->flags & viewport_flags::underground_view);
vp->flags &= ~viewport_flags::underground_view;
}
else
{
shouldInvalidate = vp->flags & viewport_flags::underground_view;
vp->flags |= viewport_flags::underground_view;
}
if (shouldInvalidate)
w->invalidate();
}
void window::viewportSetUndergroundFlag(bool underground, ui::viewport* vp)
{
ui::viewportSetUndergroundFlag(underground, this, vp);
}
// 0x004C68E4
static void viewportMove(int16_t x, int16_t y, ui::window* w, ui::viewport* vp)
{
int origX = vp->view_x >> vp->zoom;
int origY = vp->view_y >> vp->zoom;
int newX = x >> vp->zoom;
int newY = y >> vp->zoom;
int diffX = origX - newX;
int diffY = origY - newY;
vp->view_x = x;
vp->view_y = y;
// If no change in viewing area
if (diffX == 0 && diffY == 0)
return;
if (vp->flags & viewport_flags::hide_foreground_tracks_roads || vp->flags & viewport_flags::hide_foreground_scenery_buildings || w->flags & window_flags::flag_8)
{
auto rect = ui::Rect(vp->x, vp->y, vp->width, vp->height);
gfx::redrawScreenRect(rect);
return;
}
uint8_t zoom = (1 << vp->zoom);
viewport backup = *vp;
if (vp->x < 0)
{
vp->width += vp->x;
vp->view_width += vp->x * zoom;
vp->view_x -= vp->x * zoom;
vp->x = 0;
}
int32_t eax = vp->x + vp->width - ui::width();
if (eax > 0)
{
vp->width -= eax;
vp->view_width -= eax * zoom;
}
if (vp->width <= 0)
{
*vp = backup;
return;
}
if (vp->y < 0)
{
vp->height += vp->y;
vp->view_height += vp->y * zoom;
vp->view_y -= vp->y * zoom;
vp->y = 0;
}
eax = vp->y + vp->height - ui::height();
if (eax > 0)
{
vp->height -= eax;
vp->view_height -= eax * zoom;
}
if (vp->height <= 0)
{
*vp = backup;
return;
}
WindowManager::viewportShiftPixels(w, vp, diffX, diffY);
*vp = backup;
}
// 0x004C6456
void window::viewportsUpdatePosition()
{
for (int i = 0; i < 2; i++)
{
viewport* viewport = this->viewports[i];
viewport_config* config = &this->viewport_configurations[i];
if (viewport == nullptr)
{
continue;
}
this->callOnResize();
int16_t centreX, centreY;
if (config->viewport_target_sprite != 0xFFFF)
{
auto thing = thingmgr::get<Thing>(config->viewport_target_sprite);
int z = (tileElementHeight(thing->x, thing->y).landHeight) - 16;
bool underground = (thing->z < z);
viewportSetUndergroundFlag(underground, viewport);
viewport->centre2dCoordinates(thing->x, thing->y, thing->z + 12, &centreX, &centreY);
}
else
{
int16_t midX = config->saved_view_x + (viewport->view_width / 2);
int16_t midY = config->saved_view_y + (viewport->view_height / 2);
map::map_pos mapCoord = viewportCoordToMapCoord(midX, midY, 128, viewport->getRotation());
viewportSetUndergroundFlag(false, viewport);
bool atMapEdge = false;
if (mapCoord.x < -256)
{
mapCoord.x = -256;
atMapEdge = true;
}
if (mapCoord.y < -256)
{
mapCoord.y = -256;
atMapEdge = true;
}
if (mapCoord.x > 0x30FE)
{
mapCoord.x = 0x30FE;
atMapEdge = true;
}
if (mapCoord.y > 0x30FE)
{
mapCoord.y = 0x30FE;
atMapEdge = true;
}
if (atMapEdge)
{
auto coord_2d = coordinate3dTo2d(mapCoord.x, mapCoord.y, 128, viewport->getRotation());
config->saved_view_x = coord_2d.x - viewport->view_width / 2;
config->saved_view_y = coord_2d.y - viewport->view_height / 2;
}
centreX = config->saved_view_x;
centreY = config->saved_view_y;
if (this->flags & window_flags::scrolling_to_location)
{
bool flippedX = false;
centreX -= viewport->view_x;
if (centreX < 0)
{
centreX = -centreX;
flippedX = true;
}
bool flippedY = false;
centreY -= viewport->view_y;
if (centreY < 0)
{
centreY = -centreY;
flippedY = true;
}
centreX = (centreX + 7) / 8; // ceil(centreX / 8.0);
centreY = (centreY + 7) / 8; // ceil(centreX / 8.0);
if (centreX == 0 && centreY == 0)
{
this->flags &= ~window_flags::scrolling_to_location;
}
if (flippedX)
{
centreX = -centreX;
}
if (flippedY)
{
centreY = -centreY;
}
centreX += viewport->view_x;
centreY += viewport->view_y;
}
}
viewportMove(centreX, centreY, this, viewport);
}
}
// 0x004C99B9
void window::invalidatePressedImageButtons()
{
registers regs;
regs.esi = (int32_t)this;
call(0x004C99B9, regs);
}
// 0x004CA4BD
// input: regs.esi - window (this)
void window::invalidate()
{
gfx::setDirtyBlocks(x, y, x + width, y + height);
}
// 0x004CA115
void window::updateScrollWidgets()
{
uint32_t s = 0;
for (int w = 0;; ++w)
{
ui::widget_t* widget = &this->widgets[w];
if (widget->type == widget_type::end)
break;
if (widget->type != widget_type::scrollview)
continue;
uint16_t scrollWidth = 0, scrollHeight = 0;
this->callGetScrollSize(s, &scrollWidth, &scrollHeight);
bool invalidate = false;
if (widget->content & scrollbars::horizontal)
{
if (this->scroll_areas[s].contentWidth != scrollWidth + 1)
{
this->scroll_areas[s].contentWidth = scrollWidth + 1;
invalidate = true;
}
}
if (widget->content & scrollbars::vertical)
{
if (this->scroll_areas[s].contentHeight != scrollHeight + 1)
{
this->scroll_areas[s].contentHeight = scrollHeight + 1;
invalidate = true;
}
}
if (invalidate)
{
ui::scrollview::updateThumbs(this, w);
this->invalidate();
}
s++;
}
}
// 0x004CA17F
void window::initScrollWidgets()
{
uint32_t s = 0;
for (int w = 0;; ++w)
{
ui::widget_t* widget = &this->widgets[w];
if (widget->type == widget_type::end)
break;
if (widget->type != widget_type::scrollview)
continue;
this->scroll_areas[s].flags = 0;
uint16_t scrollWidth = 0, scrollHeight = 0;
this->callGetScrollSize(s, &scrollWidth, &scrollHeight);
this->scroll_areas[s].contentOffsetX = 0;
this->scroll_areas[s].contentWidth = scrollWidth + 1;
this->scroll_areas[s].contentOffsetY = 0;
this->scroll_areas[s].contentHeight = scrollHeight + 1;
if (widget->content & scrollbars::horizontal)
{
this->scroll_areas[s].flags |= ui::scrollview::scroll_flags::HSCROLLBAR_VISIBLE;
}
if (widget->content & scrollbars::vertical)
{
this->scroll_areas[s].flags |= ui::scrollview::scroll_flags::VSCROLLBAR_VISIBLE;
}
ui::scrollview::updateThumbs(this, w);
s++;
}
}
int8_t window::getScrollDataIndex(widget_index index)
{
int8_t scrollIndex = 0;
for (int i = 0; i < index; i++)
{
if (this->widgets[i].type == ui::widget_type::scrollview)
{
scrollIndex++;
}
}
return scrollIndex;
}
void window::setDisabledWidgetsAndInvalidate(uint32_t _disabled_widgets)
{
registers regs;
regs.eax = (int32_t)_disabled_widgets;
regs.esi = (int32_t)this;
call(0x004CC7CB, regs);
}
// 0x00459E54
// TODO: needs expansion in terms of (output) parameters.
static void getMapCoordinatesFromPos(int32_t screenX, int32_t screenY, int32_t flags, int16_t* x, int16_t* y)
{
registers regs;
regs.ax = screenX;
regs.bx = screenY;
regs.edx = flags;
call(0x00459E54, regs);
*x = regs.ax;
*y = regs.cx;
}
void window::viewportGetMapCoordsByCursor(int16_t* map_x, int16_t* map_y, int16_t* offset_x, int16_t* offset_y)
{
// Get mouse position to offset against.
int32_t mouse_x, mouse_y;
ui::getCursorPos(mouse_x, mouse_y);
// Compute map coordinate by mouse position.
getMapCoordinatesFromPos(mouse_x, mouse_y, 0, map_x, map_y);
// Get viewport coordinates centring around the tile.
int32_t base_height = map::tileElementHeight(*map_x, *map_y).landHeight;
int16_t dest_x, dest_y;
viewport* v = this->viewports[0];
v->centre2dCoordinates(*map_x, *map_y, base_height, &dest_x, &dest_y);
// Rebase mouse position onto centre of window, and compensate for zoom level.
int16_t rebased_x = ((this->width >> 1) - mouse_x) * (1 << v->zoom),
rebased_y = ((this->height >> 1) - mouse_y) * (1 << v->zoom);
// Compute cursor offset relative to tile.
viewport_config* vc = &this->viewport_configurations[0];
*offset_x = (vc->saved_view_x - (dest_x + rebased_x)) * (1 << v->zoom);
*offset_y = (vc->saved_view_y - (dest_y + rebased_y)) * (1 << v->zoom);
}
// 0x004C6801
void window::moveWindowToLocation(viewport_pos pos)
{
if (this->viewport_configurations->viewport_target_sprite != thing_id::null)
return;
if (this->flags & window_flags::viewport_no_scrolling)
return;
this->viewport_configurations->saved_view_x = pos.x;
this->viewport_configurations->saved_view_y = pos.y;
this->flags |= window_flags::scrolling_to_location;
}
// 0x004C6827
void window::viewportCentreOnTile(const map::map_pos3& loc)
{
auto viewport = this->viewports[0];
if (viewport == nullptr)
return;
auto tileHeight = tileElementHeight(loc.x, loc.y).landHeight;
tileHeight -= 16;
if (loc.z < tileHeight)
{
if (!(viewport->flags & viewport_flags::underground_view))
{
this->invalidate();
}
viewport->flags |= viewport_flags::underground_view;
}
else
{
if (viewport->flags & viewport_flags::underground_view)
{
this->invalidate();
}
viewport->flags &= ~viewport_flags::underground_view;
}
auto pos = coordinate3dTo2d(loc.x, loc.y, loc.z, WindowManager::getCurrentRotation());
pos.x -= viewport->view_width / 2;
pos.y -= viewport->view_height / 2;
moveWindowToLocation(pos);
}
void window::viewportCentreTileAroundCursor(int16_t map_x, int16_t map_y, int16_t offset_x, int16_t offset_y)
{
// Get viewport coordinates centring around the tile.
int16_t dest_x, dest_y;
int32_t base_height = map::tileElementHeight(map_x, map_y).landHeight;
viewport* v = this->viewports[0];
v->centre2dCoordinates(map_x, map_y, base_height, &dest_x, &dest_y);
// Get mouse position to offset against.
int32_t mouse_x, mouse_y;
ui::getCursorPos(mouse_x, mouse_y);
// Rebase mouse position onto centre of window, and compensate for zoom level.
int16_t rebased_x = ((this->width >> 1) - mouse_x) * (1 << v->zoom),
rebased_y = ((this->height >> 1) - mouse_y) * (1 << v->zoom);
// Apply offset to the viewport.
viewport_config* vc = &this->viewport_configurations[0];
vc->saved_view_x = dest_x + rebased_x + (offset_x / (1 << v->zoom));
vc->saved_view_y = dest_y + rebased_y + (offset_y / (1 << v->zoom));
}
void window::viewportZoomSet(int8_t zoomLevel, bool toCursor)
{
viewport* v = this->viewports[0];
viewport_config* vc = &this->viewport_configurations[0];
zoomLevel = std::clamp<int8_t>(zoomLevel, 0, 3);
if (v->zoom == zoomLevel)
return;
// Zooming to cursor? Remember where we're pointing at the moment.
int16_t saved_map_x = 0;
int16_t saved_map_y = 0;
int16_t offset_x = 0;
int16_t offset_y = 0;
if (toCursor && config::getNew().zoom_to_cursor)
{
this->viewportGetMapCoordsByCursor(&saved_map_x, &saved_map_y, &offset_x, &offset_y);
}
// Zoom in
while (v->zoom > zoomLevel)
{
v->zoom--;
vc->saved_view_x += v->view_width / 4;
vc->saved_view_y += v->view_height / 4;
v->view_width /= 2;
v->view_height /= 2;
}
// Zoom out
while (v->zoom < zoomLevel)
{
v->zoom++;
vc->saved_view_x -= v->view_width / 2;
vc->saved_view_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 (toCursor && config::getNew().zoom_to_cursor)
{
this->viewportCentreTileAroundCursor(saved_map_x, saved_map_y, offset_x, offset_y);
}
this->invalidate();
}
// 0x0045EFDB
void window::viewportZoomIn(bool toCursor)
{
if (this->viewports[0] == nullptr)
return;
this->viewportZoomSet(this->viewports[0]->zoom - 1, toCursor);
}
// 0x0045F015
void window::viewportZoomOut(bool toCursor)
{
if (this->viewports[0] == nullptr)
return;
this->viewportZoomSet(this->viewports[0]->zoom + 1, toCursor);
}
// 0x0045F04F
void window::viewportRotateRight()
{
registers regs;
regs.esi = (uintptr_t)this;
call(0x0045F04F, regs);
}
// 0x0045F0ED
void window::viewportRotateLeft()
{
registers regs;
regs.esi = (uintptr_t)this;
call(0x0045F0ED, regs);
}
bool window::move(int16_t dx, int16_t dy)
{
if (dx == 0 && dy == 0)
{
return false;
}
this->invalidate();
this->x += dx;
this->y += dy;
if (this->viewports[0] != nullptr)
{
this->viewports[0]->x += dx;
this->viewports[0]->y += dy;
}
if (this->viewports[1] != nullptr)
{
this->viewports[1]->x += dx;
this->viewports[1]->y += dy;
}
this->invalidate();
return true;
}
// 0x004CD320
void window::moveInsideScreenEdges()
{
gfx::point_t offset = { 0, 0 };
const int16_t xOvershoot = this->x + this->width - ui::width();
// Over the edge on the right?
if (xOvershoot > 0)
offset.x -= xOvershoot;
// If not, on the left?
if (this->x < 0)
offset.x -= this->x;
const int16_t yOvershoot = this->y + this->height - (ui::height() - 27);
// Over the edge at the bottom?
if (yOvershoot > 0)
offset.y -= yOvershoot;
// Maybe at the top?
if (this->y - 28 < 0)
offset.y -= this->y - 28;
if (offset == 0)
return;
this->invalidate();
this->x += offset.x;
this->y += offset.y;
this->invalidate();
if (this->viewports[0] != nullptr)
{
this->viewports[0]->x += offset.x;
this->viewports[0]->y += offset.y;
}
if (this->viewports[1] != nullptr)
{
this->viewports[1]->x += offset.x;
this->viewports[1]->y += offset.y;
}
}
bool window::moveToCentre()
{
int16_t dx = ((ui::width() - this->width) / 2);
int16_t dy = ((ui::height() - this->height) / 2);
dx = dx - this->x;
dy = dy - this->y;
return this->move(dx, dy);
}
// 0x004C9513
widget_index window::findWidgetAt(int16_t xPos, int16_t yPos)
{
this->callPrepareDraw();
widget_index activeWidget = -1;
widget_index widgetIndex = -1;
for (ui::widget_t* widget = &this->widgets[0]; widget->type != widget_type::end; widget++)
{
widgetIndex++;
if (widget->type == widget_type::none)
continue;
if (xPos < this->x + widget->left)
continue;
if (xPos > this->x + widget->right)
continue;
if (yPos < this->y + widget->top)
continue;
if (yPos > this->y + widget->bottom)
continue;
activeWidget = widgetIndex;
}
if (activeWidget == -1)
{
return -1;
}
if (this->widgets[activeWidget].type == widget_type ::wt_18)
{
activeWidget++;
}
return activeWidget;
}
void window::callClose()
{
if (event_handlers->on_close == nullptr)
return;
if (isInteropEvent(event_handlers->on_close))
{
registers regs;
regs.esi = (int32_t)this;
call((uint32_t)this->event_handlers->on_close, regs);
return;
}
event_handlers->on_close(this);
}
void window::callOnPeriodicUpdate()
{
if (event_handlers->on_periodic_update == nullptr)
return;
if (isInteropEvent(event_handlers->on_periodic_update))
{
registers regs;
regs.esi = (int32_t)this;
call((uint32_t)this->event_handlers->on_periodic_update, regs);
return;
}
event_handlers->on_periodic_update(this);
}
void window::callUpdate()
{
if (event_handlers->on_update == nullptr)
return;
if (isInteropEvent(event_handlers->on_update))
{
registers regs;
regs.esi = (int32_t)this;
call((uintptr_t)this->event_handlers->on_update, regs);
return;
}
event_handlers->on_update(this);
}
void window::call_8()
{
if (event_handlers->event_08 == nullptr)
return;
if (isInteropEvent(event_handlers->event_08))
{
registers regs;
regs.esi = (int32_t)this;
call((uintptr_t)this->event_handlers->event_08, regs);
return;
}
event_handlers->event_08(this);
}
void window::call_9()
{
if (event_handlers->event_09 == nullptr)
return;
if (isInteropEvent(event_handlers->event_09))
{
registers regs;
regs.esi = (int32_t)this;
call((uintptr_t)this->event_handlers->event_09, regs);
return;
}
event_handlers->event_09(this);
}
void window::callToolUpdate(int16_t widget_index, int16_t xPos, int16_t yPos)
{
if (event_handlers->on_tool_update == nullptr)
return;
if (isInteropEvent(event_handlers->on_tool_update))
{
registers regs;
regs.esi = (int32_t)this;
regs.dx = widget_index;
regs.ax = xPos;
regs.bx = yPos;
call((uintptr_t)this->event_handlers->on_tool_update, regs);
return;
}
event_handlers->on_tool_update(*this, widget_index, xPos, yPos);
}
void window::callToolDown(int16_t widget_index, int16_t xPos, int16_t yPos)
{
if (event_handlers->on_tool_down == nullptr)
return;
if (isInteropEvent(event_handlers->on_tool_down))
{
registers regs;
regs.ax = xPos;
regs.bx = yPos;
regs.dx = widget_index;
regs.esi = (int32_t)this;
call((uint32_t)this->event_handlers->on_tool_down, regs);
return;
}
event_handlers->on_tool_down(*this, widget_index, xPos, yPos);
}
void window::call_12(const int16_t widget_index)
{
if (event_handlers->event_12 == nullptr)
return;
if (isInteropEvent(event_handlers->event_12))
{
registers regs;
regs.dx = widget_index;
regs.esi = (int32_t)this;
call((uint32_t)this->event_handlers->event_12, regs);
return;
}
event_handlers->event_12(*this, widget_index);
}
void window::call_13(const int16_t widget_index)
{
if (event_handlers->event_13 == nullptr)
return;
if (isInteropEvent(event_handlers->event_13))
{
registers regs;
regs.dx = widget_index;
regs.esi = (int32_t)this;
call((uint32_t)this->event_handlers->event_13, regs);
return;
}
event_handlers->event_13(*this, widget_index);
}
void window::callToolAbort(int16_t widget_index)
{
if (event_handlers->on_tool_abort == nullptr)
return;
if (isInteropEvent(event_handlers->on_tool_abort))
{
registers regs;
regs.dx = widget_index;
regs.esi = (int32_t)this;
call((uint32_t)this->event_handlers->on_tool_abort, regs);
return;
}
event_handlers->on_tool_abort(*this, widget_index);
}
ui::cursor_id window::call_15(int16_t xPos, int16_t yPos, ui::cursor_id fallback, bool* out)
{
registers regs;
regs.ax = xPos;
regs.bl = *out;
regs.cx = yPos;
regs.edi = (int32_t)fallback;
regs.esi = (int32_t)this;
call(this->event_handlers->event_15, regs);
*out = regs.bl;
return (cursor_id)regs.edi;
}
ui::cursor_id window::callCursor(int16_t widgetIdx, int16_t xPos, int16_t yPos, ui::cursor_id fallback)
{
if (event_handlers->cursor == nullptr)
return fallback;
if (isInteropEvent(event_handlers->cursor))
{
registers regs;
regs.cx = xPos;
regs.dx = yPos;
regs.ax = widgetIdx;
regs.ebx = -1;
regs.edi = (int32_t) & this->widgets[widgetIdx];
regs.esi = (int32_t)this;
call((uintptr_t)this->event_handlers->cursor, regs);
if (regs.ebx == -1)
{
return fallback;
}
return (cursor_id)regs.ebx;
}
return event_handlers->cursor(this, widgetIdx, xPos, yPos, fallback);
}
void window::callOnMouseUp(widget_index widgetIndex)
{
if (event_handlers->on_mouse_up == nullptr)
return;
if (isInteropEvent(event_handlers->on_mouse_up))
{
registers regs;
regs.edx = widgetIndex;
regs.esi = (uint32_t)this;
// Not sure if this is used
regs.edi = (uint32_t) & this->widgets[widgetIndex];
call((uintptr_t)this->event_handlers->on_mouse_up, regs);
return;
}
event_handlers->on_mouse_up(this, widgetIndex);
}
ui::window* window::callOnResize()
{
if (event_handlers->on_resize == nullptr)
return this;
if (isInteropEvent(event_handlers->on_resize))
{
registers regs;
regs.esi = (int32_t)this;
call((uint32_t)event_handlers->on_resize, regs);
return (window*)regs.esi;
}
event_handlers->on_resize(this);
return this;
}
void window::call_3(int8_t widget_index)
{
if (event_handlers->event_03 == nullptr)
return;
if (isInteropEvent(event_handlers->event_03))
{
registers regs;
regs.edx = widget_index;
regs.esi = (uint32_t)this;
regs.edi = (uint32_t) & this->widgets[widget_index];
call((uint32_t)this->event_handlers->event_03, regs);
return;
}
event_handlers->event_03(this, widget_index);
}
void window::callOnMouseDown(ui::widget_index widget_index)
{
if (event_handlers->on_mouse_down == nullptr)
return;
if (isInteropEvent(event_handlers->on_mouse_down))
{
registers regs;
regs.edx = widget_index;
regs.esi = (uint32_t)this;
regs.edi = (uint32_t) & this->widgets[widget_index];
call((uint32_t)this->event_handlers->on_mouse_down, regs);
return;
}
event_handlers->on_mouse_down(this, widget_index);
}
void window::callOnDropdown(ui::widget_index widget_index, int16_t item_index)
{
if (event_handlers->on_dropdown == nullptr)
return;
if (isInteropEvent(event_handlers->on_dropdown))
{
registers regs;
regs.ax = item_index;
regs.edx = widget_index;
regs.esi = (uint32_t)this;
call((uint32_t)this->event_handlers->on_dropdown, regs);
return;
}
event_handlers->on_dropdown(this, widget_index, item_index);
}
void window::callGetScrollSize(uint32_t scrollIndex, uint16_t* scrollWidth, uint16_t* scrollHeight)
{
if (event_handlers->get_scroll_size == nullptr)
return;
if (isInteropEvent(event_handlers->get_scroll_size))
{
registers regs;
regs.eax = scrollIndex;
regs.esi = (uintptr_t)this;
call((uint32_t)this->event_handlers->get_scroll_size, regs);
*scrollWidth = regs.cx;
*scrollHeight = regs.dx;
return;
}
event_handlers->get_scroll_size(this, scrollIndex, scrollWidth, scrollHeight);
}
void window::callScrollMouseDown(int16_t xPos, int16_t yPos, uint8_t scroll_index)
{
if (event_handlers->scroll_mouse_down == nullptr)
return;
if (isInteropEvent(event_handlers->scroll_mouse_down))
{
registers regs;
regs.ax = scroll_index;
regs.esi = (int32_t)this;
regs.cx = xPos;
regs.dx = yPos;
call((uint32_t)this->event_handlers->scroll_mouse_down, regs);
return;
}
this->event_handlers->scroll_mouse_down(this, xPos, yPos, scroll_index);
}
void window::callScrollMouseDrag(int16_t xPos, int16_t yPos, uint8_t scroll_index)
{
if (event_handlers->scroll_mouse_drag == nullptr)
return;
if (isInteropEvent(event_handlers->scroll_mouse_drag))
{
registers regs;
regs.ax = scroll_index;
regs.esi = (int32_t)this;
regs.cx = xPos;
regs.dx = yPos;
call((uint32_t)this->event_handlers->scroll_mouse_drag, regs);
return;
}
this->event_handlers->scroll_mouse_drag(this, xPos, yPos, scroll_index);
}
void window::callScrollMouseOver(int16_t xPos, int16_t yPos, uint8_t scroll_index)
{
if (event_handlers->scroll_mouse_over == nullptr)
return;
if (isInteropEvent(event_handlers->scroll_mouse_over))
{
registers regs;
regs.ax = scroll_index;
regs.esi = (int32_t)this;
regs.cx = xPos;
regs.dx = yPos;
call((uint32_t)this->event_handlers->scroll_mouse_over, regs);
return;
}
this->event_handlers->scroll_mouse_over(this, xPos, yPos, scroll_index);
}
void window::callTextInput(widget_index caller, char* buffer)
{
if (event_handlers->text_input == nullptr)
return;
if (isInteropEvent(event_handlers->text_input))
{
registers regs;
regs.dx = caller;
regs.esi = (int32_t)this;
regs.cl = 1;
regs.edi = (uintptr_t)buffer;
call((uintptr_t)this->event_handlers->text_input, regs);
return;
}
this->event_handlers->text_input(this, caller, buffer);
}
void window::callViewportRotate()
{
if (event_handlers->viewport_rotate == nullptr)
return;
if (isInteropEvent(event_handlers->viewport_rotate))
{
registers regs;
regs.esi = (int32_t)this;
call((uintptr_t)this->event_handlers->viewport_rotate, regs);
return;
}
this->event_handlers->viewport_rotate(this);
}
bool window::callTooltip(int16_t widget_index)
{
if (event_handlers->tooltip == nullptr)
return false;
if (isInteropEvent(event_handlers->tooltip))
{
registers regs;
regs.ax = widget_index;
regs.esi = (int32_t)this;
call((int32_t)this->event_handlers->tooltip, regs);
return regs.ax != (int16_t)string_ids::null;
}
auto args = FormatArguments();
event_handlers->tooltip(args, this, widget_index);
return true;
}
void window::callOnMove(int16_t xPos, int16_t yPos)
{
registers regs;
regs.cx = xPos;
regs.dx = yPos;
regs.esi = (int32_t)this;
call(this->event_handlers->on_move, regs);
}
void window::callPrepareDraw()
{
if (event_handlers->prepare_draw == nullptr)
return;
if (isInteropEvent(event_handlers->prepare_draw))
{
registers regs;
regs.esi = (int32_t)this;
call((int32_t)this->event_handlers->prepare_draw, regs);
return;
}
event_handlers->prepare_draw(this);
}
void window::callDraw(gfx::drawpixelinfo_t* dpi)
{
if (event_handlers->draw == nullptr)
return;
if (isInteropEvent(this->event_handlers->draw))
{
registers regs;
regs.esi = (int32_t)this;
regs.edi = (int32_t)dpi;
call((int32_t)this->event_handlers->draw, regs);
return;
}
event_handlers->draw(this, dpi);
}
void window::callDrawScroll(gfx::drawpixelinfo_t* dpi, uint32_t scrollIndex)
{
if (event_handlers->draw_scroll == nullptr)
return;
if (isInteropEvent(this->event_handlers->draw_scroll))
{
registers regs;
regs.ax = scrollIndex;
regs.esi = (int32_t)this;
regs.edi = (int32_t)dpi;
call((int32_t)event_handlers->draw_scroll, regs);
return;
}
event_handlers->draw_scroll(this, dpi, scrollIndex);
}
// 0x004CA4DF
void window::draw(gfx::drawpixelinfo_t* dpi)
{
if ((this->flags & window_flags::transparent) && !(this->flags & window_flags::no_background))
{
gfx::fillRect(dpi, this->x, this->y, this->x + this->width - 1, this->y + this->height - 1, 0x2000000 | 52);
}
uint64_t pressed_widget = 0;
if (input::state() == input::input_state::dropdown_active || input::state() == input::input_state::widget_pressed)
{
if (input::isPressed(type, number))
{
const widget_index widgetIndex = input::getPressedWidgetIndex();
pressed_widget = 1ULL << widgetIndex;
}
}
uint64_t tool_widget = 0;
if (input::isToolActive(this->type, this->number))
{
tool_widget = 1ULL << addr<0x00523394, uint32_t>();
}
uint64_t hovered_widget = 0;
if (input::isHovering(this->type, this->number))
{
hovered_widget = 1ULL << input::getHoveredWidgetIndex();
}
int scrollviewIndex = 0;
for (int widgetIndex = 0; widgetIndex < 64; widgetIndex++)
{
auto widget = &this->widgets[widgetIndex];
if (widget->type == widget_type::end)
{
break;
}
if ((this->flags & window_flags::no_background) == 0)
{
// Check if widget is outside the draw region
if (this->x + widget->left >= dpi->x + dpi->width && this->x + widget->right < dpi->x)
{
if (this->y + widget->top >= dpi->y + dpi->height && this->y + widget->bottom < dpi->y)
{
continue;
}
}
}
uint16_t widgetFlags = 0;
if (widget->colour == 0 && this->flags & window_flags::flag_11)
{
widgetFlags = 0x80;
}
uint8_t colour = this->colours[widget->colour];
bool enabled = (this->enabled_widgets & (1ULL << widgetIndex)) != 0;
bool disabled = (this->disabled_widgets & (1ULL << widgetIndex)) != 0;
bool activated = (this->activated_widgets & (1ULL << widgetIndex)) != 0;
activated |= (pressed_widget & (1ULL << widgetIndex)) != 0;
activated |= (tool_widget & (1ULL << widgetIndex)) != 0;
bool hovered = (hovered_widget & (1ULL << widgetIndex)) != 0;
switch (widget->type)
{
case widget_type::none:
case widget_type::end:
break;
case widget_type::panel:
widget::drawPanel(dpi, this, widget, widgetFlags, colour);
break;
case widget_type::frame:
widget::drawFrame(dpi, this, widget, widgetFlags, colour);
break;
case widget_type::wt_3:
widget::draw_3(dpi, this, widget, widgetFlags, colour, enabled, disabled, activated);
break;
case widget_type::wt_4:
assert(false); // Unused
break;
case widget_type::wt_5:
case widget_type::wt_6:
case widget_type::wt_7:
case widget_type::wt_8:
widget::draw_5(dpi, this, widget, widgetFlags, colour, enabled, disabled, activated);
break;
case widget_type::wt_9:
widget::draw_9(dpi, this, widget, widgetFlags, colour, enabled, disabled, activated, hovered);
break;
case widget_type::wt_10:
widget::draw_10(dpi, this, widget, widgetFlags, colour, enabled, disabled, activated, hovered);
break;
case widget_type::wt_11:
case widget_type::wt_12:
case widget_type::wt_14:
if (widget->type == widget_type::wt_12)
{
assert(false); // Unused
}
widget::draw_11_a(dpi, this, widget, widgetFlags, colour, enabled, disabled, activated);
widget::draw_13(dpi, this, widget, widgetFlags, colour, enabled, disabled, activated);
break;
case widget_type::wt_13:
widget::draw_13(dpi, this, widget, widgetFlags, colour, enabled, disabled, activated);
break;
case widget_type::wt_15:
widget::draw_15(dpi, this, widget, widgetFlags, colour, disabled);
break;
case widget_type::wt_16:
assert(false); // Unused
break;
case widget_type::wt_17:
case widget_type::wt_18:
case widget_type::viewport:
widget::draw_17(dpi, this, widget, widgetFlags, colour);
widget::draw_15(dpi, this, widget, widgetFlags, colour, disabled);
break;
case widget_type::wt_20:
case widget_type::wt_21:
assert(false); // Unused
break;
case widget_type::caption_22:
widget::draw_22_caption(dpi, this, widget, widgetFlags, colour);
break;
case widget_type::caption_23:
widget::draw_23_caption(dpi, this, widget, widgetFlags, colour);
break;
case widget_type::caption_24:
widget::draw_24_caption(dpi, this, widget, widgetFlags, colour);
break;
case widget_type::caption_25:
widget::draw_25_caption(dpi, this, widget, widgetFlags, colour);
break;
case widget_type::scrollview:
widget::drawScrollview(dpi, this, widget, widgetFlags, colour, enabled, disabled, activated, hovered, scrollviewIndex);
scrollviewIndex++;
break;
case widget_type::checkbox:
widget::draw_27_checkbox(dpi, this, widget, widgetFlags, colour, enabled, disabled, activated);
widget::draw_27_label(dpi, this, widget, widgetFlags, colour, disabled);
break;
case widget_type::wt_28:
assert(false); // Unused
widget::draw_27_label(dpi, this, widget, widgetFlags, colour, disabled);
break;
case widget_type::wt_29:
assert(false); // Unused
widget::draw_29(dpi, this, widget);
break;
}
}
if (this->flags & window_flags::white_border_mask)
{
gfx::fillRectInset(
dpi,
this->x,
this->y,
this->x + this->width - 1,
this->y + this->height - 1,
colour::white,
0x10);
}
}
}