1
0
mirror of https://github.com/OpenRCT2/OpenRCT2 synced 2026-01-04 13:42:55 +01:00

Merge pull request #11685 from IntelOrca/plugin/listview-widget

[Plugin] Implement the list view widget
This commit is contained in:
Ted John
2020-05-10 22:36:44 +01:00
committed by GitHub
13 changed files with 1416 additions and 48 deletions

View File

@@ -65,7 +65,7 @@
"_DEBUG",
"UNICODE",
"_UNICODE",
"__ENABLE_SCRIPTING__"
"ENABLE_SCRIPTING"
],
"intelliSenseMode": "msvc-x64",
"browse": {

View File

@@ -50,6 +50,7 @@
4C358E5221C445F700ADE6BC /* ReplayManager.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4C358E5021C445F700ADE6BC /* ReplayManager.cpp */; };
4C3B4236205914F7000C5BB7 /* InGameConsole.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4C3B4234205914F7000C5BB7 /* InGameConsole.cpp */; };
4C724B2221F0AD790012ADD0 /* BenchSpriteSort.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4C724B2121F0AD790012ADD0 /* BenchSpriteSort.cpp */; };
4C81F7E124672C4D000E61BF /* CustomListView.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4C81F7DF24672C4D000E61BF /* CustomListView.cpp */; };
4C8A6FF323EB5326001A8255 /* Http.cURL.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4C8A6FF223EB5326001A8255 /* Http.cURL.cpp */; };
4C93F1AD1F8CD9F000A9330D /* Input.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4C93F1AC1F8CD9F000A9330D /* Input.cpp */; };
4C93F1AF1F8CD9F600A9330D /* KeyboardShortcut.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4C93F1AE1F8CD9F600A9330D /* KeyboardShortcut.cpp */; };
@@ -838,6 +839,9 @@
4C7B54792010DF4C00A52E21 /* Shared.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Shared.cpp; sourceTree = "<group>"; };
4C7B547A2010DF4C00A52E21 /* Windows.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Windows.cpp; sourceTree = "<group>"; };
4C7B547E2010DFF700A52E21 /* Crash.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Crash.h; sourceTree = "<group>"; };
4C81F7DF24672C4D000E61BF /* CustomListView.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = CustomListView.cpp; path = scripting/CustomListView.cpp; sourceTree = "<group>"; };
4C81F7E024672C4D000E61BF /* CustomListView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CustomListView.h; path = scripting/CustomListView.h; sourceTree = "<group>"; };
4C81F7E224672C58000E61BF /* ScTileSelection.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = ScTileSelection.hpp; path = scripting/ScTileSelection.hpp; sourceTree = "<group>"; };
4C8667801EEFDCDF0024AAB8 /* RideGroupManager.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = RideGroupManager.cpp; sourceTree = "<group>"; };
4C8667811EEFDCDF0024AAB8 /* RideGroupManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RideGroupManager.h; sourceTree = "<group>"; };
4C8A6FF123EB5325001A8255 /* Http.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Http.h; sourceTree = "<group>"; };
@@ -1911,10 +1915,13 @@
4C25594D244A326100CE7E45 /* scripting */ = {
isa = PBXGroup;
children = (
4C81F7DF24672C4D000E61BF /* CustomListView.cpp */,
4C81F7E024672C4D000E61BF /* CustomListView.h */,
4C25594F244A328A00CE7E45 /* CustomMenu.cpp */,
4C255953244A328A00CE7E45 /* CustomMenu.h */,
4C255957244A328B00CE7E45 /* CustomWindow.cpp */,
4C25594E244A328A00CE7E45 /* CustomWindow.h */,
4C81F7E224672C58000E61BF /* ScTileSelection.hpp */,
4C255950244A328A00CE7E45 /* ScUi.hpp */,
4C255955244A328A00CE7E45 /* ScViewport.hpp */,
4C255951244A328A00CE7E45 /* ScWidget.hpp */,
@@ -4019,6 +4026,7 @@
C68878C020289B710084B384 /* ApplyPaletteShader.cpp in Sources */,
C666EE791F37ACB10061AA04 /* ServerStart.cpp in Sources */,
C61ADB231FBBCB8B0024F2EF /* GameBottomToolbar.cpp in Sources */,
4C81F7E124672C4D000E61BF /* CustomListView.cpp in Sources */,
6341F4E22400AA0F0052902B /* Drawing.Sprite.BMP.cpp in Sources */,
C666EE7E1F37ACB10061AA04 /* TitleOptions.cpp in Sources */,
F76C887A1EC5324E00FA49E2 /* AudioMixer.cpp in Sources */,

View File

@@ -1072,7 +1072,7 @@ declare global {
* Represents the type of a widget, e.g. button or label.
*/
type WidgetType =
"button" | "checkbox" | "dropdown" | "groupbox" | "label" | "spinner" | "viewport";
"button" | "checkbox" | "dropdown" | "groupbox" | "label" | "listview" | "spinner" | "viewport";
interface Widget {
type: WidgetType;
@@ -1113,6 +1113,42 @@ declare global {
onChange: (index: number) => void;
}
type SortOrder = "none" | "ascending" | "descending";
type ScrollbarType = "none" | "horizontal" | "vertical" | "both";
interface ListViewColumn {
canSort?: boolean;
sortOrder?: SortOrder;
header?: string;
headerTooltip?: string;
width?: number;
ratioWidth?: number;
minWidth?: number;
maxWidth?: number;
}
type ListViewItem = string[];
interface RowColumn {
row: number;
column: number;
}
interface ListView extends Widget {
scrollbars?: ScrollbarType;
isStriped?: boolean;
showColumnHeaders?: boolean;
columns?: ListViewColumn[];
items?: string[] | ListViewItem[];
selectedCell?: RowColumn;
readonly highlightedCell?: RowColumn;
canSelect?: boolean;
onHighlight: (item: number, column: number) => void;
onClick: (item: number, column: number) => void;
}
interface SpinnerWidget extends Widget {
text: string;
onDecrement: () => void;

View File

@@ -0,0 +1,683 @@
/*****************************************************************************
* Copyright (c) 2014-2020 OpenRCT2 developers
*
* For a complete list of all authors, please refer to contributors.md
* Interested in contributing? Visit https://github.com/OpenRCT2/OpenRCT2
*
* OpenRCT2 is licensed under the GNU General Public License version 3.
*****************************************************************************/
#ifdef ENABLE_SCRIPTING
# include "CustomListView.h"
# include "../interface/Widget.h"
# include "../interface/Window.h"
# include <numeric>
# include <openrct2/Context.h>
# include <openrct2/localisation/Localisation.h>
# include <openrct2/util/Util.h>
using namespace OpenRCT2::Scripting;
using namespace OpenRCT2::Ui::Windows;
namespace OpenRCT2::Scripting
{
template<> ColumnSortOrder FromDuk(const DukValue& d)
{
if (d.type() == DukValue::Type::STRING)
{
auto s = d.as_string();
if (s == "ascending")
return ColumnSortOrder::Ascending;
if (s == "descending")
return ColumnSortOrder::Descending;
}
return ColumnSortOrder::None;
}
template<> DukValue ToDuk(duk_context* ctx, const ColumnSortOrder& value)
{
switch (value)
{
case ColumnSortOrder::Ascending:
return ToDuk(ctx, "ascending");
case ColumnSortOrder::Descending:
return ToDuk(ctx, "descending");
default:
return ToDuk(ctx, "none");
}
}
template<> std::optional<int32_t> FromDuk(const DukValue& d)
{
if (d.type() == DukValue::Type::NUMBER)
{
return d.as_int();
}
return std::nullopt;
}
template<> ListViewColumn FromDuk(const DukValue& d)
{
ListViewColumn result;
result.CanSort = AsOrDefault(d["canSort"], false);
result.SortOrder = FromDuk<ColumnSortOrder>(d["sortOrder"]);
result.Header = AsOrDefault(d["header"], "");
result.HeaderTooltip = AsOrDefault(d["headerTooltip"], "");
result.MinWidth = FromDuk<std::optional<int32_t>>(d["minWidth"]);
result.MaxWidth = FromDuk<std::optional<int32_t>>(d["maxWidth"]);
result.RatioWidth = FromDuk<std::optional<int32_t>>(d["ratioWidth"]);
if (d["width"].type() == DukValue::Type::NUMBER)
{
result.MinWidth = d["width"].as_int();
result.MaxWidth = result.MinWidth;
result.RatioWidth = std::nullopt;
}
else if (!result.RatioWidth)
{
result.RatioWidth = 1;
}
return result;
}
template<> DukValue ToDuk(duk_context* ctx, const ListViewColumn& value)
{
DukObject obj(ctx);
obj.Set("canSort", value.CanSort);
obj.Set("sortOrder", ToDuk(ctx, value.SortOrder));
obj.Set("header", value.Header);
obj.Set("headerTooltip", value.HeaderTooltip);
obj.Set("minWidth", value.MinWidth);
obj.Set("maxWidth", value.MaxWidth);
obj.Set("ratioWidth", value.RatioWidth);
obj.Set("width", value.Width);
return obj.Take();
}
template<> ListViewItem FromDuk(const DukValue& d)
{
ListViewItem result;
if (d.type() == DukValue::Type::STRING)
{
result = ListViewItem(ProcessString(d));
}
else if (d.is_array())
{
std::vector<std::string> cells;
for (const auto& dukCell : d.as_array())
{
cells.push_back(ProcessString(dukCell));
}
result = ListViewItem(std::move(cells));
}
return result;
}
template<> std::vector<ListViewColumn> FromDuk(const DukValue& d)
{
std::vector<ListViewColumn> result;
if (d.is_array())
{
auto dukColumns = d.as_array();
for (const auto& dukColumn : dukColumns)
{
result.push_back(FromDuk<ListViewColumn>(dukColumn));
}
}
return result;
}
template<> std::vector<ListViewItem> FromDuk(const DukValue& d)
{
std::vector<ListViewItem> result;
if (d.is_array())
{
auto dukItems = d.as_array();
for (const auto& dukItem : dukItems)
{
result.push_back(FromDuk<ListViewItem>(dukItem));
}
}
return result;
}
template<> std::optional<RowColumn> FromDuk(const DukValue& d)
{
if (d.type() == DukValue::Type::OBJECT)
{
auto dukRow = d["row"];
auto dukColumn = d["column"];
if (dukRow.type() == DukValue::Type::NUMBER && dukColumn.type() == DukValue::Type::NUMBER)
{
return RowColumn(dukRow.as_int(), dukColumn.as_int());
}
}
return std::nullopt;
}
template<> DukValue ToDuk(duk_context* ctx, const RowColumn& value)
{
DukObject obj(ctx);
obj.Set("row", value.Row);
obj.Set("column", value.Column);
return obj.Take();
}
template<> ScrollbarType FromDuk(const DukValue& d)
{
auto value = AsOrDefault(d, "");
if (value == "horizontal")
return ScrollbarType::Horizontal;
if (value == "vertical")
return ScrollbarType::Vertical;
if (value == "both")
return ScrollbarType::Both;
return ScrollbarType::None;
}
template<> DukValue ToDuk(duk_context* ctx, const ScrollbarType& value)
{
switch (value)
{
default:
case ScrollbarType::None:
return ToDuk(ctx, "none");
case ScrollbarType::Horizontal:
return ToDuk(ctx, "horizontal");
case ScrollbarType::Vertical:
return ToDuk(ctx, "vertical");
case ScrollbarType::Both:
return ToDuk(ctx, "both");
}
}
} // namespace OpenRCT2::Scripting
CustomListView::CustomListView(rct_window* parent, size_t scrollIndex)
: ParentWindow(parent)
, ScrollIndex(scrollIndex)
{
}
ScrollbarType CustomListView::GetScrollbars() const
{
return Scrollbars;
}
void CustomListView::SetScrollbars(ScrollbarType value, bool initialising)
{
Scrollbars = value;
if (!initialising)
{
size_t scrollIndex = 0;
for (auto widget = ParentWindow->widgets; widget->type != WWT_LAST; widget++)
{
if (widget->type == WWT_SCROLL)
{
if (scrollIndex == ScrollIndex)
{
if (value == ScrollbarType::Horizontal)
widget->content = SCROLL_HORIZONTAL;
else if (value == ScrollbarType::Vertical)
widget->content = SCROLL_VERTICAL;
else if (value == ScrollbarType::Both)
widget->content = SCROLL_BOTH;
else
widget->content = 0;
}
scrollIndex++;
}
}
window_init_scroll_widgets(ParentWindow);
}
}
const std::vector<ListViewColumn>& CustomListView::GetColumns() const
{
return Columns;
}
void CustomListView::SetColumns(const std::vector<ListViewColumn>& columns, bool initialising)
{
SelectedCell = std::nullopt;
Columns = columns;
LastKnownSize = {};
SortItems(0, ColumnSortOrder::None);
if (!initialising)
window_init_scroll_widgets(ParentWindow);
}
const std::vector<ListViewItem>& CustomListView::CustomListView::GetItems() const
{
return Items;
}
void CustomListView::SetItems(const std::vector<ListViewItem>& items, bool initialising)
{
SelectedCell = std::nullopt;
Items = items;
SortItems(0, ColumnSortOrder::None);
if (!initialising)
window_update_scroll_widgets(ParentWindow);
}
void CustomListView::SetItems(std::vector<ListViewItem>&& items, bool initialising)
{
Items = items;
SortItems(0, ColumnSortOrder::None);
if (!initialising)
window_init_scroll_widgets(ParentWindow);
}
bool CustomListView::SortItem(size_t indexA, size_t indexB, int32_t column)
{
const auto& cellA = Items[indexA].Cells[column];
const auto& cellB = Items[indexB].Cells[column];
return strlogicalcmp(cellA.c_str(), cellB.c_str()) < 0;
}
void CustomListView::SortItems(int32_t column)
{
auto sortOrder = ColumnSortOrder::Ascending;
if (CurrentSortColumn == column)
{
if (CurrentSortOrder == ColumnSortOrder::Ascending)
{
sortOrder = ColumnSortOrder::Descending;
}
else if (CurrentSortOrder == ColumnSortOrder::Descending)
{
sortOrder = ColumnSortOrder::None;
}
}
SortItems(column, sortOrder);
}
void CustomListView::SortItems(int32_t column, ColumnSortOrder order)
{
// Reset the sorted index map
SortedItems.resize(Items.size());
for (size_t i = 0; i < SortedItems.size(); i++)
{
SortedItems[i] = i;
}
if (order != ColumnSortOrder::None)
{
std::sort(
SortedItems.begin(), SortedItems.end(), [this, column](size_t a, size_t b) { return SortItem(a, b, column); });
if (order == ColumnSortOrder::Descending)
{
std::reverse(SortedItems.begin(), SortedItems.end());
}
}
CurrentSortOrder = order;
CurrentSortColumn = column;
Columns[column].SortOrder = order;
}
void CustomListView::Resize(const ScreenSize& size)
{
if (size == LastKnownSize)
return;
LastKnownSize = size;
// Calculate the total of all ratios
int32_t totalRatio = 0;
for (size_t c = 0; c < Columns.size(); c++)
{
auto& column = Columns[c];
if (column.RatioWidth)
{
totalRatio += *column.RatioWidth;
}
}
// Calculate column widths
bool hasHorizontalScroll = Scrollbars == ScrollbarType::Horizontal || Scrollbars == ScrollbarType::Both;
int32_t widthRemaining = size.width;
for (size_t c = 0; c < Columns.size(); c++)
{
auto& column = Columns[c];
auto isLastColumn = c == Columns.size() - 1;
if (!hasHorizontalScroll && isLastColumn)
{
column.Width = widthRemaining;
}
else
{
column.Width = 0;
if (column.RatioWidth && *column.RatioWidth > 0)
{
if (isLastColumn)
{
column.Width = widthRemaining;
}
else
{
column.Width = (size.width * *column.RatioWidth) / totalRatio;
}
}
if (column.MinWidth)
{
column.Width = std::max(column.Width, *column.MinWidth);
}
if (column.MaxWidth)
{
column.Width = std::min(column.Width, *column.MaxWidth);
}
}
widthRemaining = std::max(0, widthRemaining - column.Width);
}
window_init_scroll_widgets(ParentWindow);
}
ScreenSize CustomListView::GetSize()
{
LastHighlightedCell = HighlightedCell;
HighlightedCell = std::nullopt;
ColumnHeaderPressedCurrentState = false;
LastIsMouseDown = IsMouseDown;
IsMouseDown = false;
ScreenSize result;
if (Scrollbars == ScrollbarType::Horizontal || Scrollbars == ScrollbarType::Both)
{
result.width = std::accumulate(
Columns.begin(), Columns.end(), 0, [](int32_t acc, const ListViewColumn& column) { return acc + column.Width; });
// Fixes an off-by-one error that causes the scrollbar thumb to not fill when the widget is wide enough
result.width--;
}
if (Scrollbars == ScrollbarType::Vertical || Scrollbars == ScrollbarType::Both)
{
result.height = static_cast<int32_t>(Items.size() * LIST_ROW_HEIGHT);
}
return result;
}
void CustomListView::MouseOver(const ScreenCoordsXY& pos, bool isMouseDown)
{
auto hitResult = GetItemIndexAt(pos);
if (hitResult)
{
HighlightedCell = hitResult;
if (HighlightedCell != LastHighlightedCell)
{
if (hitResult->Row != HEADER_ROW && OnHighlight.context() != nullptr && OnHighlight.is_function())
{
auto ctx = OnHighlight.context();
duk_push_int(ctx, static_cast<int32_t>(HighlightedCell->Row));
auto dukRow = DukValue::take_from_stack(ctx, -1);
duk_push_int(ctx, static_cast<int32_t>(HighlightedCell->Column));
auto dukColumn = DukValue::take_from_stack(ctx, -1);
auto& scriptEngine = GetContext()->GetScriptEngine();
scriptEngine.ExecutePluginCall(Owner, OnHighlight, { dukRow, dukColumn }, false);
}
}
}
// Update the header currently held down
if (isMouseDown)
{
if (hitResult && hitResult->Row == HEADER_ROW)
{
ColumnHeaderPressedCurrentState = (hitResult->Column == ColumnHeaderPressed);
}
IsMouseDown = true;
}
else
{
if (LastIsMouseDown)
{
MouseUp(pos);
}
IsMouseDown = false;
}
}
void CustomListView::MouseDown(const ScreenCoordsXY& pos)
{
auto hitResult = GetItemIndexAt(pos);
if (hitResult)
{
if (hitResult->Row != HEADER_ROW && OnClick.context() != nullptr && OnClick.is_function())
{
if (CanSelect)
{
SelectedCell = hitResult;
}
auto ctx = OnClick.context();
duk_push_int(ctx, static_cast<int32_t>(hitResult->Row));
auto dukRow = DukValue::take_from_stack(ctx, -1);
duk_push_int(ctx, static_cast<int32_t>(hitResult->Column));
auto dukColumn = DukValue::take_from_stack(ctx, -1);
auto& scriptEngine = GetContext()->GetScriptEngine();
scriptEngine.ExecutePluginCall(Owner, OnClick, { dukRow, dukColumn }, false);
}
}
if (hitResult && hitResult->Row == HEADER_ROW)
{
if (Columns[hitResult->Column].CanSort)
{
ColumnHeaderPressed = hitResult->Column;
ColumnHeaderPressedCurrentState = true;
}
}
IsMouseDown = true;
}
void CustomListView::MouseUp(const ScreenCoordsXY& pos)
{
auto hitResult = GetItemIndexAt(pos);
if (hitResult && hitResult->Row == HEADER_ROW)
{
if (hitResult->Column == ColumnHeaderPressed)
{
SortItems(hitResult->Column);
}
}
ColumnHeaderPressed = std::nullopt;
ColumnHeaderPressedCurrentState = false;
}
void CustomListView::Paint(rct_window* w, rct_drawpixelinfo* dpi, const rct_scroll* scroll) const
{
auto paletteIndex = ColourMapA[w->colours[1]].mid_light;
gfx_fill_rect(dpi, dpi->x, dpi->y, dpi->x + dpi->width, dpi->y + dpi->height, paletteIndex);
int32_t y = ShowColumnHeaders ? LIST_ROW_HEIGHT + 1 : 0;
for (size_t i = 0; i < Items.size(); i++)
{
if (y > dpi->y + dpi->height)
{
// Past the scroll view area
break;
}
if (y + LIST_ROW_HEIGHT >= dpi->y)
{
const auto& itemIndex = static_cast<int32_t>(SortedItems[i]);
const auto& item = Items[itemIndex];
// Background colour
auto isStriped = IsStriped && (i & 1);
auto isHighlighted = (HighlightedCell && itemIndex == HighlightedCell->Row);
auto isSelected = (SelectedCell && itemIndex == SelectedCell->Row);
if (isSelected)
{
gfx_filter_rect(dpi, dpi->x, y, dpi->x + dpi->width, y + (LIST_ROW_HEIGHT - 1), PALETTE_DARKEN_2);
}
else if (isHighlighted)
{
gfx_filter_rect(dpi, dpi->x, y, dpi->x + dpi->width, y + (LIST_ROW_HEIGHT - 1), PALETTE_DARKEN_1);
}
else if (isStriped)
{
gfx_fill_rect(
dpi, dpi->x, y, dpi->x + dpi->width, y + (LIST_ROW_HEIGHT - 1),
ColourMapA[w->colours[1]].lighter | 0x1000000);
}
// Columns
if (Columns.size() == 0)
{
const auto& text = item.Cells[0];
if (!text.empty())
{
ScreenSize cellSize = { std::numeric_limits<int32_t>::max(), LIST_ROW_HEIGHT };
PaintCell(dpi, { 0, y }, cellSize, text.c_str(), isHighlighted);
}
}
else
{
int32_t x = 0;
for (size_t j = 0; j < Columns.size(); j++)
{
const auto& column = Columns[j];
if (item.Cells.size() > j)
{
const auto& text = item.Cells[j];
if (!text.empty())
{
ScreenSize cellSize = { column.Width, LIST_ROW_HEIGHT };
PaintCell(dpi, { x, y }, cellSize, text.c_str(), isHighlighted);
}
}
x += column.Width;
}
}
}
y += LIST_ROW_HEIGHT;
}
if (ShowColumnHeaders)
{
y = scroll->v_top;
auto bgColour = ColourMapA[w->colours[1]].mid_light;
gfx_fill_rect(dpi, dpi->x, y, dpi->x + dpi->width, y + 12, bgColour);
int32_t x = 0;
for (int32_t j = 0; j < static_cast<int32_t>(Columns.size()); j++)
{
const auto& column = Columns[j];
auto columnWidth = column.Width;
if (columnWidth != 0)
{
auto sortOrder = ColumnSortOrder::None;
if (CurrentSortColumn == j)
{
sortOrder = CurrentSortOrder;
}
bool isPressed = ColumnHeaderPressed == j && ColumnHeaderPressedCurrentState;
PaintHeading(w, dpi, { x, y }, { column.Width, LIST_ROW_HEIGHT }, column.Header, sortOrder, isPressed);
x += columnWidth;
}
}
}
}
void CustomListView::PaintHeading(
rct_window* w, rct_drawpixelinfo* dpi, const ScreenCoordsXY& pos, const ScreenSize& size, const std::string& text,
ColumnSortOrder sortOrder, bool isPressed) const
{
auto boxFlags = 0;
if (isPressed)
{
boxFlags = INSET_RECT_FLAG_BORDER_INSET;
}
gfx_fill_rect_inset(dpi, pos.x, pos.y, pos.x + size.width - 1, pos.y + size.height - 1, w->colours[1], boxFlags);
if (!text.empty())
{
PaintCell(dpi, pos, size, text.c_str(), false);
}
if (sortOrder == ColumnSortOrder::Ascending)
{
auto ft = Formatter::Common();
ft.Add<rct_string_id>(STR_UP);
gfx_draw_string_right(dpi, STR_BLACK_STRING, gCommonFormatArgs, COLOUR_BLACK, pos.x + size.width - 1, pos.y);
}
else if (sortOrder == ColumnSortOrder::Descending)
{
auto ft = Formatter::Common();
ft.Add<rct_string_id>(STR_DOWN);
gfx_draw_string_right(dpi, STR_BLACK_STRING, gCommonFormatArgs, COLOUR_BLACK, pos.x + size.width - 1, pos.y);
}
}
void CustomListView::PaintCell(
rct_drawpixelinfo* dpi, const ScreenCoordsXY& pos, const ScreenSize& size, const char* text, bool isHighlighted) const
{
rct_string_id stringId = isHighlighted ? STR_WINDOW_COLOUR_2_STRINGID : STR_BLACK_STRING;
auto ft = Formatter::Common();
ft.Add<rct_string_id>(STR_STRING);
ft.Add<const char*>(text);
gfx_draw_string_left_clipped(dpi, stringId, gCommonFormatArgs, COLOUR_BLACK, pos.x, pos.y, size.width);
}
std::optional<RowColumn> CustomListView::GetItemIndexAt(const ScreenCoordsXY& pos)
{
std::optional<RowColumn> result;
if (pos.x >= 0)
{
// Check if we pressed the header
if (ShowColumnHeaders && pos.y >= 0 && pos.y < LIST_ROW_HEIGHT)
{
result = RowColumn();
result->Row = HEADER_ROW;
}
else
{
// Check what row we pressed
int32_t firstY = ShowColumnHeaders ? LIST_ROW_HEIGHT + 1 : 0;
int32_t row = (pos.y - firstY) / LIST_ROW_HEIGHT;
if (row >= 0 && row < static_cast<int32_t>(Items.size()))
{
result = RowColumn();
result->Row = static_cast<int32_t>(SortedItems[row]);
}
}
// Check what column we pressed if there are any
if (result && Columns.size() > 0)
{
bool found = false;
int32_t x = 0;
for (size_t c = 0; c < Columns.size(); c++)
{
const auto& column = Columns[c];
x += column.Width;
if (column.Width != 0)
{
if (pos.x < x)
{
result->Column = static_cast<int32_t>(c);
found = true;
break;
}
}
}
if (!found)
{
// Past all columns
return std::nullopt;
}
}
}
return result;
}
#endif

View File

@@ -0,0 +1,170 @@
/*****************************************************************************
* Copyright (c) 2014-2020 OpenRCT2 developers
*
* For a complete list of all authors, please refer to contributors.md
* Interested in contributing? Visit https://github.com/OpenRCT2/OpenRCT2
*
* OpenRCT2 is licensed under the GNU General Public License version 3.
*****************************************************************************/
#pragma once
#ifdef ENABLE_SCRIPTING
# include <cstdint>
# include <memory>
# include <openrct2/scripting/Duktape.hpp>
# include <openrct2/scripting/ScriptEngine.h>
# include <optional>
# include <string>
# include <vector>
namespace OpenRCT2::Ui::Windows
{
using namespace OpenRCT2::Scripting;
enum class ScrollbarType
{
None,
Horizontal,
Vertical,
Both
};
enum class ColumnSortOrder
{
None,
Ascending,
Descending,
};
struct ListViewColumn
{
bool CanSort{};
ColumnSortOrder SortOrder;
std::string Header;
std::string HeaderTooltip;
std::optional<int32_t> RatioWidth{};
std::optional<int32_t> MinWidth{};
std::optional<int32_t> MaxWidth{};
int32_t Width{};
};
struct ListViewItem
{
std::vector<std::string> Cells;
ListViewItem() = default;
explicit ListViewItem(const std::string_view& text)
{
Cells.emplace_back(text);
}
explicit ListViewItem(std::vector<std::string>&& cells)
: Cells(cells)
{
}
};
struct RowColumn
{
int32_t Row{};
int32_t Column{};
RowColumn() = default;
RowColumn(int32_t row, int32_t column)
: Row(row)
, Column(column)
{
}
bool operator==(const RowColumn& other) const
{
return Row == other.Row && Column == other.Column;
}
bool operator!=(const RowColumn& other) const
{
return !(*this == other);
}
};
class CustomListView
{
private:
static constexpr int32_t HEADER_ROW = -1;
rct_window* ParentWindow{};
size_t ScrollIndex{};
std::vector<ListViewColumn> Columns;
std::vector<ListViewItem> Items;
ScrollbarType Scrollbars = ScrollbarType::Vertical;
public:
std::shared_ptr<Plugin> Owner;
std::vector<size_t> SortedItems;
std::optional<RowColumn> HighlightedCell;
std::optional<RowColumn> LastHighlightedCell;
std::optional<RowColumn> SelectedCell;
std::optional<int32_t> ColumnHeaderPressed;
bool ColumnHeaderPressedCurrentState{};
bool ShowColumnHeaders{};
bool IsStriped{};
ScreenSize LastKnownSize;
ColumnSortOrder CurrentSortOrder{};
int32_t CurrentSortColumn{};
bool LastIsMouseDown{};
bool IsMouseDown{};
bool CanSelect{};
DukValue OnClick;
DukValue OnHighlight;
CustomListView(rct_window* parent, size_t scrollIndex);
ScrollbarType GetScrollbars() const;
void SetScrollbars(ScrollbarType value, bool initialising = false);
const std::vector<ListViewColumn>& GetColumns() const;
void SetColumns(const std::vector<ListViewColumn>& columns, bool initialising = false);
const std::vector<ListViewItem>& GetItems() const;
void SetItems(const std::vector<ListViewItem>& items, bool initialising = false);
void SetItems(std::vector<ListViewItem>&& items, bool initialising = false);
bool SortItem(size_t indexA, size_t indexB, int32_t column);
void SortItems(int32_t column);
void SortItems(int32_t column, ColumnSortOrder order);
void Resize(const ScreenSize& size);
ScreenSize GetSize();
void MouseOver(const ScreenCoordsXY& pos, bool isMouseDown);
void MouseDown(const ScreenCoordsXY& pos);
void MouseUp(const ScreenCoordsXY& pos);
void Paint(rct_window* w, rct_drawpixelinfo* dpi, const rct_scroll* scroll) const;
private:
void PaintHeading(
rct_window* w, rct_drawpixelinfo* dpi, const ScreenCoordsXY& pos, const ScreenSize& size, const std::string& text,
ColumnSortOrder sortOrder, bool isPressed) const;
void PaintCell(
rct_drawpixelinfo* dpi, const ScreenCoordsXY& pos, const ScreenSize& size, const char* text,
bool isHighlighted) const;
std::optional<RowColumn> GetItemIndexAt(const ScreenCoordsXY& pos);
};
} // namespace OpenRCT2::Ui::Windows
class DukValue;
namespace OpenRCT2::Scripting
{
using namespace OpenRCT2::Ui::Windows;
template<> ColumnSortOrder FromDuk(const DukValue& d);
template<> std::optional<int32_t> FromDuk(const DukValue& d);
template<> ListViewColumn FromDuk(const DukValue& d);
template<> ListViewItem FromDuk(const DukValue& d);
template<> std::vector<ListViewColumn> FromDuk(const DukValue& d);
template<> std::vector<ListViewItem> FromDuk(const DukValue& d);
template<> std::optional<RowColumn> FromDuk(const DukValue& d);
template<> DukValue ToDuk(duk_context* ctx, const RowColumn& value);
template<> DukValue ToDuk(duk_context* ctx, const ListViewColumn& value);
template<> ScrollbarType FromDuk(const DukValue& d);
template<> DukValue ToDuk(duk_context* ctx, const ScrollbarType& value);
} // namespace OpenRCT2::Scripting
#endif

View File

@@ -10,6 +10,7 @@
#ifdef ENABLE_SCRIPTING
# include "../interface/Dropdown.h"
# include "CustomListView.h"
# include "ScUi.hpp"
# include "ScWindow.hpp"
@@ -54,8 +55,13 @@ namespace OpenRCT2::Ui::Windows
static void window_custom_resize(rct_window* w);
static void window_custom_dropdown(rct_window* w, rct_widgetindex widgetIndex, int32_t dropdownIndex);
static void window_custom_update(rct_window* w);
static void window_custom_scrollgetsize(rct_window* w, int32_t scrollIndex, int32_t* width, int32_t* height);
static void window_custom_scrollmousedrag(rct_window* w, int32_t scrollIndex, const ScreenCoordsXY& screenCoords);
static void window_custom_scrollmousedown(rct_window* w, int32_t scrollIndex, const ScreenCoordsXY& screenCoords);
static void window_custom_scrollmouseover(rct_window* w, int32_t scrollIndex, const ScreenCoordsXY& screenCoords);
static void window_custom_invalidate(rct_window* w);
static void window_custom_paint(rct_window* w, rct_drawpixelinfo* dpi);
static void window_custom_scrollpaint(rct_window* w, rct_drawpixelinfo* dpi, int32_t scrollIndex);
static void window_custom_update_viewport(rct_window* w);
static rct_window_event_list window_custom_events = { window_custom_close,
@@ -73,10 +79,10 @@ namespace OpenRCT2::Ui::Windows
nullptr,
nullptr,
nullptr,
nullptr,
nullptr,
nullptr,
nullptr,
window_custom_scrollgetsize,
window_custom_scrollmousedown,
window_custom_scrollmousedrag,
window_custom_scrollmouseover,
nullptr,
nullptr,
nullptr,
@@ -85,7 +91,7 @@ namespace OpenRCT2::Ui::Windows
nullptr,
window_custom_invalidate,
window_custom_paint,
nullptr };
window_custom_scrollpaint };
struct CustomWidgetDesc
{
@@ -100,23 +106,23 @@ namespace OpenRCT2::Ui::Windows
std::string Text;
std::string Tooltip;
std::vector<std::string> Items;
std::vector<ListViewItem> ListViewItems;
std::vector<ListViewColumn> ListViewColumns;
ScrollbarType Scrollbars{};
int32_t SelectedIndex{};
bool IsChecked{};
bool IsDisabled{};
bool HasBorder{};
bool ShowColumnHeaders{};
bool IsStriped{};
bool CanSelect{};
// Event handlers
DukValue OnClick;
DukValue OnChange;
DukValue OnIncrement;
DukValue OnDecrement;
static std::string ProcessString(const DukValue& value)
{
if (value.type() == DukValue::Type::STRING)
return language_convert_string(value.as_string());
return {};
}
DukValue OnHighlight;
static CustomWidgetDesc FromDukValue(DukValue desc)
{
@@ -157,11 +163,6 @@ namespace OpenRCT2::Ui::Windows
}
else if (result.Type == "dropdown")
{
auto dukItems = desc["items"].as_array();
for (const auto& dukItem : dukItems)
{
result.Items.push_back(ProcessString(dukItem));
}
result.SelectedIndex = desc["selectedIndex"].as_int();
result.OnChange = desc["onChange"];
}
@@ -169,6 +170,20 @@ namespace OpenRCT2::Ui::Windows
{
result.Text = ProcessString(desc["text"]);
}
else if (result.Type == "listview")
{
result.ListViewColumns = FromDuk<std::vector<ListViewColumn>>(desc["columns"]);
result.ListViewItems = FromDuk<std::vector<ListViewItem>>(desc["items"]);
result.ShowColumnHeaders = AsOrDefault(desc["showColumnHeaders"], false);
result.IsStriped = AsOrDefault(desc["isStriped"], false);
result.OnClick = desc["onClick"];
result.OnHighlight = desc["onHighlight"];
result.CanSelect = AsOrDefault(desc["canSelect"], false);
if (desc["scrollbars"].type() == DukValue::UNDEFINED)
result.Scrollbars = ScrollbarType::Vertical;
else
result.Scrollbars = FromDuk<ScrollbarType>(desc["scrollbars"]);
}
else if (result.Type == "spinner")
{
result.Text = ProcessString(desc["text"]);
@@ -312,6 +327,7 @@ namespace OpenRCT2::Ui::Windows
CustomWindowDesc Desc;
std::vector<rct_widget> Widgets;
std::vector<size_t> WidgetIndexMap;
std::vector<CustomListView> ListViews;
CustomWindowInfo(std::shared_ptr<Plugin> owner, const CustomWindowDesc& desc)
: Owner(owner)
@@ -401,8 +417,6 @@ namespace OpenRCT2::Ui::Windows
window->max_height = desc.MaxHeight.value_or(std::numeric_limits<uint16_t>::max());
}
RefreshWidgets(window);
window_init_scroll_widgets(window);
window_custom_update_viewport(window);
return window;
}
@@ -576,6 +590,44 @@ namespace OpenRCT2::Ui::Windows
}
}
static void window_custom_scrollgetsize(rct_window* w, int32_t scrollIndex, int32_t* width, int32_t* height)
{
auto& info = GetInfo(w);
if (scrollIndex < static_cast<int32_t>(info.ListViews.size()))
{
auto size = info.ListViews[scrollIndex].GetSize();
*width = size.width;
*height = size.height;
}
}
static void window_custom_scrollmousedown(rct_window* w, int32_t scrollIndex, const ScreenCoordsXY& screenCoords)
{
auto& info = GetInfo(w);
if (scrollIndex < static_cast<int32_t>(info.ListViews.size()))
{
info.ListViews[scrollIndex].MouseDown(screenCoords);
}
}
static void window_custom_scrollmousedrag(rct_window* w, int32_t scrollIndex, const ScreenCoordsXY& screenCoords)
{
auto& info = GetInfo(w);
if (scrollIndex < static_cast<int32_t>(info.ListViews.size()))
{
info.ListViews[scrollIndex].MouseOver(screenCoords, true);
}
}
static void window_custom_scrollmouseover(rct_window* w, int32_t scrollIndex, const ScreenCoordsXY& screenCoords)
{
auto& info = GetInfo(w);
if (scrollIndex < static_cast<int32_t>(info.ListViews.size()))
{
info.ListViews[scrollIndex].MouseOver(screenCoords, false);
}
}
static void window_custom_set_pressed_tab(rct_window* w)
{
const auto& info = GetInfo(w);
@@ -604,6 +656,28 @@ namespace OpenRCT2::Ui::Windows
const auto& desc = GetInfo(w).Desc;
set_format_arg(0, void*, desc.Title.c_str());
auto& info = GetInfo(w);
size_t scrollIndex = 0;
for (auto widget = w->widgets; widget->type != WWT_LAST; widget++)
{
if (widget->type == WWT_SCROLL)
{
auto& listView = info.ListViews[scrollIndex];
auto width = widget->right - widget->left + 1 - 2;
auto height = widget->bottom - widget->top + 1 - 2;
if (listView.GetScrollbars() == ScrollbarType::Horizontal || listView.GetScrollbars() == ScrollbarType::Both)
{
height -= SCROLLBAR_WIDTH + 1;
}
if (listView.GetScrollbars() == ScrollbarType::Vertical || listView.GetScrollbars() == ScrollbarType::Both)
{
width -= SCROLLBAR_WIDTH + 1;
}
listView.Resize({ width, height });
scrollIndex++;
}
}
}
static void window_custom_draw_tab_images(rct_window* w, rct_drawpixelinfo* dpi)
@@ -642,6 +716,15 @@ namespace OpenRCT2::Ui::Windows
}
}
static void window_custom_scrollpaint(rct_window* w, rct_drawpixelinfo* dpi, int32_t scrollIndex)
{
const auto& info = GetInfo(w);
if (scrollIndex < static_cast<int32_t>(info.ListViews.size()))
{
info.ListViews[scrollIndex].Paint(w, dpi, &w->scrolls[scrollIndex]);
}
}
static std::optional<rct_widgetindex> GetViewportWidgetIndex(rct_window* w)
{
rct_widgetindex widgetIndex = 0;
@@ -752,6 +835,10 @@ namespace OpenRCT2::Ui::Windows
{
widget.string = const_cast<utf8*>(desc.Items[desc.SelectedIndex].c_str());
}
else
{
widget.string = const_cast<utf8*>("");
}
widget.flags |= WIDGET_FLAGS::TEXT_IS_STRING;
widgetList.push_back(widget);
@@ -782,6 +869,18 @@ namespace OpenRCT2::Ui::Windows
widget.flags |= WIDGET_FLAGS::TEXT_IS_STRING;
widgetList.push_back(widget);
}
else if (desc.Type == "listview")
{
widget.type = WWT_SCROLL;
widget.content = 0;
if (desc.Scrollbars == ScrollbarType::Horizontal)
widget.content = SCROLL_HORIZONTAL;
else if (desc.Scrollbars == ScrollbarType::Vertical)
widget.content = SCROLL_VERTICAL;
else if (desc.Scrollbars == ScrollbarType::Both)
widget.content = SCROLL_BOTH;
widgetList.push_back(widget);
}
else if (desc.Type == "spinner")
{
widget.type = WWT_SPINNER;
@@ -827,6 +926,7 @@ namespace OpenRCT2::Ui::Windows
widgets.clear();
info.WidgetIndexMap.clear();
info.ListViews.clear();
// Add default widgets (window shim)
widgets.insert(widgets.begin(), std::begin(CustomDefaultWidgets), std::end(CustomDefaultWidgets));
@@ -877,6 +977,20 @@ namespace OpenRCT2::Ui::Windows
{
info.WidgetIndexMap.push_back(widgetDescIndex);
}
if (widgetDesc.Type == "listview")
{
CustomListView listView(w, info.ListViews.size());
listView.SetScrollbars(widgetDesc.Scrollbars, true);
listView.SetColumns(widgetDesc.ListViewColumns, true);
listView.SetItems(widgetDesc.ListViewItems, true);
listView.ShowColumnHeaders = widgetDesc.ShowColumnHeaders;
listView.IsStriped = widgetDesc.IsStriped;
listView.OnClick = widgetDesc.OnClick;
listView.OnHighlight = widgetDesc.OnHighlight;
listView.CanSelect = widgetDesc.CanSelect;
info.ListViews.push_back(std::move(listView));
}
}
for (size_t i = firstCustomWidgetIndex; i < widgets.size(); i++)
@@ -899,6 +1013,9 @@ namespace OpenRCT2::Ui::Windows
widgets.push_back({ WIDGETS_END });
w->widgets = widgets.data();
window_init_scroll_widgets(w);
window_custom_update_viewport(w);
}
static void InvokeEventHandler(const std::shared_ptr<Plugin>& owner, const DukValue& dukHandler)
@@ -984,6 +1101,47 @@ namespace OpenRCT2::Ui::Windows
return std::nullopt;
}
std::string GetWidgetName(rct_window* w, rct_widgetindex widgetIndex)
{
if (w->custom_info != nullptr)
{
const auto& customInfo = GetInfo(w);
auto customWidgetInfo = customInfo.GetCustomWidgetDesc(w, widgetIndex);
if (customWidgetInfo != nullptr)
{
return customWidgetInfo->Name;
}
}
return {};
}
void SetWidgetName(rct_window* w, rct_widgetindex widgetIndex, const std::string_view& name)
{
if (w->custom_info != nullptr)
{
auto& customInfo = GetInfo(w);
auto customWidgetInfo = customInfo.GetCustomWidgetDesc(w, widgetIndex);
if (customWidgetInfo != nullptr)
{
customWidgetInfo->Name = std::string(name);
}
}
}
CustomListView* GetCustomListView(rct_window* w, rct_widgetindex widgetIndex)
{
if (w->custom_info != nullptr)
{
auto& info = GetInfo(w);
auto scrollIndex = window_get_scroll_data_index(w, widgetIndex);
if (scrollIndex < static_cast<int32_t>(info.ListViews.size()))
{
return &info.ListViews[scrollIndex];
}
}
return nullptr;
}
} // namespace OpenRCT2::Ui::Windows
#endif

