OpenRCT2/src/openrct2-ui/interface/Widget.cpp

1194 lines
40 KiB
C++

/*****************************************************************************
* Copyright (c) 2014-2022 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 "Widget.h"
#include "Window.h"
#include <algorithm>
#include <cmath>
#include <openrct2/Context.h>
#include <openrct2/Input.h>
#include <openrct2/drawing/Drawing.h>
#include <openrct2/localisation/Formatter.h>
#include <openrct2/localisation/Localisation.h>
#include <openrct2/sprites.h>
#include <openrct2/util/Util.h>
static void WidgetFrameDraw(rct_drawpixelinfo* dpi, rct_window& w, WidgetIndex widgetIndex);
static void WidgetResizeDraw(rct_drawpixelinfo* dpi, rct_window& w, WidgetIndex widgetIndex);
static void WidgetButtonDraw(rct_drawpixelinfo* dpi, rct_window& w, WidgetIndex widgetIndex);
static void WidgetTabDraw(rct_drawpixelinfo* dpi, rct_window& w, WidgetIndex widgetIndex);
static void WidgetFlatButtonDraw(rct_drawpixelinfo* dpi, rct_window& w, WidgetIndex widgetIndex);
static void WidgetTextButton(rct_drawpixelinfo* dpi, rct_window& w, WidgetIndex widgetIndex);
static void WidgetTextCentred(rct_drawpixelinfo* dpi, rct_window& w, WidgetIndex widgetIndex);
static void WidgetText(rct_drawpixelinfo* dpi, rct_window& w, WidgetIndex widgetIndex);
static void WidgetTextInset(rct_drawpixelinfo* dpi, rct_window& w, WidgetIndex widgetIndex);
static void WidgetTextBoxDraw(rct_drawpixelinfo* dpi, rct_window& w, WidgetIndex widgetIndex);
static void WidgetGroupboxDraw(rct_drawpixelinfo* dpi, rct_window& w, WidgetIndex widgetIndex);
static void WidgetCaptionDraw(rct_drawpixelinfo* dpi, rct_window& w, WidgetIndex widgetIndex);
static void WidgetCheckboxDraw(rct_drawpixelinfo* dpi, rct_window& w, WidgetIndex widgetIndex);
static void WidgetCloseboxDraw(rct_drawpixelinfo* dpi, rct_window& w, WidgetIndex widgetIndex);
static void WidgetScrollDraw(rct_drawpixelinfo* dpi, rct_window& w, WidgetIndex widgetIndex);
static void WidgetHScrollbarDraw(
rct_drawpixelinfo* dpi, const rct_scroll& scroll, int32_t l, int32_t t, int32_t r, int32_t b, int32_t colour);
static void WidgetVScrollbarDraw(
rct_drawpixelinfo* dpi, const rct_scroll& scroll, int32_t l, int32_t t, int32_t r, int32_t b, int32_t colour);
static void WidgetDrawImage(rct_drawpixelinfo* dpi, rct_window& w, WidgetIndex widgetIndex);
/**
*
* rct2: 0x006EB2A8
*/
void WidgetDraw(rct_drawpixelinfo* dpi, rct_window& w, WidgetIndex widgetIndex)
{
const auto* widget = GetWidgetByIndex(w, widgetIndex);
if (widget == nullptr)
{
log_error("Tried drawing an out-of-bounds widget index!");
return;
}
switch (widget->type)
{
case WindowWidgetType::Frame:
WidgetFrameDraw(dpi, w, widgetIndex);
break;
case WindowWidgetType::Resize:
WidgetResizeDraw(dpi, w, widgetIndex);
break;
case WindowWidgetType::ImgBtn:
WidgetButtonDraw(dpi, w, widgetIndex);
break;
case WindowWidgetType::ColourBtn:
case WindowWidgetType::TrnBtn:
case WindowWidgetType::Tab:
WidgetTabDraw(dpi, w, widgetIndex);
break;
case WindowWidgetType::FlatBtn:
WidgetFlatButtonDraw(dpi, w, widgetIndex);
break;
case WindowWidgetType::Button:
case WindowWidgetType::TableHeader:
WidgetTextButton(dpi, w, widgetIndex);
break;
case WindowWidgetType::LabelCentred:
WidgetTextCentred(dpi, w, widgetIndex);
break;
case WindowWidgetType::Label:
WidgetText(dpi, w, widgetIndex);
break;
case WindowWidgetType::Spinner:
case WindowWidgetType::DropdownMenu:
case WindowWidgetType::Viewport:
WidgetTextInset(dpi, w, widgetIndex);
break;
case WindowWidgetType::Groupbox:
WidgetGroupboxDraw(dpi, w, widgetIndex);
break;
case WindowWidgetType::Caption:
WidgetCaptionDraw(dpi, w, widgetIndex);
break;
case WindowWidgetType::CloseBox:
WidgetCloseboxDraw(dpi, w, widgetIndex);
break;
case WindowWidgetType::Scroll:
WidgetScrollDraw(dpi, w, widgetIndex);
break;
case WindowWidgetType::Checkbox:
WidgetCheckboxDraw(dpi, w, widgetIndex);
break;
case WindowWidgetType::TextBox:
WidgetTextBoxDraw(dpi, w, widgetIndex);
break;
default:
break;
}
}
/**
*
* rct2: 0x006EB6CE
*/
static void WidgetFrameDraw(rct_drawpixelinfo* dpi, rct_window& w, WidgetIndex widgetIndex)
{
// Get the widget
const auto& widget = w.widgets[widgetIndex];
// Resolve the absolute ltrb
auto leftTop = w.windowPos + ScreenCoordsXY{ widget.left, widget.top };
int32_t r = w.windowPos.x + widget.right;
int32_t b = w.windowPos.y + widget.bottom;
//
uint8_t press = ((w.flags & WF_10) ? INSET_RECT_FLAG_FILL_MID_LIGHT : 0);
// Get the colour
uint8_t colour = w.colours[widget.colour];
// Draw the frame
gfx_fill_rect_inset(dpi, { leftTop, { r, b } }, colour, press);
// Check if the window can be resized
if (!(w.flags & WF_RESIZABLE))
return;
if (w.min_width == w.max_width && w.min_height == w.max_height)
return;
// Draw the resize sprite at the bottom right corner
leftTop = w.windowPos + ScreenCoordsXY{ widget.right - 18, widget.bottom - 18 };
gfx_draw_sprite(dpi, ImageId(SPR_RESIZE, colour & 0x7F), leftTop);
}
/**
*
* rct2: 0x006EB765
*/
static void WidgetResizeDraw(rct_drawpixelinfo* dpi, rct_window& w, WidgetIndex widgetIndex)
{
// Get the widget
const auto& widget = w.widgets[widgetIndex];
// Resolve the absolute ltrb
auto leftTop = w.windowPos + ScreenCoordsXY{ widget.left, widget.top };
int32_t r = w.windowPos.x + widget.right;
int32_t b = w.windowPos.y + widget.bottom;
// Get the colour
uint8_t colour = w.colours[widget.colour];
// Draw the panel
gfx_fill_rect_inset(dpi, { leftTop, { r, b } }, colour, 0);
// Check if the window can be resized
if (!(w.flags & WF_RESIZABLE))
return;
if (w.min_width == w.max_width && w.min_height == w.max_height)
return;
// Draw the resize sprite at the bottom right corner
leftTop = w.windowPos + ScreenCoordsXY{ widget.right - 18, widget.bottom - 18 };
gfx_draw_sprite(dpi, ImageId(SPR_RESIZE, colour & 0x7F), leftTop);
}
/**
*
* rct2: 0x006EB8E5
*/
static void WidgetButtonDraw(rct_drawpixelinfo* dpi, rct_window& w, WidgetIndex widgetIndex)
{
// Get the widget
const auto& widget = w.widgets[widgetIndex];
// Resolve the absolute ltrb
ScreenRect rect{ w.windowPos + ScreenCoordsXY{ widget.left, widget.top },
w.windowPos + ScreenCoordsXY{ widget.right, widget.bottom } };
// Check if the button is pressed down
uint8_t press = WidgetIsPressed(w, widgetIndex) || WidgetIsActiveTool(w, widgetIndex) ? INSET_RECT_FLAG_BORDER_INSET : 0;
// Get the colour
uint8_t colour = w.colours[widget.colour];
if (static_cast<int32_t>(widget.image) == -2)
{
// Draw border with no fill
gfx_fill_rect_inset(dpi, rect, colour, press | INSET_RECT_FLAG_FILL_NONE);
return;
}
// Draw the border with fill
gfx_fill_rect_inset(dpi, rect, colour, press);
WidgetDrawImage(dpi, w, widgetIndex);
}
/**
*
* rct2: 0x006EB806
*/
static void WidgetTabDraw(rct_drawpixelinfo* dpi, rct_window& w, WidgetIndex widgetIndex)
{
// Get the widget
auto& widget = w.widgets[widgetIndex];
if (widget.type != WindowWidgetType::Tab && static_cast<int32_t>(widget.image) == -1)
return;
if (widget.type == WindowWidgetType::Tab)
{
if (WidgetIsDisabled(w, widgetIndex))
return;
if (widget.image == static_cast<uint32_t>(SPR_NONE))
{
// Set standard tab sprite to use.
widget.image = IMAGE_TYPE_REMAP | SPR_TAB;
}
}
// Draw widgets that aren't explicitly disabled.
if (!WidgetIsDisabled(w, widgetIndex))
{
WidgetDrawImage(dpi, w, widgetIndex);
return;
}
if (widget.type != WindowWidgetType::TrnBtn)
{
WidgetDrawImage(dpi, w, widgetIndex);
return;
}
// Resolve the absolute ltrb
auto leftTop = w.windowPos + ScreenCoordsXY{ widget.left, widget.top };
// Get the colour and disabled image
auto colour = w.colours[widget.colour] & 0x7F;
auto image = ImageId::FromUInt32(widget.image + 2).WithPrimary(colour);
// Draw disabled image
gfx_draw_sprite(dpi, image, leftTop);
}
/**
*
* rct2: 0x006EB861
*/
static void WidgetFlatButtonDraw(rct_drawpixelinfo* dpi, rct_window& w, WidgetIndex widgetIndex)
{
if (!WidgetIsDisabled(w, widgetIndex) && WidgetIsHighlighted(w, widgetIndex))
{
WidgetButtonDraw(dpi, w, widgetIndex);
return;
}
// Get the widget
const auto& widget = w.widgets[widgetIndex];
// Resolve the absolute ltrb
ScreenRect rect{ w.windowPos + ScreenCoordsXY{ widget.left, widget.top },
w.windowPos + ScreenCoordsXY{ widget.right, widget.bottom } };
// Get the colour
uint8_t colour = w.colours[widget.colour];
// Check if the button is pressed down
if (WidgetIsPressed(w, widgetIndex) || WidgetIsActiveTool(w, widgetIndex))
{
if (static_cast<int32_t>(widget.image) == -2)
{
// Draw border with no fill
gfx_fill_rect_inset(dpi, rect, colour, INSET_RECT_FLAG_BORDER_INSET | INSET_RECT_FLAG_FILL_NONE);
return;
}
// Draw the border with fill
gfx_fill_rect_inset(dpi, rect, colour, INSET_RECT_FLAG_BORDER_INSET);
}
// Draw image
WidgetDrawImage(dpi, w, widgetIndex);
}
/**
*
* rct2: 0x006EBBEB
*/
static void WidgetTextButton(rct_drawpixelinfo* dpi, rct_window& w, WidgetIndex widgetIndex)
{
// Get the widget
const auto& widget = w.widgets[widgetIndex];
// Resolve the absolute ltrb
ScreenRect rect{ w.windowPos + ScreenCoordsXY{ widget.left, widget.top },
w.windowPos + ScreenCoordsXY{ widget.right, widget.bottom } };
// Get the colour
uint8_t colour = w.colours[widget.colour];
// Border
uint8_t press = WidgetIsPressed(w, widgetIndex) || WidgetIsActiveTool(w, widgetIndex) ? INSET_RECT_FLAG_BORDER_INSET : 0;
gfx_fill_rect_inset(dpi, rect, colour, press);
// Button caption
if (widget.type != WindowWidgetType::TableHeader)
{
WidgetTextCentred(dpi, w, widgetIndex);
}
else
{
WidgetText(dpi, w, widgetIndex);
}
}
/**
*
* rct2: 0x006EBC41
*/
static void WidgetTextCentred(rct_drawpixelinfo* dpi, rct_window& w, WidgetIndex widgetIndex)
{
// Get the widget
const auto& widget = w.widgets[widgetIndex];
if (widget.text == STR_NONE)
return;
// Get the colour
colour_t colour = w.colours[widget.colour];
colour &= ~(COLOUR_FLAG_TRANSLUCENT);
if (WidgetIsDisabled(w, widgetIndex))
colour |= COLOUR_FLAG_INSET;
// Resolve the absolute ltrb
auto topLeft = w.windowPos + ScreenCoordsXY{ widget.left, 0 };
int32_t r = w.windowPos.x + widget.right;
if (widget.type == WindowWidgetType::Button || widget.type == WindowWidgetType::TableHeader)
topLeft.y += widget.textTop();
else
topLeft.y += widget.top;
auto stringId = widget.text;
auto ft = Formatter::Common();
if (widget.flags & WIDGET_FLAGS::TEXT_IS_STRING)
{
stringId = STR_STRING;
ft.Add<utf8*>(widget.string);
}
ScreenCoordsXY coords = { (topLeft.x + r + 1) / 2 - 1, topLeft.y };
if (widget.type == WindowWidgetType::LabelCentred)
{
DrawTextWrapped(dpi, coords, widget.width() - 2, stringId, ft, { colour, TextAlignment::CENTRE });
}
else
{
DrawTextEllipsised(dpi, coords, widget.width() - 2, stringId, ft, { colour, TextAlignment::CENTRE });
}
}
/**
*
* rct2: 0x006EBD52
*/
static void WidgetText(rct_drawpixelinfo* dpi, rct_window& w, WidgetIndex widgetIndex)
{
// Get the widget
const auto& widget = w.widgets[widgetIndex];
if (widget.text == STR_NONE || widget.text == STR_VIEWPORT)
return;
// Get the colour
uint8_t colour = w.colours[widget.colour];
if (WidgetIsDisabled(w, widgetIndex))
colour |= COLOUR_FLAG_INSET;
// Resolve the absolute ltrb
int32_t l = w.windowPos.x + widget.left;
int32_t r = w.windowPos.x + widget.right;
int32_t t;
if (widget.type == WindowWidgetType::Button || widget.type == WindowWidgetType::DropdownMenu
|| widget.type == WindowWidgetType::Spinner || widget.type == WindowWidgetType::TableHeader)
{
t = w.windowPos.y + widget.textTop();
}
else
t = w.windowPos.y + widget.top;
auto stringId = widget.text;
auto ft = Formatter::Common();
if (widget.flags & WIDGET_FLAGS::TEXT_IS_STRING)
{
stringId = STR_STRING;
ft.Add<utf8*>(widget.string);
}
ScreenCoordsXY coords = { l + 1, t };
if (widget.type == WindowWidgetType::LabelCentred)
{
DrawTextWrapped(dpi, coords, r - l, stringId, ft, { colour, TextAlignment::CENTRE });
}
else
{
DrawTextEllipsised(dpi, coords, r - l, stringId, ft, colour);
}
}
/**
*
* rct2: 0x006EBD1F
*/
static void WidgetTextInset(rct_drawpixelinfo* dpi, rct_window& w, WidgetIndex widgetIndex)
{
// Get the widget
const auto& widget = w.widgets[widgetIndex];
// Resolve the absolute ltrb
ScreenRect rect{ w.windowPos + ScreenCoordsXY{ widget.left, widget.top },
w.windowPos + ScreenCoordsXY{ widget.right, widget.bottom } };
// Get the colour
uint8_t colour = w.colours[widget.colour];
gfx_fill_rect_inset(dpi, rect, colour, INSET_RECT_F_60);
WidgetText(dpi, w, widgetIndex);
}
static std::pair<StringId, void*> WidgetGetStringidAndArgs(const rct_widget& widget)
{
auto stringId = widget.text;
void* formatArgs = gCommonFormatArgs;
if (widget.flags & WIDGET_FLAGS::TEXT_IS_STRING)
{
if (widget.string == nullptr || widget.string[0] == '\0')
{
stringId = STR_NONE;
formatArgs = nullptr;
}
else
{
stringId = STR_STRING;
formatArgs = const_cast<void*>(reinterpret_cast<const void*>(&widget.string));
}
}
return std::make_pair(stringId, formatArgs);
}
/**
*
* rct2: 0x006EB535
*/
static void WidgetGroupboxDraw(rct_drawpixelinfo* dpi, rct_window& w, WidgetIndex widgetIndex)
{
// Get the widget
const auto& widget = w.widgets[widgetIndex];
// Resolve the absolute ltrb
auto l = w.windowPos.x + widget.left + 5;
auto t = w.windowPos.y + widget.top;
auto textRight = l;
// Text
auto [stringId, formatArgs] = WidgetGetStringidAndArgs(widget);
if (stringId != STR_NONE)
{
uint8_t colour = w.colours[widget.colour] & 0x7F;
if (WidgetIsDisabled(w, widgetIndex))
colour |= COLOUR_FLAG_INSET;
utf8 buffer[512] = { 0 };
format_string(buffer, sizeof(buffer), stringId, formatArgs);
auto ft = Formatter();
ft.Add<utf8*>(buffer);
DrawTextBasic(dpi, { l, t }, STR_STRING, ft, { colour });
textRight = l + gfx_get_string_width(buffer, FontStyle::Medium) + 1;
}
// Border
// Resolve the absolute ltrb
l = w.windowPos.x + widget.left;
t = w.windowPos.y + widget.top + 4;
const auto r = w.windowPos.x + widget.right;
const auto b = w.windowPos.y + widget.bottom;
// Get the colour
uint8_t colour = w.colours[widget.colour] & 0x7F;
// Border left of text
gfx_fill_rect(dpi, { { l, t }, { l + 4, t } }, ColourMapA[colour].mid_dark);
gfx_fill_rect(dpi, { { l + 1, t + 1 }, { l + 4, t + 1 } }, ColourMapA[colour].lighter);
// Border right of text
gfx_fill_rect(dpi, { { textRight, t }, { r - 1, t } }, ColourMapA[colour].mid_dark);
gfx_fill_rect(dpi, { { textRight, t + 1 }, { r - 2, t + 1 } }, ColourMapA[colour].lighter);
// Border right
gfx_fill_rect(dpi, { { r - 1, t + 1 }, { r - 1, b - 1 } }, ColourMapA[colour].mid_dark);
gfx_fill_rect(dpi, { { r, t }, { r, b } }, ColourMapA[colour].lighter);
// Border bottom
gfx_fill_rect(dpi, { { l, b - 1 }, { r - 2, b - 1 } }, ColourMapA[colour].mid_dark);
gfx_fill_rect(dpi, { { l, b }, { r - 1, b } }, ColourMapA[colour].lighter);
// Border left
gfx_fill_rect(dpi, { { l, t + 1 }, { l, b - 2 } }, ColourMapA[colour].mid_dark);
gfx_fill_rect(dpi, { { l + 1, t + 2 }, { l + 1, b - 2 } }, ColourMapA[colour].lighter);
}
/**
*
* rct2: 0x006EB2F9
*/
static void WidgetCaptionDraw(rct_drawpixelinfo* dpi, rct_window& w, WidgetIndex widgetIndex)
{
// Get the widget
const auto* widget = &w.widgets[widgetIndex];
// Resolve the absolute ltrb
auto topLeft = w.windowPos + ScreenCoordsXY{ widget->left, widget->top };
auto bottomRight = w.windowPos + ScreenCoordsXY{ widget->right, widget->bottom };
// Get the colour
uint8_t colour = w.colours[widget->colour];
uint8_t press = INSET_RECT_F_60;
if (w.flags & WF_10)
press |= INSET_RECT_FLAG_FILL_MID_LIGHT;
gfx_fill_rect_inset(dpi, { topLeft, bottomRight }, colour, press);
// Black caption bars look slightly green, this fixes that
if (colour == 0)
gfx_fill_rect(
dpi, { { topLeft + ScreenCoordsXY{ 1, 1 } }, { bottomRight - ScreenCoordsXY{ 1, 1 } } }, ColourMapA[colour].dark);
else
gfx_filter_rect(
dpi, { { topLeft + ScreenCoordsXY{ 1, 1 } }, { bottomRight - ScreenCoordsXY{ 1, 1 } } },
FilterPaletteID::PaletteDarken3);
// Draw text
if (widget->text == STR_NONE)
return;
topLeft = w.windowPos + ScreenCoordsXY{ widget->left + 2, widget->top + 1 };
int32_t width = widget->width() - 4;
if ((widget + 1)->type == WindowWidgetType::CloseBox)
{
width -= 10;
if ((widget + 2)->type == WindowWidgetType::CloseBox)
width -= 10;
}
topLeft.x += width / 2;
DrawTextEllipsised(
dpi, topLeft, width, widget->text, Formatter::Common(), { COLOUR_WHITE | COLOUR_FLAG_OUTLINE, TextAlignment::CENTRE });
}
/**
*
* rct2: 0x006EBB85
*/
static void WidgetCloseboxDraw(rct_drawpixelinfo* dpi, rct_window& w, WidgetIndex widgetIndex)
{
// Get the widget
const auto& widget = w.widgets[widgetIndex];
// Resolve the absolute ltrb
auto topLeft = w.windowPos + ScreenCoordsXY{ widget.left, widget.top };
auto bottomRight = w.windowPos + ScreenCoordsXY{ widget.right, widget.bottom };
// Check if the button is pressed down
uint8_t press = 0;
if (w.flags & WF_10)
press |= INSET_RECT_FLAG_FILL_MID_LIGHT;
if (WidgetIsPressed(w, widgetIndex) || WidgetIsActiveTool(w, widgetIndex))
press |= INSET_RECT_FLAG_BORDER_INSET;
// Get the colour
uint8_t colour = w.colours[widget.colour];
// Draw the button
gfx_fill_rect_inset(dpi, { topLeft, bottomRight }, colour, press);
if (widget.text == STR_NONE)
return;
topLeft = w.windowPos + ScreenCoordsXY{ widget.midX() - 1, std::max<int32_t>(widget.top, widget.midY() - 5) };
if (WidgetIsDisabled(w, widgetIndex))
colour |= COLOUR_FLAG_INSET;
DrawTextEllipsised(dpi, topLeft, widget.width() - 2, widget.text, Formatter::Common(), { colour, TextAlignment::CENTRE });
}
/**
*
* rct2: 0x006EBAD9
*/
static void WidgetCheckboxDraw(rct_drawpixelinfo* dpi, rct_window& w, WidgetIndex widgetIndex)
{
// Get the widget
const auto& widget = w.widgets[widgetIndex];
// Resolve the absolute ltb
ScreenCoordsXY topLeft = w.windowPos + ScreenCoordsXY{ widget.left, widget.top };
ScreenCoordsXY bottomRight = w.windowPos + ScreenCoordsXY{ widget.right, widget.bottom };
ScreenCoordsXY midLeft = { topLeft.x, (topLeft.y + bottomRight.y) / 2 };
// Get the colour
colour_t colour = w.colours[widget.colour];
// checkbox
gfx_fill_rect_inset(dpi, { midLeft - ScreenCoordsXY{ 0, 5 }, midLeft + ScreenCoordsXY{ 9, 4 } }, colour, INSET_RECT_F_60);
if (WidgetIsDisabled(w, widgetIndex))
{
colour |= COLOUR_FLAG_INSET;
}
// fill it when checkbox is pressed
if (WidgetIsPressed(w, widgetIndex))
{
gfx_draw_string(
dpi, { midLeft - ScreenCoordsXY{ 0, 5 } }, static_cast<const char*>(CheckBoxMarkString),
{ static_cast<colour_t>(NOT_TRANSLUCENT(colour)) });
}
// draw the text
if (widget.text == STR_NONE)
return;
auto [stringId, formatArgs] = WidgetGetStringidAndArgs(widget);
gfx_draw_string_left_centred(dpi, stringId, formatArgs, colour, { midLeft + ScreenCoordsXY{ 14, 0 } });
}
/**
*
* rct2: 0x006EBD96
*/
static void WidgetScrollDraw(rct_drawpixelinfo* dpi, rct_window& w, WidgetIndex widgetIndex)
{
// Get the widget
int32_t scrollIndex = window_get_scroll_data_index(w, widgetIndex);
const auto& widget = w.widgets[widgetIndex];
const auto& scroll = w.scrolls[scrollIndex];
// Resolve the absolute ltrb
ScreenCoordsXY topLeft = w.windowPos + ScreenCoordsXY{ widget.left, widget.top };
ScreenCoordsXY bottomRight = w.windowPos + ScreenCoordsXY{ widget.right, widget.bottom };
// Get the colour
uint8_t colour = w.colours[widget.colour];
// Draw the border
gfx_fill_rect_inset(dpi, { topLeft, bottomRight }, colour, INSET_RECT_F_60);
// Inflate by -1
topLeft.x++;
topLeft.y++;
bottomRight.x--;
bottomRight.y--;
// Horizontal scrollbar
if (scroll.flags & HSCROLLBAR_VISIBLE)
WidgetHScrollbarDraw(
dpi, scroll, topLeft.x, bottomRight.y - SCROLLBAR_WIDTH,
((scroll.flags & VSCROLLBAR_VISIBLE) ? bottomRight.x - (SCROLLBAR_WIDTH + 1) : bottomRight.x), bottomRight.y,
colour);
// Vertical scrollbar
if (scroll.flags & VSCROLLBAR_VISIBLE)
WidgetVScrollbarDraw(
dpi, scroll, bottomRight.x - SCROLLBAR_WIDTH, topLeft.y, bottomRight.x,
((scroll.flags & HSCROLLBAR_VISIBLE) ? bottomRight.y - (SCROLLBAR_WIDTH + 1) : bottomRight.y), colour);
// Contents
if (scroll.flags & HSCROLLBAR_VISIBLE)
bottomRight.y -= (SCROLLBAR_WIDTH + 1);
if (scroll.flags & VSCROLLBAR_VISIBLE)
bottomRight.x -= (SCROLLBAR_WIDTH + 1);
bottomRight.y++;
bottomRight.x++;
// Create a new inner scroll dpi
rct_drawpixelinfo scroll_dpi = *dpi;
// Clip the scroll dpi against the outer dpi
int32_t cl = std::max<int32_t>(dpi->x, topLeft.x);
int32_t ct = std::max<int32_t>(dpi->y, topLeft.y);
int32_t cr = std::min<int32_t>(dpi->x + dpi->width, bottomRight.x);
int32_t cb = std::min<int32_t>(dpi->y + dpi->height, bottomRight.y);
// Set the respective dpi attributes
scroll_dpi.x = cl - topLeft.x + scroll.h_left;
scroll_dpi.y = ct - topLeft.y + scroll.v_top;
scroll_dpi.width = cr - cl;
scroll_dpi.height = cb - ct;
scroll_dpi.bits += cl - dpi->x;
scroll_dpi.bits += (ct - dpi->y) * (dpi->width + dpi->pitch);
scroll_dpi.pitch = (dpi->width + dpi->pitch) - scroll_dpi.width;
// Draw the scroll contents
if (scroll_dpi.width > 0 && scroll_dpi.height > 0)
window_event_scroll_paint_call(&w, &scroll_dpi, scrollIndex);
}
static void WidgetHScrollbarDraw(
rct_drawpixelinfo* dpi, const rct_scroll& scroll, int32_t l, int32_t t, int32_t r, int32_t b, int32_t colour)
{
colour &= 0x7F;
// Trough
gfx_fill_rect(dpi, { { l + SCROLLBAR_WIDTH, t }, { r - SCROLLBAR_WIDTH, b } }, ColourMapA[colour].lighter);
gfx_fill_rect(dpi, { { l + SCROLLBAR_WIDTH, t }, { r - SCROLLBAR_WIDTH, b } }, 0x1000000 | ColourMapA[colour].mid_dark);
gfx_fill_rect(dpi, { { l + SCROLLBAR_WIDTH, t + 2 }, { r - SCROLLBAR_WIDTH, t + 2 } }, ColourMapA[colour].mid_dark);
gfx_fill_rect(dpi, { { l + SCROLLBAR_WIDTH, t + 3 }, { r - SCROLLBAR_WIDTH, t + 3 } }, ColourMapA[colour].lighter);
gfx_fill_rect(dpi, { { l + SCROLLBAR_WIDTH, t + 7 }, { r - SCROLLBAR_WIDTH, t + 7 } }, ColourMapA[colour].mid_dark);
gfx_fill_rect(dpi, { { l + SCROLLBAR_WIDTH, t + 8 }, { r - SCROLLBAR_WIDTH, t + 8 } }, ColourMapA[colour].lighter);
// Left button
{
uint8_t flags = (scroll.flags & HSCROLLBAR_LEFT_PRESSED) ? INSET_RECT_FLAG_BORDER_INSET : 0;
gfx_fill_rect_inset(dpi, { { l, t }, { l + (SCROLLBAR_WIDTH - 1), b } }, colour, flags);
gfx_draw_string(dpi, { l + 1, t }, static_cast<const char*>(BlackLeftArrowString), {});
}
// Thumb
{
int16_t left = std::max(l + SCROLLBAR_WIDTH, l + scroll.h_thumb_left - 1);
int16_t right = std::min(r - SCROLLBAR_WIDTH, l + scroll.h_thumb_right - 1);
uint8_t flags = (scroll.flags & HSCROLLBAR_THUMB_PRESSED) ? INSET_RECT_FLAG_BORDER_INSET : 0;
gfx_fill_rect_inset(dpi, { { left, t }, { right, b } }, colour, flags);
}
// Right button
{
uint8_t flags = (scroll.flags & HSCROLLBAR_RIGHT_PRESSED) ? INSET_RECT_FLAG_BORDER_INSET : 0;
gfx_fill_rect_inset(dpi, { { r - (SCROLLBAR_WIDTH - 1), t }, { r, b } }, colour, flags);
gfx_draw_string(dpi, { r - 6, t }, static_cast<const char*>(BlackRightArrowString), {});
}
}
static void WidgetVScrollbarDraw(
rct_drawpixelinfo* dpi, const rct_scroll& scroll, int32_t l, int32_t t, int32_t r, int32_t b, int32_t colour)
{
colour &= 0x7F;
// Trough
gfx_fill_rect(dpi, { { l, t + SCROLLBAR_WIDTH }, { r, b - SCROLLBAR_WIDTH } }, ColourMapA[colour].lighter);
gfx_fill_rect(dpi, { { l, t + SCROLLBAR_WIDTH }, { r, b - SCROLLBAR_WIDTH } }, 0x1000000 | ColourMapA[colour].mid_dark);
gfx_fill_rect(dpi, { { l + 2, t + SCROLLBAR_WIDTH }, { l + 2, b - SCROLLBAR_WIDTH } }, ColourMapA[colour].mid_dark);
gfx_fill_rect(dpi, { { l + 3, t + SCROLLBAR_WIDTH }, { l + 3, b - SCROLLBAR_WIDTH } }, ColourMapA[colour].lighter);
gfx_fill_rect(dpi, { { l + 7, t + SCROLLBAR_WIDTH }, { l + 7, b - SCROLLBAR_WIDTH } }, ColourMapA[colour].mid_dark);
gfx_fill_rect(dpi, { { l + 8, t + SCROLLBAR_WIDTH }, { l + 8, b - SCROLLBAR_WIDTH } }, ColourMapA[colour].lighter);
// Up button
gfx_fill_rect_inset(
dpi, { { l, t }, { r, t + (SCROLLBAR_WIDTH - 1) } }, colour,
((scroll.flags & VSCROLLBAR_UP_PRESSED) ? INSET_RECT_FLAG_BORDER_INSET : 0));
gfx_draw_string(dpi, { l + 1, t - 1 }, static_cast<const char*>(BlackUpArrowString), {});
// Thumb
gfx_fill_rect_inset(
dpi,
{ { l, std::max(t + SCROLLBAR_WIDTH, t + scroll.v_thumb_top - 1) },
{ r, std::min(b - SCROLLBAR_WIDTH, t + scroll.v_thumb_bottom - 1) } },
colour, ((scroll.flags & VSCROLLBAR_THUMB_PRESSED) ? INSET_RECT_FLAG_BORDER_INSET : 0));
// Down button
gfx_fill_rect_inset(
dpi, { { l, b - (SCROLLBAR_WIDTH - 1) }, { r, b } }, colour,
((scroll.flags & VSCROLLBAR_DOWN_PRESSED) ? INSET_RECT_FLAG_BORDER_INSET : 0));
gfx_draw_string(dpi, { l + 1, b - (SCROLLBAR_WIDTH - 1) }, static_cast<const char*>(BlackDownArrowString), {});
}
/**
*
* rct2: 0x006EB951
*/
static void WidgetDrawImage(rct_drawpixelinfo* dpi, rct_window& w, WidgetIndex widgetIndex)
{
// Get the widget
const auto& widget = w.widgets[widgetIndex];
// Get the image
if (static_cast<int32_t>(widget.image) == SPR_NONE)
return;
auto image = ImageId::FromUInt32(widget.image);
// Resolve the absolute ltrb
auto screenCoords = w.windowPos + ScreenCoordsXY{ widget.left, widget.top };
// Get the colour
uint8_t colour = NOT_TRANSLUCENT(w.colours[widget.colour]);
if (widget.type == WindowWidgetType::ColourBtn || widget.type == WindowWidgetType::TrnBtn
|| widget.type == WindowWidgetType::Tab)
if (WidgetIsPressed(w, widgetIndex) || WidgetIsActiveTool(w, widgetIndex))
image = image.WithIndexOffset(1);
if (WidgetIsDisabled(w, widgetIndex))
{
// Draw greyed out (light border bottom right shadow)
colour = w.colours[widget.colour];
colour = ColourMapA[NOT_TRANSLUCENT(colour)].lighter;
gfx_draw_sprite_solid(dpi, image, screenCoords + ScreenCoordsXY{ 1, 1 }, colour);
// Draw greyed out (dark)
colour = w.colours[widget.colour];
colour = ColourMapA[NOT_TRANSLUCENT(colour)].mid_light;
gfx_draw_sprite_solid(dpi, image, screenCoords, colour);
}
else
{
if (image.HasSecondary())
{
// ?
}
if (image.IsBlended())
image = image.WithBlended(false);
else
image = image.WithPrimary(colour);
gfx_draw_sprite(dpi, image, screenCoords);
}
}
bool WidgetIsDisabled(const rct_window& w, WidgetIndex widgetIndex)
{
if (w.classification == WindowClass::Custom)
return w.widgets[widgetIndex].flags & WIDGET_FLAGS::IS_DISABLED;
return (w.disabled_widgets & (1LL << widgetIndex)) != 0;
}
bool WidgetIsHoldable(const rct_window& w, WidgetIndex widgetIndex)
{
if (w.classification == WindowClass::Custom)
return w.widgets[widgetIndex].flags & WIDGET_FLAGS::IS_HOLDABLE;
return (w.hold_down_widgets & (1LL << widgetIndex)) != 0;
}
bool WidgetIsVisible(const rct_window& w, WidgetIndex widgetIndex)
{
return w.widgets[widgetIndex].IsVisible();
}
bool WidgetIsPressed(const rct_window& w, WidgetIndex widgetIndex)
{
if (w.classification == WindowClass::Custom)
{
if (w.widgets[widgetIndex].flags & WIDGET_FLAGS::IS_PRESSED)
{
return true;
}
}
else
{
if (w.pressed_widgets & (1LL << widgetIndex))
{
return true;
}
}
if (input_get_state() == InputState::WidgetPressed || input_get_state() == InputState::DropdownActive)
{
if (!(input_test_flag(INPUT_FLAG_WIDGET_PRESSED)))
return false;
if (gPressedWidget.window_classification != w.classification)
return false;
if (gPressedWidget.window_number != w.number)
return false;
if (gPressedWidget.widget_index != widgetIndex)
return false;
return true;
}
return false;
}
bool WidgetIsHighlighted(const rct_window& w, WidgetIndex widgetIndex)
{
if (gHoverWidget.window_classification != w.classification)
return false;
if (gHoverWidget.window_number != w.number)
return false;
if (gHoverWidget.widget_index != widgetIndex)
return false;
return true;
}
bool WidgetIsActiveTool(const rct_window& w, WidgetIndex widgetIndex)
{
if (!(input_test_flag(INPUT_FLAG_TOOL_ACTIVE)))
return false;
if (gCurrentToolWidget.window_classification != w.classification)
return false;
if (gCurrentToolWidget.window_number != w.number)
return false;
if (gCurrentToolWidget.widget_index != widgetIndex)
return false;
return true;
}
/**
*
* rct2: 0x006E9F92
* eax: x / output_x
* ebx: y / output_y
* ecx: output_scroll_area
* edx: scroll_id
* esi: w
* edi: widget
*/
void WidgetScrollGetPart(
rct_window& w, const rct_widget* widget, const ScreenCoordsXY& screenCoords, ScreenCoordsXY& retScreenCoords,
int32_t* output_scroll_area, int32_t* scroll_id)
{
*scroll_id = 0;
for (rct_widget* iterator = w.widgets; iterator != widget; iterator++)
{
if (iterator->type == WindowWidgetType::Scroll)
{
*scroll_id += 1;
}
}
const auto& scroll = w.scrolls[*scroll_id];
if ((scroll.flags & HSCROLLBAR_VISIBLE) && screenCoords.y >= (w.windowPos.y + widget->bottom - (SCROLLBAR_WIDTH + 1)))
{
// horizontal scrollbar
int32_t rightOffset = 0;
int32_t iteratorLeft = widget->left + w.windowPos.x + SCROLLBAR_WIDTH;
int32_t iteratorRight = widget->right + w.windowPos.x - SCROLLBAR_WIDTH;
if (!(scroll.flags & VSCROLLBAR_VISIBLE))
{
rightOffset = SCROLLBAR_WIDTH + 1;
}
if (screenCoords.x <= iteratorLeft)
{
*output_scroll_area = SCROLL_PART_HSCROLLBAR_LEFT;
}
else if (screenCoords.x >= iteratorRight + rightOffset)
{
*output_scroll_area = SCROLL_PART_NONE;
}
else if (screenCoords.x >= iteratorRight + rightOffset - SCROLLBAR_WIDTH)
{
*output_scroll_area = SCROLL_PART_HSCROLLBAR_RIGHT;
}
else if (screenCoords.x < (widget->left + w.windowPos.x + scroll.h_thumb_left))
{
*output_scroll_area = SCROLL_PART_HSCROLLBAR_LEFT_TROUGH;
}
else if (screenCoords.x > (widget->left + w.windowPos.x + scroll.h_thumb_right))
{
*output_scroll_area = SCROLL_PART_HSCROLLBAR_RIGHT_TROUGH;
}
else
{
*output_scroll_area = SCROLL_PART_HSCROLLBAR_THUMB;
}
}
else if ((scroll.flags & VSCROLLBAR_VISIBLE) && (screenCoords.x >= w.windowPos.x + widget->right - (SCROLLBAR_WIDTH + 1)))
{
// vertical scrollbar
int32_t bottomOffset = 0;
int32_t iteratorTop = widget->top + w.windowPos.y + SCROLLBAR_WIDTH;
int32_t iteratorBottom = widget->bottom + w.windowPos.y;
if (scroll.flags & HSCROLLBAR_VISIBLE)
{
bottomOffset = (SCROLLBAR_WIDTH + 1);
}
if (screenCoords.y <= iteratorTop)
{
*output_scroll_area = SCROLL_PART_VSCROLLBAR_TOP;
}
else if (screenCoords.y >= (iteratorBottom - bottomOffset))
{
*output_scroll_area = SCROLL_PART_NONE;
}
else if (screenCoords.y >= (iteratorBottom - bottomOffset - SCROLLBAR_WIDTH))
{
*output_scroll_area = SCROLL_PART_VSCROLLBAR_BOTTOM;
}
else if (screenCoords.y < (widget->top + w.windowPos.y + scroll.v_thumb_top))
{
*output_scroll_area = SCROLL_PART_VSCROLLBAR_TOP_TROUGH;
}
else if (screenCoords.y > (widget->top + w.windowPos.y + scroll.v_thumb_bottom))
{
*output_scroll_area = SCROLL_PART_VSCROLLBAR_BOTTOM_TROUGH;
}
else
{
*output_scroll_area = SCROLL_PART_VSCROLLBAR_THUMB;
}
}
else
{
// view
*output_scroll_area = SCROLL_PART_VIEW;
retScreenCoords.x = screenCoords.x - widget->left;
retScreenCoords.y = screenCoords.y - widget->top;
retScreenCoords -= w.windowPos;
if (retScreenCoords.x <= 0 || retScreenCoords.y <= 0)
{
*output_scroll_area = SCROLL_PART_NONE;
}
else
{
retScreenCoords.x += scroll.h_left - 1;
retScreenCoords.y += scroll.v_top - 1;
}
}
}
rct_widget* GetWidgetByIndex(const rct_window& w, WidgetIndex widgetIndex)
{
// Make sure we don't go out of bounds if we are given a bad widget index
WidgetIndex index = 0;
for (auto* widget = w.widgets; widget->type != WindowWidgetType::Last; widget++)
{
if (index == widgetIndex)
{
return widget;
}
index++;
}
log_error("Widget index %i out of bounds for window class %u", widgetIndex, w.classification);
return nullptr;
}
static void SafeSetWidgetFlag(rct_window& w, WidgetIndex widgetIndex, WidgetFlags mask, bool value)
{
rct_widget* widget = GetWidgetByIndex(w, widgetIndex);
if (widget == nullptr)
{
return;
}
if (value)
widget->flags |= mask;
else
widget->flags &= ~mask;
}
void WidgetSetEnabled(rct_window& w, WidgetIndex widgetIndex, bool enabled)
{
WidgetSetDisabled(w, widgetIndex, !enabled);
}
void WidgetSetDisabled(rct_window& w, WidgetIndex widgetIndex, bool value)
{
SafeSetWidgetFlag(w, widgetIndex, WIDGET_FLAGS::IS_DISABLED, value);
if (value)
{
w.disabled_widgets |= (1uLL << widgetIndex);
}
else
{
w.disabled_widgets &= ~(1uLL << widgetIndex);
}
}
void WidgetSetHoldable(rct_window& w, WidgetIndex widgetIndex, bool value)
{
SafeSetWidgetFlag(w, widgetIndex, WIDGET_FLAGS::IS_HOLDABLE, value);
if (value)
{
w.hold_down_widgets |= (1uLL << widgetIndex);
}
else
{
w.hold_down_widgets &= ~(1uLL << widgetIndex);
}
}
void WidgetSetVisible(rct_window& w, WidgetIndex widgetIndex, bool value)
{
SafeSetWidgetFlag(w, widgetIndex, WIDGET_FLAGS::IS_HIDDEN, !value);
}
void WidgetSetPressed(rct_window& w, WidgetIndex widgetIndex, bool value)
{
SafeSetWidgetFlag(w, widgetIndex, WIDGET_FLAGS::IS_PRESSED, value);
if (value)
w.pressed_widgets |= (1uLL << widgetIndex);
else
w.pressed_widgets &= ~(1uLL << widgetIndex);
}
void WidgetSetCheckboxValue(rct_window& w, WidgetIndex widgetIndex, bool value)
{
WidgetSetPressed(w, widgetIndex, value);
}
static void WidgetTextBoxDraw(rct_drawpixelinfo* dpi, rct_window& w, WidgetIndex widgetIndex)
{
int32_t no_lines = 0;
char wrapped_string[TEXT_INPUT_SIZE];
// Get the widget
const auto& widget = w.widgets[widgetIndex];
// Resolve the absolute ltrb
ScreenCoordsXY topLeft{ w.windowPos + ScreenCoordsXY{ widget.left, widget.top } };
ScreenCoordsXY bottomRight{ w.windowPos + ScreenCoordsXY{ widget.right, widget.bottom } };
// Get the colour
uint8_t colour = w.colours[widget.colour];
bool active = w.classification == gCurrentTextBox.window.classification && w.number == gCurrentTextBox.window.number
&& widgetIndex == gCurrentTextBox.widget_index;
// gfx_fill_rect_inset(dpi, l, t, r, b, colour, 0x20 | (!active ? 0x40 : 0x00));
gfx_fill_rect_inset(dpi, { topLeft, bottomRight }, colour, INSET_RECT_F_60);
// Figure out where the text should be positioned vertically.
topLeft.y = w.windowPos.y + widget.textTop();
if (!active || gTextInput == nullptr)
{
if (widget.text != 0)
{
safe_strcpy(wrapped_string, widget.string, 512);
gfx_wrap_string(wrapped_string, bottomRight.x - topLeft.x - 5, FontStyle::Medium, &no_lines);
gfx_draw_string_no_formatting(
dpi, { topLeft.x + 2, topLeft.y }, wrapped_string, { w.colours[1], FontStyle::Medium });
}
return;
}
safe_strcpy(wrapped_string, gTextBoxInput, TEXT_INPUT_SIZE);
// String length needs to add 12 either side of box
// +13 for cursor when max length.
gfx_wrap_string(wrapped_string, bottomRight.x - topLeft.x - 5 - 6, FontStyle::Medium, &no_lines);
gfx_draw_string_no_formatting(dpi, { topLeft.x + 2, topLeft.y }, wrapped_string, { w.colours[1], FontStyle::Medium });
size_t string_length = get_string_size(wrapped_string) - 1;
// Make a copy of the string for measuring the width.
char temp_string[TEXT_INPUT_SIZE] = { 0 };
std::memcpy(temp_string, wrapped_string, std::min(string_length, gTextInput->SelectionStart));
int32_t cur_x = topLeft.x + gfx_get_string_width_no_formatting(temp_string, FontStyle::Medium) + 3;
int32_t width = 6;
if (static_cast<uint32_t>(gTextInput->SelectionStart) < strlen(gTextBoxInput))
{
// Make a new 1 character wide string for measuring the width
// of the character that the cursor is under.
temp_string[1] = '\0';
temp_string[0] = gTextBoxInput[gTextInput->SelectionStart];
width = std::max(gfx_get_string_width_no_formatting(temp_string, FontStyle::Medium) - 2, 4);
}
if (gTextBoxFrameNo <= 15)
{
colour = ColourMapA[w.colours[1]].mid_light;
auto y = topLeft.y + (widget.height() - 1);
gfx_fill_rect(dpi, { { cur_x, y }, { cur_x + width, y } }, colour + 5);
}
}
ImageId GetColourButtonImage(colour_t colour)
{
return ImageId(SPR_PALETTE_BTN, colour).WithBlended(true);
}