mirror of https://github.com/OpenRCT2/OpenRCT2.git
Refactor list view so we can access it from ScWidget
This commit is contained in:
parent
faf59598e5
commit
5e427413a6
|
@ -65,7 +65,7 @@
|
|||
"_DEBUG",
|
||||
"UNICODE",
|
||||
"_UNICODE",
|
||||
"__ENABLE_SCRIPTING__"
|
||||
"ENABLE_SCRIPTING"
|
||||
],
|
||||
"intelliSenseMode": "msvc-x64",
|
||||
"browse": {
|
||||
|
|
|
@ -0,0 +1,491 @@
|
|||
/*****************************************************************************
|
||||
* 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/Window.h"
|
||||
|
||||
# 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<> 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<> 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;
|
||||
}
|
||||
} // namespace OpenRCT2::Scripting
|
||||
|
||||
void CustomListView::SetItems(const std::vector<ListViewItem>& items)
|
||||
{
|
||||
Items = items;
|
||||
SortItems(0, ColumnSortOrder::None);
|
||||
}
|
||||
|
||||
void CustomListView::SetItems(std::vector<ListViewItem>&& items)
|
||||
{
|
||||
Items = items;
|
||||
SortItems(0, ColumnSortOrder::None);
|
||||
}
|
||||
|
||||
bool CustomListView::SortItem(size_t indexA, size_t indexB, size_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(size_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(size_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
|
||||
int32_t widthRemaining = size.width;
|
||||
for (size_t c = 0; c < Columns.size(); c++)
|
||||
{
|
||||
auto& column = Columns[c];
|
||||
if (c == Columns.size() - 1)
|
||||
{
|
||||
column.Width = widthRemaining;
|
||||
}
|
||||
else
|
||||
{
|
||||
column.Width = 0;
|
||||
if (column.RatioWidth && *column.RatioWidth > 0)
|
||||
{
|
||||
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 -= column.Width;
|
||||
}
|
||||
}
|
||||
|
||||
ScreenSize CustomListView::GetSize()
|
||||
{
|
||||
LastHighlightedCell = HighlightedCell;
|
||||
HighlightedCell = std::nullopt;
|
||||
ColumnHeaderPressedCurrentState = false;
|
||||
LastIsMouseDown = IsMouseDown;
|
||||
IsMouseDown = false;
|
||||
|
||||
ScreenSize result;
|
||||
result.width = 0;
|
||||
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 (isHighlighted)
|
||||
{
|
||||
gfx_filter_rect(dpi, dpi->x, y, dpi->x + dpi->width, y + (LIST_ROW_HEIGHT - 1), PALETTE_DARKEN_1);
|
||||
}
|
||||
else if (isSelected)
|
||||
{
|
||||
// gfx_fill_rect(dpi, dpi->x, y, dpi->x + dpi->width, y + LIST_ROW_HEIGHT - 1,
|
||||
// ColourMapA[w->colours[1]].dark);
|
||||
gfx_filter_rect(dpi, dpi->x, y, dpi->x + dpi->width, y + (LIST_ROW_HEIGHT - 1), PALETTE_DARKEN_2);
|
||||
}
|
||||
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 (size_t j = 0; j < 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
|
|
@ -0,0 +1,154 @@
|
|||
/*****************************************************************************
|
||||
* 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;
|
||||
std::vector<ListViewItem> Items;
|
||||
|
||||
public:
|
||||
std::shared_ptr<Plugin> Owner;
|
||||
std::vector<ListViewColumn> Columns;
|
||||
std::vector<size_t> SortedItems;
|
||||
std::optional<RowColumn> HighlightedCell;
|
||||
std::optional<RowColumn> LastHighlightedCell;
|
||||
std::optional<RowColumn> SelectedCell;
|
||||
std::optional<size_t> ColumnHeaderPressed;
|
||||
bool ColumnHeaderPressedCurrentState{};
|
||||
bool ShowColumnHeaders{};
|
||||
bool IsStriped{};
|
||||
ScreenSize LastKnownSize;
|
||||
ScrollbarType Scrollbars = ScrollbarType::Vertical;
|
||||
ColumnSortOrder CurrentSortOrder{};
|
||||
size_t CurrentSortColumn{};
|
||||
bool LastIsMouseDown{};
|
||||
bool IsMouseDown{};
|
||||
bool CanSelect{};
|
||||
|
||||
DukValue OnClick;
|
||||
DukValue OnHighlight;
|
||||
|
||||
void SetItems(const std::vector<ListViewItem>& items);
|
||||
void SetItems(std::vector<ListViewItem>&& items);
|
||||
bool SortItem(size_t indexA, size_t indexB, size_t column);
|
||||
void SortItems(size_t column);
|
||||
void SortItems(size_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);
|
||||
} // namespace OpenRCT2::Scripting
|
||||
|
||||
#endif
|
|
@ -10,6 +10,7 @@
|
|||
#ifdef ENABLE_SCRIPTING
|
||||
|
||||
# include "../interface/Dropdown.h"
|
||||
# include "CustomListView.h"
|
||||
# include "ScUi.hpp"
|
||||
# include "ScWindow.hpp"
|
||||
|
||||
|
@ -22,7 +23,6 @@
|
|||
# include <openrct2/localisation/StringIds.h>
|
||||
# include <openrct2/scripting/Plugin.h>
|
||||
# include <openrct2/sprites.h>
|
||||
# include <openrct2/util/Util.h>
|
||||
# include <openrct2/world/Sprite.h>
|
||||
# include <optional>
|
||||
# include <string>
|
||||
|
@ -31,125 +31,6 @@
|
|||
using namespace OpenRCT2;
|
||||
using namespace OpenRCT2::Scripting;
|
||||
|
||||
namespace OpenRCT2::Ui::Windows
|
||||
{
|
||||
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)
|
||||
{
|
||||
}
|
||||
};
|
||||
} // namespace OpenRCT2::Ui::Windows
|
||||
|
||||
namespace OpenRCT2::Scripting
|
||||
{
|
||||
static std::string ProcessString(const DukValue& value)
|
||||
{
|
||||
if (value.type() == DukValue::Type::STRING)
|
||||
return language_convert_string(value.as_string());
|
||||
return {};
|
||||
}
|
||||
|
||||
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<> 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<> 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;
|
||||
}
|
||||
} // namespace OpenRCT2::Scripting
|
||||
|
||||
namespace OpenRCT2::Ui::Windows
|
||||
{
|
||||
enum CUSTOM_WINDOW_WIDX
|
||||
|
@ -448,462 +329,6 @@ namespace OpenRCT2::Ui::Windows
|
|||
}
|
||||
};
|
||||
|
||||
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 CustomListViewInfo
|
||||
{
|
||||
private:
|
||||
static constexpr int32_t HEADER_ROW = -1;
|
||||
std::vector<ListViewItem> Items;
|
||||
|
||||
public:
|
||||
std::shared_ptr<Plugin> Owner;
|
||||
std::vector<ListViewColumn> Columns;
|
||||
std::vector<size_t> SortedItems;
|
||||
std::optional<RowColumn> HighlightedCell;
|
||||
std::optional<RowColumn> LastHighlightedCell;
|
||||
std::optional<RowColumn> SelectedCell;
|
||||
std::optional<size_t> ColumnHeaderPressed;
|
||||
bool ColumnHeaderPressedCurrentState{};
|
||||
bool ShowColumnHeaders{};
|
||||
bool IsStriped{};
|
||||
ScreenSize LastKnownSize;
|
||||
ScrollbarType Scrollbars = ScrollbarType::Vertical;
|
||||
ColumnSortOrder CurrentSortOrder{};
|
||||
size_t CurrentSortColumn{};
|
||||
bool LastIsMouseDown{};
|
||||
bool IsMouseDown{};
|
||||
bool CanSelect{};
|
||||
|
||||
DukValue OnClick;
|
||||
DukValue OnHighlight;
|
||||
|
||||
void SetItems(const std::vector<ListViewItem>& items)
|
||||
{
|
||||
Items = items;
|
||||
SortItems(0, ColumnSortOrder::None);
|
||||
}
|
||||
|
||||
void SetItems(std::vector<ListViewItem>&& items)
|
||||
{
|
||||
Items = items;
|
||||
SortItems(0, ColumnSortOrder::None);
|
||||
}
|
||||
|
||||
bool SortItem(size_t indexA, size_t indexB, size_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 SortItems(size_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 SortItems(size_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 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
|
||||
int32_t widthRemaining = size.width;
|
||||
for (size_t c = 0; c < Columns.size(); c++)
|
||||
{
|
||||
auto& column = Columns[c];
|
||||
if (c == Columns.size() - 1)
|
||||
{
|
||||
column.Width = widthRemaining;
|
||||
}
|
||||
else
|
||||
{
|
||||
column.Width = 0;
|
||||
if (column.RatioWidth && *column.RatioWidth > 0)
|
||||
{
|
||||
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 -= column.Width;
|
||||
}
|
||||
}
|
||||
|
||||
ScreenSize GetSize()
|
||||
{
|
||||
LastHighlightedCell = HighlightedCell;
|
||||
HighlightedCell = std::nullopt;
|
||||
ColumnHeaderPressedCurrentState = false;
|
||||
LastIsMouseDown = IsMouseDown;
|
||||
IsMouseDown = false;
|
||||
|
||||
ScreenSize result;
|
||||
result.width = 0;
|
||||
result.height = static_cast<int32_t>(Items.size() * LIST_ROW_HEIGHT);
|
||||
return result;
|
||||
}
|
||||
|
||||
void 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 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 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 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 = 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 (isHighlighted)
|
||||
{
|
||||
gfx_filter_rect(dpi, dpi->x, y, dpi->x + dpi->width, y + (LIST_ROW_HEIGHT - 1), PALETTE_DARKEN_1);
|
||||
}
|
||||
else if (isSelected)
|
||||
{
|
||||
// gfx_fill_rect(dpi, dpi->x, y, dpi->x + dpi->width, y + LIST_ROW_HEIGHT - 1,
|
||||
// ColourMapA[w->colours[1]].dark);
|
||||
gfx_filter_rect(dpi, dpi->x, y, dpi->x + dpi->width, y + (LIST_ROW_HEIGHT - 1), PALETTE_DARKEN_2);
|
||||
}
|
||||
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 (size_t j = 0; j < 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
void 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 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> 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;
|
||||
}
|
||||
};
|
||||
|
||||
class CustomWindowInfo
|
||||
{
|
||||
public:
|
||||
|
@ -911,7 +336,7 @@ namespace OpenRCT2::Ui::Windows
|
|||
CustomWindowDesc Desc;
|
||||
std::vector<rct_widget> Widgets;
|
||||
std::vector<size_t> WidgetIndexMap;
|
||||
std::vector<CustomListViewInfo> ListViews;
|
||||
std::vector<CustomListView> ListViews;
|
||||
|
||||
CustomWindowInfo(std::shared_ptr<Plugin> owner, const CustomWindowDesc& desc)
|
||||
: Owner(owner)
|
||||
|
@ -1179,7 +604,7 @@ 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 < info.ListViews.size())
|
||||
if (scrollIndex < static_cast<int32_t>(info.ListViews.size()))
|
||||
{
|
||||
auto size = info.ListViews[scrollIndex].GetSize();
|
||||
*width = size.width;
|
||||
|
@ -1190,7 +615,7 @@ namespace OpenRCT2::Ui::Windows
|
|||
static void window_custom_scrollmousedown(rct_window* w, int32_t scrollIndex, const ScreenCoordsXY& screenCoords)
|
||||
{
|
||||
auto& info = GetInfo(w);
|
||||
if (scrollIndex < info.ListViews.size())
|
||||
if (scrollIndex < static_cast<int32_t>(info.ListViews.size()))
|
||||
{
|
||||
info.ListViews[scrollIndex].MouseDown(screenCoords);
|
||||
}
|
||||
|
@ -1199,7 +624,7 @@ namespace OpenRCT2::Ui::Windows
|
|||
static void window_custom_scrollmousedrag(rct_window* w, int32_t scrollIndex, const ScreenCoordsXY& screenCoords)
|
||||
{
|
||||
auto& info = GetInfo(w);
|
||||
if (scrollIndex < info.ListViews.size())
|
||||
if (scrollIndex < static_cast<int32_t>(info.ListViews.size()))
|
||||
{
|
||||
info.ListViews[scrollIndex].MouseOver(screenCoords, true);
|
||||
}
|
||||
|
@ -1208,7 +633,7 @@ namespace OpenRCT2::Ui::Windows
|
|||
static void window_custom_scrollmouseover(rct_window* w, int32_t scrollIndex, const ScreenCoordsXY& screenCoords)
|
||||
{
|
||||
auto& info = GetInfo(w);
|
||||
if (scrollIndex < info.ListViews.size())
|
||||
if (scrollIndex < static_cast<int32_t>(info.ListViews.size()))
|
||||
{
|
||||
info.ListViews[scrollIndex].MouseOver(screenCoords, false);
|
||||
}
|
||||
|
@ -1305,7 +730,7 @@ 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 < info.ListViews.size())
|
||||
if (scrollIndex < static_cast<int32_t>(info.ListViews.size()))
|
||||
{
|
||||
info.ListViews[scrollIndex].Paint(w, dpi, &w->scrolls[scrollIndex]);
|
||||
}
|
||||
|
@ -1421,6 +846,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);
|
||||
|
||||
|
@ -1556,7 +985,7 @@ namespace OpenRCT2::Ui::Windows
|
|||
|
||||
if (widgetDesc.Type == "listview")
|
||||
{
|
||||
CustomListViewInfo listView;
|
||||
CustomListView listView;
|
||||
listView.Columns = widgetDesc.ListViewColumns;
|
||||
listView.SetItems(widgetDesc.ListViewItems);
|
||||
listView.ShowColumnHeaders = widgetDesc.ShowColumnHeaders;
|
||||
|
@ -1673,6 +1102,20 @@ namespace OpenRCT2::Ui::Windows
|
|||
return std::nullopt;
|
||||
}
|
||||
|
||||
CustomListView* GetCustomListView(rct_window* w, rct_widgetindex widgetIndex)
|
||||
{
|
||||
if (w->custom_info != nullptr)
|
||||
{
|
||||
auto& customInfo = GetInfo(w);
|
||||
auto scrollIndex = window_get_scroll_data_index(w, widgetIndex);
|
||||
if (scrollIndex < static_cast<int32_t>(info.ListViews.size()))
|
||||
{
|
||||
return &customInfo.ListViews[scrollIndex];
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
} // namespace OpenRCT2::Ui::Windows
|
||||
|
||||
#endif
|
||||
|
|
|
@ -18,11 +18,14 @@
|
|||
|
||||
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);
|
||||
CustomListView* GetCustomListView(rct_window* w, rct_widgetindex widgetIndex);
|
||||
} // namespace OpenRCT2::Ui::Windows
|
||||
|
||||
#endif
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
|
||||
# include "../interface/Widget.h"
|
||||
# include "../interface/Window.h"
|
||||
# include "CustomListView.h"
|
||||
# include "CustomWindow.h"
|
||||
# include "ScViewport.hpp"
|
||||
|
||||
|
@ -351,10 +352,31 @@ namespace OpenRCT2::Scripting
|
|||
private:
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
CustomListView* GetListView() const
|
||||
{
|
||||
auto w = GetWindow();
|
||||
if (w != nullptr)
|
||||
{
|
||||
return GetCustomListView(w, _widgetIndex);
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -190,6 +190,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;
|
||||
|
||||
|
|
|
@ -1136,6 +1136,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
|
||||
|
|
Loading…
Reference in New Issue