View File

@@ -18,11 +18,16 @@
namespace OpenRCT2::Ui::Windows
{
class CustomListView;
std::string GetWindowTitle(rct_window* w);
void UpdateWindowTitle(rct_window* w, const std::string_view& value);
void UpdateWidgetText(rct_window* w, rct_widgetindex widget, const std::string_view& string_view);
rct_window* FindCustomWindowByClassification(const std::string_view& classification);
std::optional<rct_widgetindex> FindWidgetIndexByName(rct_window* w, const std::string_view& name);
std::string GetWidgetName(rct_window* w, rct_widgetindex widgetIndex);
void SetWidgetName(rct_window* w, rct_widgetindex widgetIndex, const std::string_view& name);
CustomListView* GetCustomListView(rct_window* w, rct_widgetindex widgetIndex);
} // namespace OpenRCT2::Ui::Windows
#endif

View File

@@ -13,6 +13,7 @@
# include "../interface/Widget.h"
# include "../interface/Window.h"
# include "CustomListView.h"
# include "CustomWindow.h"
# include "ScViewport.hpp"
@@ -26,7 +27,7 @@ namespace OpenRCT2::Scripting
{
class ScWidget
{
private:
protected:
rct_windowclass _class{};
rct_windownumber _number{};
rct_widgetindex _widgetIndex{};
@@ -39,7 +40,28 @@ namespace OpenRCT2::Scripting
{
}
static DukValue ToDukValue(duk_context* ctx, rct_window* w, rct_widgetindex widgetIndex);
private:
std::string name_get() const
{
auto w = GetWindow();
if (w != nullptr && IsCustomWindow())
{
return OpenRCT2::Ui::Windows::GetWidgetName(w, _widgetIndex);
}
return {};
}
void name_set(const std::string& value)
{
auto w = GetWindow();
if (w != nullptr && IsCustomWindow())
{
OpenRCT2::Ui::Windows::SetWidgetName(w, _widgetIndex, value);
}
}
std::string type_get() const
{
auto widget = GetWidget();
@@ -190,24 +212,6 @@ namespace OpenRCT2::Scripting
}
}
bool isChecked_get() const
{
auto w = GetWindow();
if (w != nullptr)
{
return widget_is_pressed(w, _widgetIndex);
}
return false;
}
void isChecked_set(bool value)
{
auto w = GetWindow();
if (w != nullptr)
{
widget_set_checkbox_value(w, _widgetIndex, value ? 1 : 0);
}
}
uint32_t image_get() const
{
if (IsCustomWindow())
@@ -268,6 +272,7 @@ namespace OpenRCT2::Scripting
static void Register(duk_context* ctx)
{
// Common
dukglue_register_property(ctx, &ScWidget::name_get, &ScWidget::name_set, "name");
dukglue_register_property(ctx, &ScWidget::type_get, nullptr, "type");
dukglue_register_property(ctx, &ScWidget::x_get, &ScWidget::x_set, "x");
dukglue_register_property(ctx, &ScWidget::y_get, &ScWidget::y_set, "y");
@@ -278,11 +283,10 @@ namespace OpenRCT2::Scripting
// No so common
dukglue_register_property(ctx, &ScWidget::image_get, &ScWidget::image_set, "image");
dukglue_register_property(ctx, &ScWidget::text_get, &ScWidget::text_set, "text");
dukglue_register_property(ctx, &ScWidget::isChecked_get, &ScWidget::isChecked_set, "isChecked");
dukglue_register_property(ctx, &ScWidget::viewport_get, nullptr, "viewport");
}
private:
protected:
rct_window* GetWindow() const
{
if (_class == WC_MAIN_WINDOW)
@@ -316,6 +320,248 @@ namespace OpenRCT2::Scripting
widget_invalidate_by_number(_class, _number, _widgetIndex);
}
};
class ScCheckBoxWidget : public ScWidget
{
public:
ScCheckBoxWidget(rct_windowclass c, rct_windownumber n, rct_widgetindex widgetIndex)
: ScWidget(c, n, widgetIndex)
{
}
static void Register(duk_context* ctx)
{
dukglue_set_base_class<ScWidget, ScCheckBoxWidget>(ctx);
dukglue_register_property(ctx, &ScCheckBoxWidget::isChecked_get, &ScCheckBoxWidget::isChecked_set, "isChecked");
}
private:
bool isChecked_get() const
{
auto w = GetWindow();
if (w != nullptr)
{
return widget_is_pressed(w, _widgetIndex);
}
return false;
}
void isChecked_set(bool value)
{
auto w = GetWindow();
if (w != nullptr)
{
widget_set_checkbox_value(w, _widgetIndex, value ? 1 : 0);
}
}
};
class ScListViewWidget : public ScWidget
{
public:
ScListViewWidget(rct_windowclass c, rct_windownumber n, rct_widgetindex widgetIndex)
: ScWidget(c, n, widgetIndex)
{
}
static void Register(duk_context* ctx)
{
dukglue_set_base_class<ScWidget, ScListViewWidget>(ctx);
dukglue_register_property(ctx, &ScListViewWidget::canSelect_get, &ScListViewWidget::canSelect_set, "canSelect");
dukglue_register_property(ctx, &ScListViewWidget::isStriped_get, &ScListViewWidget::isStriped_set, "isStriped");
dukglue_register_property(ctx, &ScListViewWidget::scrollbars_get, &ScListViewWidget::scrollbars_set, "scrollbars");
dukglue_register_property(
ctx, &ScListViewWidget::showColumnHeaders_get, &ScListViewWidget::showColumnHeaders_set, "showColumnHeaders");
dukglue_register_property(ctx, &ScListViewWidget::highlightedCell_get, nullptr, "highlightedCell");
dukglue_register_property(
ctx, &ScListViewWidget::selectedCell_get, &ScListViewWidget::selectedCell_set, "selectedCell");
dukglue_register_property(ctx, &ScListViewWidget::columns_get, &ScListViewWidget::columns_set, "columns");
dukglue_register_property(ctx, &ScListViewWidget::items_get, &ScListViewWidget::items_set, "items");
}
private:
bool canSelect_get() const
{
auto listView = GetListView();
if (listView != nullptr)
{
return listView->CanSelect;
}
return false;
}
void canSelect_set(bool value)
{
auto listView = GetListView();
if (listView != nullptr)
{
listView->CanSelect = value;
}
}
bool isStriped_get() const
{
auto listView = GetListView();
if (listView != nullptr)
{
return listView->IsStriped;
}
return false;
}
void isStriped_set(bool value)
{
auto listView = GetListView();
if (listView != nullptr)
{
listView->IsStriped = value;
}
}
DukValue scrollbars_get() const
{
auto ctx = GetContext()->GetScriptEngine().GetContext();
auto scrollType = ScrollbarType::None;
auto listView = GetListView();
if (listView != nullptr)
{
scrollType = listView->GetScrollbars();
}
return ToDuk(ctx, scrollType);
}
void scrollbars_set(const DukValue& value)
{
auto listView = GetListView();
if (listView != nullptr)
{
listView->SetScrollbars(FromDuk<ScrollbarType>(value));
}
}
bool showColumnHeaders_get() const
{
auto listView = GetListView();
if (listView != nullptr)
{
return listView->ShowColumnHeaders;
}
return false;
}
void showColumnHeaders_set(bool value)
{
auto listView = GetListView();
if (listView != nullptr)
{
listView->ShowColumnHeaders = value;
}
}
DukValue highlightedCell_get()
{
auto ctx = GetContext()->GetScriptEngine().GetContext();
auto listView = GetListView();
if (listView != nullptr)
{
return ToDuk(ctx, listView->LastHighlightedCell);
}
return ToDuk(ctx, nullptr);
}
DukValue selectedCell_get()
{
auto ctx = GetContext()->GetScriptEngine().GetContext();
auto listView = GetListView();
if (listView != nullptr)
{
return ToDuk(ctx, listView->SelectedCell);
}
return ToDuk(ctx, nullptr);
}
void selectedCell_set(const DukValue& value)
{
auto listView = GetListView();
if (listView != nullptr)
{
listView->SelectedCell = FromDuk<std::optional<RowColumn>>(value);
}
}
std::vector<std::vector<std::string>> items_get()
{
std::vector<std::vector<std::string>> result;
auto listView = GetListView();
if (listView != nullptr)
{
for (const auto& item : listView->GetItems())
{
result.push_back(item.Cells);
}
}
return result;
}
void items_set(const DukValue& value)
{
auto listView = GetListView();
if (listView != nullptr)
{
listView->SetItems(FromDuk<std::vector<ListViewItem>>(value));
}
}
std::vector<DukValue> columns_get()
{
std::vector<DukValue> result;
auto listView = GetListView();
if (listView != nullptr)
{
auto ctx = GetContext()->GetScriptEngine().GetContext();
for (const auto& column : listView->GetColumns())
{
result.push_back(ToDuk(ctx, column));
}
}
return result;
}
void columns_set(const DukValue& value)
{
auto listView = GetListView();
if (listView != nullptr)
{
listView->SetColumns(FromDuk<std::vector<ListViewColumn>>(value));
}
}
CustomListView* GetListView() const
{
auto w = GetWindow();
if (w != nullptr)
{
return GetCustomListView(w, _widgetIndex);
}
return nullptr;
}
};
inline DukValue ScWidget::ToDukValue(duk_context* ctx, rct_window* w, rct_widgetindex widgetIndex)
{
const auto& widget = w->widgets[widgetIndex];
auto c = w->classification;
auto n = w->number;
switch (widget.type)
{
case WWT_CHECKBOX:
return GetObjectAsDukValue(ctx, std::make_shared<ScCheckBoxWidget>(c, n, widgetIndex));
case WWT_SCROLL:
return GetObjectAsDukValue(ctx, std::make_shared<ScListViewWidget>(c, n, widgetIndex));
default:
return GetObjectAsDukValue(ctx, std::make_shared<ScWidget>(c, n, widgetIndex));
}
}
} // namespace OpenRCT2::Scripting
#endif

