mirror of https://github.com/OpenRCT2/OpenRCT2.git
1202 lines
39 KiB
C++
1202 lines
39 KiB
C++
/*****************************************************************************
|
|
* Copyright (c) 2014-2024 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/config/Config.h>
|
|
#include <openrct2/drawing/Drawing.h>
|
|
#include <openrct2/localisation/Formatter.h>
|
|
#include <openrct2/localisation/Formatting.h>
|
|
#include <openrct2/localisation/Localisation.h>
|
|
#include <openrct2/sprites.h>
|
|
#include <openrct2/util/Util.h>
|
|
|
|
static void WidgetFrameDraw(DrawPixelInfo& dpi, WindowBase& w, WidgetIndex widgetIndex);
|
|
static void WidgetResizeDraw(DrawPixelInfo& dpi, WindowBase& w, WidgetIndex widgetIndex);
|
|
static void WidgetButtonDraw(DrawPixelInfo& dpi, WindowBase& w, WidgetIndex widgetIndex);
|
|
static void WidgetTabDraw(DrawPixelInfo& dpi, WindowBase& w, WidgetIndex widgetIndex);
|
|
static void WidgetFlatButtonDraw(DrawPixelInfo& dpi, WindowBase& w, WidgetIndex widgetIndex);
|
|
static void WidgetTextButton(DrawPixelInfo& dpi, WindowBase& w, WidgetIndex widgetIndex);
|
|
static void WidgetTextCentred(DrawPixelInfo& dpi, WindowBase& w, WidgetIndex widgetIndex);
|
|
static void WidgetText(DrawPixelInfo& dpi, WindowBase& w, WidgetIndex widgetIndex);
|
|
static void WidgetTextInset(DrawPixelInfo& dpi, WindowBase& w, WidgetIndex widgetIndex);
|
|
static void WidgetTextBoxDraw(DrawPixelInfo& dpi, WindowBase& w, WidgetIndex widgetIndex);
|
|
static void WidgetGroupboxDraw(DrawPixelInfo& dpi, WindowBase& w, WidgetIndex widgetIndex);
|
|
static void WidgetCaptionDraw(DrawPixelInfo& dpi, WindowBase& w, WidgetIndex widgetIndex);
|
|
static void WidgetCheckboxDraw(DrawPixelInfo& dpi, WindowBase& w, WidgetIndex widgetIndex);
|
|
static void WidgetCloseboxDraw(DrawPixelInfo& dpi, WindowBase& w, WidgetIndex widgetIndex);
|
|
static void WidgetScrollDraw(DrawPixelInfo& dpi, WindowBase& w, WidgetIndex widgetIndex);
|
|
static void WidgetHScrollbarDraw(
|
|
DrawPixelInfo& dpi, const ScrollBar& scroll, int32_t l, int32_t t, int32_t r, int32_t b, int32_t colour);
|
|
static void WidgetVScrollbarDraw(
|
|
DrawPixelInfo& dpi, const ScrollBar& scroll, int32_t l, int32_t t, int32_t r, int32_t b, int32_t colour);
|
|
static void WidgetDrawImage(DrawPixelInfo& dpi, WindowBase& w, WidgetIndex widgetIndex);
|
|
|
|
/**
|
|
*
|
|
* rct2: 0x006EB2A8
|
|
*/
|
|
void WidgetDraw(DrawPixelInfo& dpi, WindowBase& 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(DrawPixelInfo& dpi, WindowBase& 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
|
|
GfxFillRectInset(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 };
|
|
GfxDrawSprite(dpi, ImageId(SPR_RESIZE, colour & 0x7F), leftTop);
|
|
}
|
|
|
|
/**
|
|
*
|
|
* rct2: 0x006EB765
|
|
*/
|
|
static void WidgetResizeDraw(DrawPixelInfo& dpi, WindowBase& 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
|
|
GfxFillRectInset(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 };
|
|
GfxDrawSprite(dpi, ImageId(SPR_RESIZE, colour & 0x7F), leftTop);
|
|
}
|
|
|
|
/**
|
|
*
|
|
* rct2: 0x006EB8E5
|
|
*/
|
|
static void WidgetButtonDraw(DrawPixelInfo& dpi, WindowBase& 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.ToUInt32()) == -2)
|
|
{
|
|
// Draw border with no fill
|
|
GfxFillRectInset(dpi, rect, colour, press | INSET_RECT_FLAG_FILL_NONE);
|
|
return;
|
|
}
|
|
|
|
// Draw the border with fill
|
|
GfxFillRectInset(dpi, rect, colour, press);
|
|
|
|
WidgetDrawImage(dpi, w, widgetIndex);
|
|
}
|
|
|
|
/**
|
|
*
|
|
* rct2: 0x006EB806
|
|
*/
|
|
static void WidgetTabDraw(DrawPixelInfo& dpi, WindowBase& w, WidgetIndex widgetIndex)
|
|
{
|
|
// Get the widget
|
|
auto& widget = w.widgets[widgetIndex];
|
|
|
|
if (widget.type != WindowWidgetType::Tab && widget.image.GetIndex() == ImageIndexUndefined)
|
|
return;
|
|
|
|
if (widget.type == WindowWidgetType::Tab)
|
|
{
|
|
if (WidgetIsDisabled(w, widgetIndex))
|
|
return;
|
|
|
|
if (widget.image.GetIndex() == ImageIndexUndefined)
|
|
{
|
|
// Set standard tab sprite to use.
|
|
widget.image = ImageId(SPR_TAB, FilterPaletteID::PaletteNull);
|
|
}
|
|
}
|
|
|
|
// 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;
|
|
const auto newIndex = widget.image.GetIndex() + 2;
|
|
auto image = widget.image.WithIndex(newIndex).WithPrimary(colour);
|
|
|
|
// Draw disabled image
|
|
GfxDrawSprite(dpi, image, leftTop);
|
|
}
|
|
|
|
/**
|
|
*
|
|
* rct2: 0x006EB861
|
|
*/
|
|
static void WidgetFlatButtonDraw(DrawPixelInfo& dpi, WindowBase& 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.ToUInt32()) == -2)
|
|
{
|
|
// Draw border with no fill
|
|
GfxFillRectInset(dpi, rect, colour, INSET_RECT_FLAG_BORDER_INSET | INSET_RECT_FLAG_FILL_NONE);
|
|
return;
|
|
}
|
|
|
|
// Draw the border with fill
|
|
GfxFillRectInset(dpi, rect, colour, INSET_RECT_FLAG_BORDER_INSET);
|
|
}
|
|
|
|
// Draw image
|
|
WidgetDrawImage(dpi, w, widgetIndex);
|
|
}
|
|
|
|
/**
|
|
*
|
|
* rct2: 0x006EBBEB
|
|
*/
|
|
static void WidgetTextButton(DrawPixelInfo& dpi, WindowBase& 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;
|
|
GfxFillRectInset(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(DrawPixelInfo& dpi, WindowBase& 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(DrawPixelInfo& dpi, WindowBase& 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(DrawPixelInfo& dpi, WindowBase& 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];
|
|
|
|
GfxFillRectInset(dpi, rect, colour, INSET_RECT_F_60);
|
|
WidgetText(dpi, w, widgetIndex);
|
|
}
|
|
|
|
static std::pair<StringId, void*> WidgetGetStringidAndArgs(const 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(DrawPixelInfo& dpi, WindowBase& 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 };
|
|
OpenRCT2::FormatStringLegacy(buffer, sizeof(buffer), stringId, formatArgs);
|
|
auto ft = Formatter();
|
|
ft.Add<utf8*>(buffer);
|
|
DrawTextBasic(dpi, { l, t }, STR_STRING, ft, { colour });
|
|
textRight = l + GfxGetStringWidth(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
|
|
GfxFillRect(dpi, { { l, t }, { l + 4, t } }, ColourMapA[colour].mid_dark);
|
|
GfxFillRect(dpi, { { l + 1, t + 1 }, { l + 4, t + 1 } }, ColourMapA[colour].lighter);
|
|
|
|
// Border right of text
|
|
GfxFillRect(dpi, { { textRight, t }, { r - 1, t } }, ColourMapA[colour].mid_dark);
|
|
GfxFillRect(dpi, { { textRight, t + 1 }, { r - 2, t + 1 } }, ColourMapA[colour].lighter);
|
|
|
|
// Border right
|
|
GfxFillRect(dpi, { { r - 1, t + 1 }, { r - 1, b - 1 } }, ColourMapA[colour].mid_dark);
|
|
GfxFillRect(dpi, { { r, t }, { r, b } }, ColourMapA[colour].lighter);
|
|
|
|
// Border bottom
|
|
GfxFillRect(dpi, { { l, b - 1 }, { r - 2, b - 1 } }, ColourMapA[colour].mid_dark);
|
|
GfxFillRect(dpi, { { l, b }, { r - 1, b } }, ColourMapA[colour].lighter);
|
|
|
|
// Border left
|
|
GfxFillRect(dpi, { { l, t + 1 }, { l, b - 2 } }, ColourMapA[colour].mid_dark);
|
|
GfxFillRect(dpi, { { l + 1, t + 2 }, { l + 1, b - 2 } }, ColourMapA[colour].lighter);
|
|
}
|
|
|
|
/**
|
|
*
|
|
* rct2: 0x006EB2F9
|
|
*/
|
|
static void WidgetCaptionDraw(DrawPixelInfo& dpi, WindowBase& 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;
|
|
|
|
GfxFillRectInset(dpi, { topLeft, bottomRight }, colour, press);
|
|
|
|
// Black caption bars look slightly green, this fixes that
|
|
if (colour == 0)
|
|
GfxFillRect(
|
|
dpi, { { topLeft + ScreenCoordsXY{ 1, 1 } }, { bottomRight - ScreenCoordsXY{ 1, 1 } } }, ColourMapA[colour].dark);
|
|
else
|
|
GfxFilterRect(
|
|
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 -= CloseButtonWidth;
|
|
if ((widget + 2)->type == WindowWidgetType::CloseBox)
|
|
width -= CloseButtonWidth;
|
|
}
|
|
topLeft.x += width / 2;
|
|
if (gConfigInterface.WindowButtonsOnTheLeft)
|
|
topLeft.x += CloseButtonWidth;
|
|
|
|
DrawTextEllipsised(
|
|
dpi, topLeft, width, widget->text, Formatter::Common(),
|
|
{ COLOUR_WHITE | static_cast<uint8_t>(COLOUR_FLAG_OUTLINE), TextAlignment::CENTRE });
|
|
}
|
|
|
|
/**
|
|
*
|
|
* rct2: 0x006EBB85
|
|
*/
|
|
static void WidgetCloseboxDraw(DrawPixelInfo& dpi, WindowBase& 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
|
|
GfxFillRectInset(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(DrawPixelInfo& dpi, WindowBase& 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
|
|
GfxFillRectInset(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))
|
|
{
|
|
GfxDrawString(
|
|
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);
|
|
GfxDrawStringLeftCentred(dpi, stringId, formatArgs, colour, { midLeft + ScreenCoordsXY{ 14, 0 } });
|
|
}
|
|
|
|
/**
|
|
*
|
|
* rct2: 0x006EBD96
|
|
*/
|
|
static void WidgetScrollDraw(DrawPixelInfo& dpi, WindowBase& w, WidgetIndex widgetIndex)
|
|
{
|
|
// Get the widget
|
|
int32_t scrollIndex = WindowGetScrollDataIndex(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
|
|
GfxFillRectInset(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
|
|
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)
|
|
WindowEventScrollDrawCall(&w, scroll_dpi, scrollIndex);
|
|
}
|
|
|
|
static void WidgetHScrollbarDraw(
|
|
DrawPixelInfo& dpi, const ScrollBar& scroll, int32_t l, int32_t t, int32_t r, int32_t b, int32_t colour)
|
|
{
|
|
colour &= 0x7F;
|
|
// Trough
|
|
GfxFillRect(dpi, { { l + SCROLLBAR_WIDTH, t }, { r - SCROLLBAR_WIDTH, b } }, ColourMapA[colour].lighter);
|
|
GfxFillRect(dpi, { { l + SCROLLBAR_WIDTH, t }, { r - SCROLLBAR_WIDTH, b } }, 0x1000000 | ColourMapA[colour].mid_dark);
|
|
GfxFillRect(dpi, { { l + SCROLLBAR_WIDTH, t + 2 }, { r - SCROLLBAR_WIDTH, t + 2 } }, ColourMapA[colour].mid_dark);
|
|
GfxFillRect(dpi, { { l + SCROLLBAR_WIDTH, t + 3 }, { r - SCROLLBAR_WIDTH, t + 3 } }, ColourMapA[colour].lighter);
|
|
GfxFillRect(dpi, { { l + SCROLLBAR_WIDTH, t + 7 }, { r - SCROLLBAR_WIDTH, t + 7 } }, ColourMapA[colour].mid_dark);
|
|
GfxFillRect(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;
|
|
|
|
GfxFillRectInset(dpi, { { l, t }, { l + (SCROLLBAR_WIDTH - 1), b } }, colour, flags);
|
|
GfxDrawString(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;
|
|
|
|
GfxFillRectInset(dpi, { { left, t }, { right, b } }, colour, flags);
|
|
}
|
|
|
|
// Right button
|
|
{
|
|
uint8_t flags = (scroll.flags & HSCROLLBAR_RIGHT_PRESSED) ? INSET_RECT_FLAG_BORDER_INSET : 0;
|
|
|
|
GfxFillRectInset(dpi, { { r - (SCROLLBAR_WIDTH - 1), t }, { r, b } }, colour, flags);
|
|
GfxDrawString(dpi, { r - 6, t }, static_cast<const char*>(BlackRightArrowString), {});
|
|
}
|
|
}
|
|
|
|
static void WidgetVScrollbarDraw(
|
|
DrawPixelInfo& dpi, const ScrollBar& scroll, int32_t l, int32_t t, int32_t r, int32_t b, int32_t colour)
|
|
{
|
|
colour &= 0x7F;
|
|
// Trough
|
|
GfxFillRect(dpi, { { l, t + SCROLLBAR_WIDTH }, { r, b - SCROLLBAR_WIDTH } }, ColourMapA[colour].lighter);
|
|
GfxFillRect(dpi, { { l, t + SCROLLBAR_WIDTH }, { r, b - SCROLLBAR_WIDTH } }, 0x1000000 | ColourMapA[colour].mid_dark);
|
|
GfxFillRect(dpi, { { l + 2, t + SCROLLBAR_WIDTH }, { l + 2, b - SCROLLBAR_WIDTH } }, ColourMapA[colour].mid_dark);
|
|
GfxFillRect(dpi, { { l + 3, t + SCROLLBAR_WIDTH }, { l + 3, b - SCROLLBAR_WIDTH } }, ColourMapA[colour].lighter);
|
|
GfxFillRect(dpi, { { l + 7, t + SCROLLBAR_WIDTH }, { l + 7, b - SCROLLBAR_WIDTH } }, ColourMapA[colour].mid_dark);
|
|
GfxFillRect(dpi, { { l + 8, t + SCROLLBAR_WIDTH }, { l + 8, b - SCROLLBAR_WIDTH } }, ColourMapA[colour].lighter);
|
|
|
|
// Up button
|
|
GfxFillRectInset(
|
|
dpi, { { l, t }, { r, t + (SCROLLBAR_WIDTH - 1) } }, colour,
|
|
((scroll.flags & VSCROLLBAR_UP_PRESSED) ? INSET_RECT_FLAG_BORDER_INSET : 0));
|
|
GfxDrawString(dpi, { l + 1, t - 1 }, static_cast<const char*>(BlackUpArrowString), {});
|
|
|
|
// Thumb
|
|
GfxFillRectInset(
|
|
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
|
|
GfxFillRectInset(
|
|
dpi, { { l, b - (SCROLLBAR_WIDTH - 1) }, { r, b } }, colour,
|
|
((scroll.flags & VSCROLLBAR_DOWN_PRESSED) ? INSET_RECT_FLAG_BORDER_INSET : 0));
|
|
GfxDrawString(dpi, { l + 1, b - (SCROLLBAR_WIDTH - 1) }, static_cast<const char*>(BlackDownArrowString), {});
|
|
}
|
|
|
|
/**
|
|
*
|
|
* rct2: 0x006EB951
|
|
*/
|
|
static void WidgetDrawImage(DrawPixelInfo& dpi, WindowBase& w, WidgetIndex widgetIndex)
|
|
{
|
|
// Get the widget
|
|
const auto& widget = w.widgets[widgetIndex];
|
|
|
|
// Get the image
|
|
if (static_cast<int32_t>(widget.image.ToUInt32()) == SPR_NONE)
|
|
return;
|
|
auto image = 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;
|
|
GfxDrawSpriteSolid(&dpi, image, screenCoords + ScreenCoordsXY{ 1, 1 }, colour);
|
|
|
|
// Draw greyed out (dark)
|
|
colour = w.colours[widget.colour];
|
|
colour = ColourMapA[NOT_TRANSLUCENT(colour)].mid_light;
|
|
GfxDrawSpriteSolid(&dpi, image, screenCoords, colour);
|
|
}
|
|
else
|
|
{
|
|
if (image.HasSecondary())
|
|
{
|
|
// ?
|
|
}
|
|
|
|
if (image.IsBlended())
|
|
image = image.WithBlended(false);
|
|
else
|
|
image = image.WithPrimary(colour);
|
|
|
|
GfxDrawSprite(dpi, image, screenCoords);
|
|
}
|
|
}
|
|
|
|
bool WidgetIsDisabled(const WindowBase& 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 WindowBase& 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 WindowBase& w, WidgetIndex widgetIndex)
|
|
{
|
|
return w.widgets[widgetIndex].IsVisible();
|
|
}
|
|
|
|
bool WidgetIsPressed(const WindowBase& 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 (InputGetState() == InputState::WidgetPressed || InputGetState() == InputState::DropdownActive)
|
|
{
|
|
if (!(InputTestFlag(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 WindowBase& 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 WindowBase& w, WidgetIndex widgetIndex)
|
|
{
|
|
if (!(InputTestFlag(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(
|
|
WindowBase& w, const Widget* widget, const ScreenCoordsXY& screenCoords, ScreenCoordsXY& retScreenCoords,
|
|
int32_t* output_scroll_area, int32_t* scroll_id)
|
|
{
|
|
*scroll_id = 0;
|
|
for (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;
|
|
}
|
|
}
|
|
}
|
|
|
|
Widget* GetWidgetByIndex(const WindowBase& 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(WindowBase& w, WidgetIndex widgetIndex, WidgetFlags mask, bool value)
|
|
{
|
|
Widget* widget = GetWidgetByIndex(w, widgetIndex);
|
|
if (widget == nullptr)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (value)
|
|
widget->flags |= mask;
|
|
else
|
|
widget->flags &= ~mask;
|
|
}
|
|
|
|
void WidgetSetEnabled(WindowBase& w, WidgetIndex widgetIndex, bool enabled)
|
|
{
|
|
WidgetSetDisabled(w, widgetIndex, !enabled);
|
|
}
|
|
|
|
void WidgetSetDisabled(WindowBase& 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(WindowBase& 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(WindowBase& w, WidgetIndex widgetIndex, bool value)
|
|
{
|
|
SafeSetWidgetFlag(w, widgetIndex, WIDGET_FLAGS::IS_HIDDEN, !value);
|
|
}
|
|
|
|
void WidgetSetPressed(WindowBase& 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(WindowBase& w, WidgetIndex widgetIndex, bool value)
|
|
{
|
|
WidgetSetPressed(w, widgetIndex, value);
|
|
}
|
|
|
|
static void WidgetTextBoxDraw(DrawPixelInfo& dpi, WindowBase& w, WidgetIndex widgetIndex)
|
|
{
|
|
// 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;
|
|
|
|
// GfxFillRectInset(dpi, l, t, r, b, colour, 0x20 | (!active ? 0x40 : 0x00));
|
|
GfxFillRectInset(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)
|
|
{
|
|
u8string wrappedString;
|
|
GfxWrapString(widget.string, bottomRight.x - topLeft.x - 5, FontStyle::Medium, &wrappedString, nullptr);
|
|
DrawText(dpi, { topLeft.x + 2, topLeft.y }, { w.colours[1] }, wrappedString.c_str(), true);
|
|
}
|
|
return;
|
|
}
|
|
|
|
// String length needs to add 12 either side of box
|
|
// +13 for cursor when max length.
|
|
u8string wrappedString;
|
|
GfxWrapString(gTextBoxInput, bottomRight.x - topLeft.x - 5 - 6, FontStyle::Medium, &wrappedString, nullptr);
|
|
|
|
DrawText(dpi, { topLeft.x + 2, topLeft.y }, { w.colours[1] }, wrappedString.c_str(), true);
|
|
|
|
// Make a trimmed view of the string for measuring the width.
|
|
int32_t curX = topLeft.x
|
|
+ GfxGetStringWidthNoFormatting(
|
|
u8string_view{ wrappedString.c_str(), std::min(wrappedString.length(), gTextInput->SelectionStart) },
|
|
FontStyle::Medium)
|
|
+ 3;
|
|
|
|
int32_t width = 6;
|
|
if (static_cast<uint32_t>(gTextInput->SelectionStart) < gTextBoxInput.size())
|
|
{
|
|
// Make a new 1 character wide string for measuring the width
|
|
// of the character that the cursor is under.
|
|
width = std::max(
|
|
GfxGetStringWidthNoFormatting(u8string{ gTextBoxInput[gTextInput->SelectionStart] }, FontStyle::Medium) - 2, 4);
|
|
}
|
|
|
|
if (gTextBoxFrameNo <= 15)
|
|
{
|
|
colour = ColourMapA[w.colours[1]].mid_light;
|
|
auto y = topLeft.y + (widget.height() - 1);
|
|
GfxFillRect(dpi, { { curX, y }, { curX + width, y } }, colour + 5);
|
|
}
|
|
}
|
|
|
|
ImageId GetColourButtonImage(colour_t colour)
|
|
{
|
|
if (colour == COLOUR_INVISIBLE)
|
|
{
|
|
return ImageId(SPR_G2_ICON_PALETTE_INVISIBLE, colour).WithBlended(false);
|
|
}
|
|
else
|
|
{
|
|
return ImageId(SPR_PALETTE_BTN, colour).WithBlended(true);
|
|
}
|
|
}
|