View File

@@ -177,16 +177,18 @@ namespace OpenRCT2::Scripting
return (flags & (WF_STICK_TO_BACK | WF_STICK_TO_FRONT)) != 0;
}
std::vector<std::shared_ptr<ScWidget>> widgets_get() const
std::vector<DukValue> widgets_get() const
{
std::vector<std::shared_ptr<ScWidget>> result;
auto ctx = GetContext()->GetScriptEngine().GetContext();
std::vector<DukValue> result;
auto w = GetWindow();
if (w != nullptr)
{
rct_widgetindex widgetIndex = 0;
for (auto widget = w->widgets; widget->type != WWT_LAST; widget++)
{
result.push_back(std::make_shared<ScWidget>(_class, _number, widgetIndex));
result.push_back(ScWidget::ToDukValue(ctx, w, widgetIndex));
widgetIndex++;
}
}
@@ -257,18 +259,19 @@ namespace OpenRCT2::Scripting
}
}
std::shared_ptr<ScWidget> findWidget(std::string name) const
DukValue findWidget(std::string name) const
{
auto ctx = GetContext()->GetScriptEngine().GetContext();
auto w = GetWindow();
if (w != nullptr)
{
auto widgetIndex = FindWidgetIndexByName(w, name);
if (widgetIndex)
{
return std::make_shared<ScWidget>(_class, _number, *widgetIndex);
return ScWidget::ToDukValue(ctx, w, *widgetIndex);
}
}
return {};
return GetObjectAsDukValue<ScWidget>(ctx, nullptr);
}
void bringToFront()

View File

@@ -32,6 +32,8 @@ void UiScriptExtensions::Extend(ScriptEngine& scriptEngine)
ScUi::Register(ctx);
ScViewport::Register(ctx);
ScWidget::Register(ctx);
ScCheckBoxWidget::Register(ctx);
ScListViewWidget::Register(ctx);
ScWindow::Register(ctx);
InitialiseCustomMenuItems(scriptEngine);

View File

@@ -113,6 +113,20 @@ namespace OpenRCT2::Scripting
duk_put_prop_string(_ctx, _idx, name);
}
template<typename T> void Set(const char* name, const std::optional<T>& value)
{
if (value)
{
EnsureObjectPushed();
duk_push_null(_ctx);
duk_put_prop_string(_ctx, _idx, name);
}
else
{
Set(name, *value);
}
}
DukValue Take()
{
EnsureObjectPushed();
@@ -190,6 +204,8 @@ namespace OpenRCT2::Scripting
}
}
std::string ProcessString(const DukValue& value);
template<typename T> DukValue ToDuk(duk_context* ctx, const T& value) = delete;
template<typename T> T FromDuk(const DukValue& s) = delete;
@@ -205,6 +221,17 @@ namespace OpenRCT2::Scripting
return DukValue::take_from_stack(ctx);
}
template<size_t TLen> inline DukValue ToDuk(duk_context* ctx, const char (&value)[TLen])
{
duk_push_string(ctx, value);
return DukValue::take_from_stack(ctx);
}
template<typename T> DukValue ToDuk(duk_context* ctx, const std::optional<T>& value)
{
return value ? ToDuk(ctx, *value) : ToDuk(ctx, nullptr);
}
} // namespace OpenRCT2::Scripting
#endif

View File

@@ -1140,6 +1140,13 @@ std::string OpenRCT2::Scripting::Stringify(const DukValue& val)
return ExpressionStringifier::StringifyExpression(val);
}
std::string OpenRCT2::Scripting::ProcessString(const DukValue& value)
{
if (value.type() == DukValue::Type::STRING)
return language_convert_string(value.as_string());
return {};
}
bool OpenRCT2::Scripting::IsGameStateMutable()
{
// Allow single player to alter game state anywhere

View File

@@ -85,6 +85,29 @@ struct ScreenCoordsXY
}
};
struct ScreenSize
{
int32_t width{};
int32_t height{};
ScreenSize() = default;
constexpr ScreenSize(int32_t _width, int32_t _height)
: width(_width)
, height(_height)
{
}
bool operator==(const ScreenSize& other) const
{
return width == other.width && height == other.height;
}
bool operator!=(const ScreenSize& other) const
{
return !(*this == other);
}
};
/**
* Tile coordinates use 1 x/y increment per tile and 1 z increment per step.
* Regular ('big', 'sprite') coordinates use 32 x/y increments per tile and 8 z increments per step.