From 096de3ccc81ad69d88d2ff2ec51beca93c84a961 Mon Sep 17 00:00:00 2001 From: Ted John Date: Sat, 2 May 2020 20:11:07 +0100 Subject: [PATCH 01/16] Implement list view --- distribution/openrct2.d.ts | 35 +- src/openrct2-ui/scripting/CustomWindow.cpp | 571 ++++++++++++++++++++- src/openrct2/world/Location.hpp | 23 + 3 files changed, 612 insertions(+), 17 deletions(-) diff --git a/distribution/openrct2.d.ts b/distribution/openrct2.d.ts index 6c04404802..3096912c53 100644 --- a/distribution/openrct2.d.ts +++ b/distribution/openrct2.d.ts @@ -1031,7 +1031,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; @@ -1072,6 +1072,39 @@ declare global { onChange: (index: number) => void; } + type SortOrder = "none" | "ascending" | "descending"; + + type ScrollType = "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 ListView extends Widget { + scroll?: ScrollType; + isStriped?: boolean; + showColumnHeaders?: boolean; + columns?: ListViewColumn[]; + items?: string[] | ListViewItem[]; + selectedIndex?: number; + highlightedIndex?: number; + + onHighlight: (index: number) => void; + onSelect: (index: number) => void; + + getCell(row: number, column: number): string; + setCell(row: number, column: number, value: string): void; + } + interface SpinnerWidget extends Widget { text: string; onDecrement: () => void; diff --git a/src/openrct2-ui/scripting/CustomWindow.cpp b/src/openrct2-ui/scripting/CustomWindow.cpp index 9681fdbadc..56f0692f4e 100644 --- a/src/openrct2-ui/scripting/CustomWindow.cpp +++ b/src/openrct2-ui/scripting/CustomWindow.cpp @@ -30,6 +30,125 @@ 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 RatioWidth{}; + std::optional MinWidth{}; + std::optional MaxWidth{}; + int32_t Width{}; + }; + + struct ListViewItem + { + std::vector Cells; + + ListViewItem() = default; + explicit ListViewItem(const std::string_view& text) + { + Cells.emplace_back(text); + } + explicit ListViewItem(std::vector&& 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 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(d["sortOrder"]); + result.Header = AsOrDefault(d["header"], ""); + result.HeaderTooltip = AsOrDefault(d["headerTooltip"], ""); + result.MinWidth = FromDuk>(d["minWidth"]); + result.MaxWidth = FromDuk>(d["maxWidth"]); + result.RatioWidth = FromDuk>(d["ratioWidth"]); + if (d["width"].type() == DukValue::Type::NUMBER) + { + result.MinWidth = d["width"].as_int(); + result.MaxWidth = result.MinWidth; + result.RatioWidth = std::nullopt; + } + else + { + 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 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 @@ -54,8 +173,12 @@ 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_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 +196,10 @@ namespace OpenRCT2::Ui::Windows nullptr, nullptr, nullptr, + window_custom_scrollgetsize, + window_custom_scrollmousedown, nullptr, - nullptr, - nullptr, - nullptr, + window_custom_scrollmouseover, nullptr, nullptr, nullptr, @@ -85,7 +208,7 @@ namespace OpenRCT2::Ui::Windows nullptr, window_custom_invalidate, window_custom_paint, - nullptr }; + window_custom_scrollpaint }; struct CustomWidgetDesc { @@ -100,10 +223,14 @@ namespace OpenRCT2::Ui::Windows std::string Text; std::string Tooltip; std::vector Items; + std::vector ListViewItems; + std::vector ListViewColumns; int32_t SelectedIndex{}; bool IsChecked{}; bool IsDisabled{}; bool HasBorder{}; + bool ShowColumnHeaders{}; + bool IsStriped{}; // Event handlers DukValue OnClick; @@ -111,13 +238,6 @@ namespace OpenRCT2::Ui::Windows 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 {}; - } - static CustomWidgetDesc FromDukValue(DukValue desc) { CustomWidgetDesc result; @@ -157,11 +277,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 +284,27 @@ namespace OpenRCT2::Ui::Windows { result.Text = ProcessString(desc["text"]); } + else if (result.Type == "listview") + { + if (desc["columns"].is_array()) + { + auto dukColumns = desc["columns"].as_array(); + for (const auto& dukColumn : dukColumns) + { + result.ListViewColumns.push_back(FromDuk(dukColumn)); + } + } + if (desc["items"].is_array()) + { + auto dukItems = desc["items"].as_array(); + for (const auto& dukItem : dukItems) + { + result.ListViewItems.push_back(FromDuk(dukItem)); + } + } + result.ShowColumnHeaders = AsOrDefault(desc["showColumnHeaders"], false); + result.IsStriped = AsOrDefault(desc["isStriped"], false); + } else if (result.Type == "spinner") { result.Text = ProcessString(desc["text"]); @@ -305,6 +441,331 @@ namespace OpenRCT2::Ui::Windows } }; + class CustomListViewInfo + { + private: + struct HitTestResult + { + static constexpr size_t HEADER_ROW = std::numeric_limits::max(); + + size_t Row{}; + size_t Column{}; + + bool IsHeader() const + { + return Row == HEADER_ROW; + } + }; + + public: + std::shared_ptr Owner; + std::vector Columns; + std::vector Items; + std::optional HighlightedRow; + std::optional HighlightedColumn; + std::optional SelectedRow; + std::optional SelectedColumn; + std::optional ColumnHeaderPressed; + bool ShowColumnHeaders{}; + bool IsStriped{}; + ScreenSize LastKnownSize; + ScrollbarType Scrollbars = ScrollbarType::Vertical; + + DukValue OnHighlight; + DukValue OnSelect; + + 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 + { + if (column.RatioWidth) + { + 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() const + { + ScreenSize result; + result.width = 0; + result.height = static_cast(Items.size() * LIST_ROW_HEIGHT); + return result; + } + + void MouseOver(const ScreenCoordsXY& pos) + { + auto hitResult = GetItemIndexAt(pos); + if (hitResult) + { + if (HighlightedRow != hitResult->Row || HighlightedColumn != hitResult->Column) + { + HighlightedRow = hitResult->Row; + HighlightedColumn = hitResult->Column; + if (!hitResult->IsHeader() && OnHighlight.context() != nullptr && OnHighlight.is_function()) + { + auto ctx = OnHighlight.context(); + duk_push_int(ctx, static_cast(*HighlightedRow)); + auto dukRow = DukValue::take_from_stack(ctx, -1); + duk_push_int(ctx, static_cast(*HighlightedColumn)); + auto dukColumn = DukValue::take_from_stack(ctx, -1); + auto& scriptEngine = GetContext()->GetScriptEngine(); + scriptEngine.ExecutePluginCall(Owner, OnHighlight, { dukRow, dukColumn }, false); + } + } + } + ColumnHeaderPressed = std::nullopt; + } + + void MouseDown(const ScreenCoordsXY& pos) + { + auto hitResult = GetItemIndexAt(pos); + if (hitResult) + { + if (SelectedRow != hitResult->Row || SelectedColumn != hitResult->Column) + { + SelectedRow = hitResult->Row; + SelectedColumn = hitResult->Column; + if (!hitResult->IsHeader() && OnSelect.context() != nullptr && OnSelect.is_function()) + { + auto ctx = OnSelect.context(); + duk_push_int(ctx, static_cast(*SelectedRow)); + auto dukRow = DukValue::take_from_stack(ctx, -1); + duk_push_int(ctx, static_cast(*SelectedColumn)); + auto dukColumn = DukValue::take_from_stack(ctx, -1); + auto& scriptEngine = GetContext()->GetScriptEngine(); + scriptEngine.ExecutePluginCall(Owner, OnSelect, { dukRow, dukColumn }, false); + } + } + } + if (hitResult && hitResult->IsHeader()) + { + ColumnHeaderPressed = hitResult->Column; + } + else + { + ColumnHeaderPressed = std::nullopt; + } + } + + 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) + { + // Background colour + auto isStriped = IsStriped && (i & 1); + auto isHighlighted = i == HighlightedRow; + 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 + const auto& item = Items[i]; + if (Columns.size() == 0) + { + const auto& text = item.Cells[0]; + if (!text.empty()) + { + ScreenSize cellSize = { std::numeric_limits::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) + { + bool isPressed = ColumnHeaderPressed == j; + PaintHeading( + w, dpi, { x, y }, { column.Width, LIST_ROW_HEIGHT }, column.Header, ColumnSortOrder::None, + 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(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(STR_DOWN); + gfx_draw_string_right(dpi, STR_BLACK_STRING, gCommonFormatArgs, COLOUR_BLACK, pos.y + 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(STR_STRING); + ft.Add(text); + gfx_draw_string_left_clipped(dpi, stringId, gCommonFormatArgs, COLOUR_BLACK, pos.x, pos.y, size.width); + } + + std::optional GetItemIndexAt(const ScreenCoordsXY& pos) + { + std::optional result; + if (pos.x >= 0) + { + // Check if we pressed the header + if (ShowColumnHeaders && pos.y >= 0 && pos.y < LIST_ROW_HEIGHT) + { + result = HitTestResult(); + result->Row = HitTestResult::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(Items.size())) + { + result = HitTestResult(); + result->Row = static_cast(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 = c; + found = true; + break; + } + } + } + if (!found) + { + // Past all columns + return std::nullopt; + } + } + } + return result; + } + }; + class CustomWindowInfo { public: @@ -312,6 +773,7 @@ namespace OpenRCT2::Ui::Windows CustomWindowDesc Desc; std::vector Widgets; std::vector WidgetIndexMap; + std::vector ListViews; CustomWindowInfo(std::shared_ptr owner, const CustomWindowDesc& desc) : Owner(owner) @@ -576,6 +1038,35 @@ namespace OpenRCT2::Ui::Windows } } + static void window_custom_scrollgetsize(rct_window* w, int32_t scrollIndex, int32_t* width, int32_t* height) + { + const auto& info = GetInfo(w); + if (scrollIndex < 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 < info.ListViews.size()) + { + info.ListViews[scrollIndex].MouseDown(screenCoords); + } + } + + static void window_custom_scrollmouseover(rct_window* w, int32_t scrollIndex, const ScreenCoordsXY& screenCoords) + { + auto& info = GetInfo(w); + if (scrollIndex < info.ListViews.size()) + { + info.ListViews[scrollIndex].MouseOver(screenCoords); + } + } + static void window_custom_set_pressed_tab(rct_window* w) { const auto& info = GetInfo(w); @@ -604,6 +1095,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.Scrollbars == ScrollbarType::Horizontal || listView.Scrollbars == ScrollbarType::Both) + { + height -= SCROLLBAR_WIDTH + 1; + } + if (listView.Scrollbars == ScrollbarType::Vertical || listView.Scrollbars == 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 +1155,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 < info.ListViews.size()) + { + info.ListViews[scrollIndex].Paint(w, dpi, &w->scrolls[scrollIndex]); + } + } + static std::optional GetViewportWidgetIndex(rct_window* w) { rct_widgetindex widgetIndex = 0; @@ -782,6 +1304,12 @@ 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.text = SCROLL_VERTICAL; + widgetList.push_back(widget); + } else if (desc.Type == "spinner") { widget.type = WWT_SPINNER; @@ -827,6 +1355,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 +1406,16 @@ namespace OpenRCT2::Ui::Windows { info.WidgetIndexMap.push_back(widgetDescIndex); } + + if (widgetDesc.Type == "listview") + { + CustomListViewInfo listView; + listView.Columns = widgetDesc.ListViewColumns; + listView.Items = widgetDesc.ListViewItems; + listView.ShowColumnHeaders = widgetDesc.ShowColumnHeaders; + listView.IsStriped = widgetDesc.IsStriped; + info.ListViews.push_back(std::move(listView)); + } } for (size_t i = firstCustomWidgetIndex; i < widgets.size(); i++) diff --git a/src/openrct2/world/Location.hpp b/src/openrct2/world/Location.hpp index 22aa3243e9..d2222e66d6 100644 --- a/src/openrct2/world/Location.hpp +++ b/src/openrct2/world/Location.hpp @@ -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. From 928bba97952f2bc1a4a018bc2bdc19299c46283f Mon Sep 17 00:00:00 2001 From: Ted John Date: Sun, 3 May 2020 16:29:15 +0100 Subject: [PATCH 02/16] Implement sorting --- distribution/openrct2.d.ts | 2 +- src/openrct2-ui/scripting/CustomWindow.cpp | 260 ++++++++++++++++----- 2 files changed, 197 insertions(+), 65 deletions(-) diff --git a/distribution/openrct2.d.ts b/distribution/openrct2.d.ts index 3096912c53..f21db9fb60 100644 --- a/distribution/openrct2.d.ts +++ b/distribution/openrct2.d.ts @@ -1099,7 +1099,7 @@ declare global { highlightedIndex?: number; onHighlight: (index: number) => void; - onSelect: (index: number) => void; + onClick: (index: number) => void; getCell(row: number, column: number): string; setCell(row: number, column: number, value: string): void; diff --git a/src/openrct2-ui/scripting/CustomWindow.cpp b/src/openrct2-ui/scripting/CustomWindow.cpp index 56f0692f4e..a1be644d16 100644 --- a/src/openrct2-ui/scripting/CustomWindow.cpp +++ b/src/openrct2-ui/scripting/CustomWindow.cpp @@ -22,6 +22,7 @@ # include # include # include +# include # include # include # include @@ -122,7 +123,7 @@ namespace OpenRCT2::Scripting result.MaxWidth = result.MinWidth; result.RatioWidth = std::nullopt; } - else + else if (!result.RatioWidth) { result.RatioWidth = 1; } @@ -174,6 +175,7 @@ namespace OpenRCT2::Ui::Windows 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); @@ -198,7 +200,7 @@ namespace OpenRCT2::Ui::Windows nullptr, window_custom_scrollgetsize, window_custom_scrollmousedown, - nullptr, + window_custom_scrollmousedrag, window_custom_scrollmouseover, nullptr, nullptr, @@ -237,6 +239,7 @@ namespace OpenRCT2::Ui::Windows DukValue OnChange; DukValue OnIncrement; DukValue OnDecrement; + DukValue OnHighlight; static CustomWidgetDesc FromDukValue(DukValue desc) { @@ -304,6 +307,8 @@ namespace OpenRCT2::Ui::Windows } result.ShowColumnHeaders = AsOrDefault(desc["showColumnHeaders"], false); result.IsStriped = AsOrDefault(desc["isStriped"], false); + result.OnClick = desc["onClick"]; + result.OnHighlight = desc["onHighlight"]; } else if (result.Type == "spinner") { @@ -441,38 +446,115 @@ 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: - struct HitTestResult - { - static constexpr size_t HEADER_ROW = std::numeric_limits::max(); - - size_t Row{}; - size_t Column{}; - - bool IsHeader() const - { - return Row == HEADER_ROW; - } - }; + static constexpr int32_t HEADER_ROW = -1; + std::vector Items; public: std::shared_ptr Owner; std::vector Columns; - std::vector Items; - std::optional HighlightedRow; - std::optional HighlightedColumn; - std::optional SelectedRow; - std::optional SelectedColumn; + std::vector SortedItems; + std::optional HighlightedCell; + std::optional LastHighlightedCell; std::optional ColumnHeaderPressed; + bool ColumnHeaderPressedCurrentState{}; bool ShowColumnHeaders{}; bool IsStriped{}; ScreenSize LastKnownSize; ScrollbarType Scrollbars = ScrollbarType::Vertical; + ColumnSortOrder CurrentSortOrder{}; + size_t CurrentSortColumn{}; + bool LastIsMouseDown{}; + bool IsMouseDown{}; + DukValue OnClick; DukValue OnHighlight; - DukValue OnSelect; + + void SetItems(const std::vector& items) + { + Items = items; + SortItems(0, ColumnSortOrder::None); + } + + void SetItems(std::vector&& 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) { @@ -503,7 +585,8 @@ namespace OpenRCT2::Ui::Windows } else { - if (column.RatioWidth) + column.Width = 0; + if (column.RatioWidth && *column.RatioWidth > 0) { column.Width = (size.width * *column.RatioWidth) / totalRatio; } @@ -520,36 +603,58 @@ namespace OpenRCT2::Ui::Windows } } - ScreenSize GetSize() const + ScreenSize GetSize() { + LastHighlightedCell = HighlightedCell; + HighlightedCell = std::nullopt; + ColumnHeaderPressedCurrentState = false; + LastIsMouseDown = IsMouseDown; + IsMouseDown = false; + ScreenSize result; result.width = 0; result.height = static_cast(Items.size() * LIST_ROW_HEIGHT); return result; } - void MouseOver(const ScreenCoordsXY& pos) + void MouseOver(const ScreenCoordsXY& pos, bool isMouseDown) { auto hitResult = GetItemIndexAt(pos); if (hitResult) { - if (HighlightedRow != hitResult->Row || HighlightedColumn != hitResult->Column) + HighlightedCell = hitResult; + if (HighlightedCell != LastHighlightedCell) { - HighlightedRow = hitResult->Row; - HighlightedColumn = hitResult->Column; - if (!hitResult->IsHeader() && OnHighlight.context() != nullptr && OnHighlight.is_function()) + if (hitResult->Row != HEADER_ROW && OnHighlight.context() != nullptr && OnHighlight.is_function()) { auto ctx = OnHighlight.context(); - duk_push_int(ctx, static_cast(*HighlightedRow)); + duk_push_int(ctx, static_cast(HighlightedCell->Row)); auto dukRow = DukValue::take_from_stack(ctx, -1); - duk_push_int(ctx, static_cast(*HighlightedColumn)); + duk_push_int(ctx, static_cast(HighlightedCell->Column)); auto dukColumn = DukValue::take_from_stack(ctx, -1); auto& scriptEngine = GetContext()->GetScriptEngine(); scriptEngine.ExecutePluginCall(Owner, OnHighlight, { dukRow, dukColumn }, false); } } } - ColumnHeaderPressed = std::nullopt; + + // 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) @@ -557,30 +662,41 @@ namespace OpenRCT2::Ui::Windows auto hitResult = GetItemIndexAt(pos); if (hitResult) { - if (SelectedRow != hitResult->Row || SelectedColumn != hitResult->Column) + if (hitResult->Row != HEADER_ROW && OnClick.context() != nullptr && OnClick.is_function()) { - SelectedRow = hitResult->Row; - SelectedColumn = hitResult->Column; - if (!hitResult->IsHeader() && OnSelect.context() != nullptr && OnSelect.is_function()) - { - auto ctx = OnSelect.context(); - duk_push_int(ctx, static_cast(*SelectedRow)); - auto dukRow = DukValue::take_from_stack(ctx, -1); - duk_push_int(ctx, static_cast(*SelectedColumn)); - auto dukColumn = DukValue::take_from_stack(ctx, -1); - auto& scriptEngine = GetContext()->GetScriptEngine(); - scriptEngine.ExecutePluginCall(Owner, OnSelect, { dukRow, dukColumn }, false); - } + auto ctx = OnClick.context(); + duk_push_int(ctx, static_cast(hitResult->Row)); + auto dukRow = DukValue::take_from_stack(ctx, -1); + duk_push_int(ctx, static_cast(hitResult->Column)); + auto dukColumn = DukValue::take_from_stack(ctx, -1); + auto& scriptEngine = GetContext()->GetScriptEngine(); + scriptEngine.ExecutePluginCall(Owner, OnClick, { dukRow, dukColumn }, false); } } - if (hitResult && hitResult->IsHeader()) + if (hitResult && hitResult->Row == HEADER_ROW) { - ColumnHeaderPressed = hitResult->Column; + if (Columns[hitResult->Column].CanSort) + { + ColumnHeaderPressed = hitResult->Column; + ColumnHeaderPressedCurrentState = true; + } } - else + IsMouseDown = true; + } + + void MouseUp(const ScreenCoordsXY& pos) + { + auto hitResult = GetItemIndexAt(pos); + if (hitResult && hitResult->Row == HEADER_ROW) { - ColumnHeaderPressed = std::nullopt; + 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 @@ -601,7 +717,7 @@ namespace OpenRCT2::Ui::Windows { // Background colour auto isStriped = IsStriped && (i & 1); - auto isHighlighted = i == HighlightedRow; + auto isHighlighted = (HighlightedCell && i == HighlightedCell->Row); if (isHighlighted) { gfx_filter_rect(dpi, dpi->x, y, dpi->x + dpi->width, y + (LIST_ROW_HEIGHT - 1), PALETTE_DARKEN_1); @@ -614,7 +730,8 @@ namespace OpenRCT2::Ui::Windows } // Columns - const auto& item = Items[i]; + const auto& itemIndex = SortedItems[i]; + const auto& item = Items[itemIndex]; if (Columns.size() == 0) { const auto& text = item.Cells[0]; @@ -661,10 +778,14 @@ namespace OpenRCT2::Ui::Windows auto columnWidth = column.Width; if (columnWidth != 0) { - bool isPressed = ColumnHeaderPressed == j; - PaintHeading( - w, dpi, { x, y }, { column.Width, LIST_ROW_HEIGHT }, column.Header, ColumnSortOrder::None, - isPressed); + 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; } } @@ -697,7 +818,7 @@ namespace OpenRCT2::Ui::Windows { auto ft = Formatter::Common(); ft.Add(STR_DOWN); - gfx_draw_string_right(dpi, STR_BLACK_STRING, gCommonFormatArgs, COLOUR_BLACK, pos.y + size.width - 1, pos.y); + gfx_draw_string_right(dpi, STR_BLACK_STRING, gCommonFormatArgs, COLOUR_BLACK, pos.x + size.width - 1, pos.y); } } @@ -713,16 +834,16 @@ namespace OpenRCT2::Ui::Windows gfx_draw_string_left_clipped(dpi, stringId, gCommonFormatArgs, COLOUR_BLACK, pos.x, pos.y, size.width); } - std::optional GetItemIndexAt(const ScreenCoordsXY& pos) + std::optional GetItemIndexAt(const ScreenCoordsXY& pos) { - std::optional result; + std::optional result; if (pos.x >= 0) { // Check if we pressed the header if (ShowColumnHeaders && pos.y >= 0 && pos.y < LIST_ROW_HEIGHT) { - result = HitTestResult(); - result->Row = HitTestResult::HEADER_ROW; + result = RowColumn(); + result->Row = HEADER_ROW; } else { @@ -731,8 +852,8 @@ namespace OpenRCT2::Ui::Windows int32_t row = (pos.y - firstY) / LIST_ROW_HEIGHT; if (row >= 0 && row < static_cast(Items.size())) { - result = HitTestResult(); - result->Row = static_cast(row); + result = RowColumn(); + result->Row = row; } } @@ -749,7 +870,7 @@ namespace OpenRCT2::Ui::Windows { if (pos.x < x) { - result->Column = c; + result->Column = static_cast(c); found = true; break; } @@ -1040,7 +1161,7 @@ namespace OpenRCT2::Ui::Windows static void window_custom_scrollgetsize(rct_window* w, int32_t scrollIndex, int32_t* width, int32_t* height) { - const auto& info = GetInfo(w); + auto& info = GetInfo(w); if (scrollIndex < info.ListViews.size()) { auto size = info.ListViews[scrollIndex].GetSize(); @@ -1058,12 +1179,21 @@ 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()) + { + 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 < info.ListViews.size()) { - info.ListViews[scrollIndex].MouseOver(screenCoords); + info.ListViews[scrollIndex].MouseOver(screenCoords, false); } } @@ -1411,9 +1541,11 @@ namespace OpenRCT2::Ui::Windows { CustomListViewInfo listView; listView.Columns = widgetDesc.ListViewColumns; - listView.Items = widgetDesc.ListViewItems; + listView.SetItems(widgetDesc.ListViewItems); listView.ShowColumnHeaders = widgetDesc.ShowColumnHeaders; listView.IsStriped = widgetDesc.IsStriped; + listView.OnClick = widgetDesc.OnClick; + listView.OnHighlight = widgetDesc.OnHighlight; info.ListViews.push_back(std::move(listView)); } } From 45a1184b0ea7dfd92068e2d66311d936825b13d0 Mon Sep 17 00:00:00 2001 From: Ted John Date: Sun, 3 May 2020 17:07:27 +0100 Subject: [PATCH 03/16] Implement selection --- distribution/openrct2.d.ts | 5 +++-- src/openrct2-ui/scripting/CustomWindow.cpp | 26 ++++++++++++++++++---- 2 files changed, 25 insertions(+), 6 deletions(-) diff --git a/distribution/openrct2.d.ts b/distribution/openrct2.d.ts index f21db9fb60..7f1c169bcb 100644 --- a/distribution/openrct2.d.ts +++ b/distribution/openrct2.d.ts @@ -1097,9 +1097,10 @@ declare global { items?: string[] | ListViewItem[]; selectedIndex?: number; highlightedIndex?: number; + canSelect?: boolean; - onHighlight: (index: number) => void; - onClick: (index: number) => void; + onHighlight: (item: number, column: number) => void; + onClick: (item: number, column: number) => void; getCell(row: number, column: number): string; setCell(row: number, column: number, value: string): void; diff --git a/src/openrct2-ui/scripting/CustomWindow.cpp b/src/openrct2-ui/scripting/CustomWindow.cpp index a1be644d16..17a2bf7368 100644 --- a/src/openrct2-ui/scripting/CustomWindow.cpp +++ b/src/openrct2-ui/scripting/CustomWindow.cpp @@ -233,6 +233,7 @@ namespace OpenRCT2::Ui::Windows bool HasBorder{}; bool ShowColumnHeaders{}; bool IsStriped{}; + bool CanSelect{}; // Event handlers DukValue OnClick; @@ -309,6 +310,7 @@ namespace OpenRCT2::Ui::Windows result.IsStriped = AsOrDefault(desc["isStriped"], false); result.OnClick = desc["onClick"]; result.OnHighlight = desc["onHighlight"]; + result.CanSelect = AsOrDefault(desc["canSelect"], false); } else if (result.Type == "spinner") { @@ -481,6 +483,7 @@ namespace OpenRCT2::Ui::Windows std::vector SortedItems; std::optional HighlightedCell; std::optional LastHighlightedCell; + std::optional SelectedCell; std::optional ColumnHeaderPressed; bool ColumnHeaderPressedCurrentState{}; bool ShowColumnHeaders{}; @@ -491,6 +494,7 @@ namespace OpenRCT2::Ui::Windows size_t CurrentSortColumn{}; bool LastIsMouseDown{}; bool IsMouseDown{}; + bool CanSelect{}; DukValue OnClick; DukValue OnHighlight; @@ -664,6 +668,11 @@ namespace OpenRCT2::Ui::Windows { 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(hitResult->Row)); auto dukRow = DukValue::take_from_stack(ctx, -1); @@ -715,13 +724,23 @@ namespace OpenRCT2::Ui::Windows 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 && i == HighlightedCell->Row); + 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( @@ -730,8 +749,6 @@ namespace OpenRCT2::Ui::Windows } // Columns - const auto& itemIndex = SortedItems[i]; - const auto& item = Items[itemIndex]; if (Columns.size() == 0) { const auto& text = item.Cells[0]; @@ -853,7 +870,7 @@ namespace OpenRCT2::Ui::Windows if (row >= 0 && row < static_cast(Items.size())) { result = RowColumn(); - result->Row = row; + result->Row = static_cast(SortedItems[row]); } } @@ -1546,6 +1563,7 @@ namespace OpenRCT2::Ui::Windows listView.IsStriped = widgetDesc.IsStriped; listView.OnClick = widgetDesc.OnClick; listView.OnHighlight = widgetDesc.OnHighlight; + listView.CanSelect = widgetDesc.CanSelect; info.ListViews.push_back(std::move(listView)); } } From faf59598e565c262452d8a2823ff5ecda74cac96 Mon Sep 17 00:00:00 2001 From: Ted John Date: Mon, 4 May 2020 00:05:56 +0100 Subject: [PATCH 04/16] Start working on exposing ListViewWidget --- src/openrct2-ui/scripting/ScWidget.hpp | 100 ++++++++++++++++----- src/openrct2-ui/scripting/ScWindow.hpp | 15 ++-- src/openrct2-ui/scripting/UiExtensions.cpp | 2 + 3 files changed, 90 insertions(+), 27 deletions(-) diff --git a/src/openrct2-ui/scripting/ScWidget.hpp b/src/openrct2-ui/scripting/ScWidget.hpp index ee112cfbed..daeee14751 100644 --- a/src/openrct2-ui/scripting/ScWidget.hpp +++ b/src/openrct2-ui/scripting/ScWidget.hpp @@ -26,7 +26,7 @@ namespace OpenRCT2::Scripting { class ScWidget { - private: + protected: rct_windowclass _class{}; rct_windownumber _number{}; rct_widgetindex _widgetIndex{}; @@ -39,6 +39,8 @@ namespace OpenRCT2::Scripting { } + static DukValue ToDuk(duk_context* ctx, rct_window* w, rct_widgetindex widgetIndex); + private: std::string type_get() const { @@ -190,24 +192,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()) @@ -278,11 +262,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 +299,81 @@ 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(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(ctx); + dukglue_register_property(ctx, &ScListViewWidget::isStriped_get, &ScListViewWidget::isStriped_set, "isStriped"); + } + + private: + bool isStriped_get() const + { + } + + void isStriped_set(bool value) + { + } + }; + + inline DukValue ScWidget::ToDuk(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(c, n, widgetIndex)); + case WWT_SCROLL: + return GetObjectAsDukValue(ctx, std::make_shared(c, n, widgetIndex)); + default: + return GetObjectAsDukValue(ctx, std::make_shared(c, n, widgetIndex)); + } + } + } // namespace OpenRCT2::Scripting #endif diff --git a/src/openrct2-ui/scripting/ScWindow.hpp b/src/openrct2-ui/scripting/ScWindow.hpp index 1efab2166c..1f2dd412db 100644 --- a/src/openrct2-ui/scripting/ScWindow.hpp +++ b/src/openrct2-ui/scripting/ScWindow.hpp @@ -177,16 +177,18 @@ namespace OpenRCT2::Scripting return (flags & (WF_STICK_TO_BACK | WF_STICK_TO_FRONT)) != 0; } - std::vector> widgets_get() const + std::vector widgets_get() const { - std::vector> result; + auto ctx = GetContext()->GetScriptEngine().GetContext(); + + std::vector 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(_class, _number, widgetIndex)); + result.push_back(ScWidget::ToDuk(ctx, w, widgetIndex)); widgetIndex++; } } @@ -257,18 +259,19 @@ namespace OpenRCT2::Scripting } } - std::shared_ptr 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(_class, _number, *widgetIndex); + return ScWidget::ToDuk(ctx, w, *widgetIndex); } } - return {}; + return GetObjectAsDukValue(ctx, nullptr); } void bringToFront() diff --git a/src/openrct2-ui/scripting/UiExtensions.cpp b/src/openrct2-ui/scripting/UiExtensions.cpp index 54a00df453..446412c8ad 100644 --- a/src/openrct2-ui/scripting/UiExtensions.cpp +++ b/src/openrct2-ui/scripting/UiExtensions.cpp @@ -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); From 5e427413a6bdb45073239cd2cd984a51f61a0f45 Mon Sep 17 00:00:00 2001 From: Ted John Date: Wed, 6 May 2020 20:39:57 +0100 Subject: [PATCH 05/16] Refactor list view so we can access it from ScWidget --- .vscode/c_cpp_properties.json | 2 +- src/openrct2-ui/scripting/CustomListView.cpp | 491 +++++++++++++++ src/openrct2-ui/scripting/CustomListView.h | 154 +++++ src/openrct2-ui/scripting/CustomWindow.cpp | 609 +------------------ src/openrct2-ui/scripting/CustomWindow.h | 3 + src/openrct2-ui/scripting/ScWidget.hpp | 22 + src/openrct2/scripting/Duktape.hpp | 2 + src/openrct2/scripting/ScriptEngine.cpp | 7 + 8 files changed, 706 insertions(+), 584 deletions(-) create mode 100644 src/openrct2-ui/scripting/CustomListView.cpp create mode 100644 src/openrct2-ui/scripting/CustomListView.h diff --git a/.vscode/c_cpp_properties.json b/.vscode/c_cpp_properties.json index e073544f69..24a763966c 100644 --- a/.vscode/c_cpp_properties.json +++ b/.vscode/c_cpp_properties.json @@ -65,7 +65,7 @@ "_DEBUG", "UNICODE", "_UNICODE", - "__ENABLE_SCRIPTING__" + "ENABLE_SCRIPTING" ], "intelliSenseMode": "msvc-x64", "browse": { diff --git a/src/openrct2-ui/scripting/CustomListView.cpp b/src/openrct2-ui/scripting/CustomListView.cpp new file mode 100644 index 0000000000..5d8b02610d --- /dev/null +++ b/src/openrct2-ui/scripting/CustomListView.cpp @@ -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 +# include +# include + +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 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(d["sortOrder"]); + result.Header = AsOrDefault(d["header"], ""); + result.HeaderTooltip = AsOrDefault(d["headerTooltip"], ""); + result.MinWidth = FromDuk>(d["minWidth"]); + result.MaxWidth = FromDuk>(d["maxWidth"]); + result.RatioWidth = FromDuk>(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 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& items) +{ + Items = items; + SortItems(0, ColumnSortOrder::None); +} + +void CustomListView::SetItems(std::vector&& 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(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(HighlightedCell->Row)); + auto dukRow = DukValue::take_from_stack(ctx, -1); + duk_push_int(ctx, static_cast(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(hitResult->Row)); + auto dukRow = DukValue::take_from_stack(ctx, -1); + duk_push_int(ctx, static_cast(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(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::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(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(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(STR_STRING); + ft.Add(text); + gfx_draw_string_left_clipped(dpi, stringId, gCommonFormatArgs, COLOUR_BLACK, pos.x, pos.y, size.width); +} + +std::optional CustomListView::GetItemIndexAt(const ScreenCoordsXY& pos) +{ + std::optional 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(Items.size())) + { + result = RowColumn(); + result->Row = static_cast(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(c); + found = true; + break; + } + } + } + if (!found) + { + // Past all columns + return std::nullopt; + } + } + } + return result; +} + +#endif diff --git a/src/openrct2-ui/scripting/CustomListView.h b/src/openrct2-ui/scripting/CustomListView.h new file mode 100644 index 0000000000..dda7c69825 --- /dev/null +++ b/src/openrct2-ui/scripting/CustomListView.h @@ -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 +# include +# include +# include +# include +# include +# include + +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 RatioWidth{}; + std::optional MinWidth{}; + std::optional MaxWidth{}; + int32_t Width{}; + }; + + struct ListViewItem + { + std::vector Cells; + + ListViewItem() = default; + explicit ListViewItem(const std::string_view& text) + { + Cells.emplace_back(text); + } + explicit ListViewItem(std::vector&& 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 Items; + + public: + std::shared_ptr Owner; + std::vector Columns; + std::vector SortedItems; + std::optional HighlightedCell; + std::optional LastHighlightedCell; + std::optional SelectedCell; + std::optional 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& items); + void SetItems(std::vector&& 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 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 FromDuk(const DukValue& d); + template<> ListViewColumn FromDuk(const DukValue& d); + template<> ListViewItem FromDuk(const DukValue& d); +} // namespace OpenRCT2::Scripting + +#endif diff --git a/src/openrct2-ui/scripting/CustomWindow.cpp b/src/openrct2-ui/scripting/CustomWindow.cpp index 17a2bf7368..992d0d8c7c 100644 --- a/src/openrct2-ui/scripting/CustomWindow.cpp +++ b/src/openrct2-ui/scripting/CustomWindow.cpp @@ -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 # include # include -# include # include # include # include @@ -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 RatioWidth{}; - std::optional MinWidth{}; - std::optional MaxWidth{}; - int32_t Width{}; - }; - - struct ListViewItem - { - std::vector Cells; - - ListViewItem() = default; - explicit ListViewItem(const std::string_view& text) - { - Cells.emplace_back(text); - } - explicit ListViewItem(std::vector&& 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 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(d["sortOrder"]); - result.Header = AsOrDefault(d["header"], ""); - result.HeaderTooltip = AsOrDefault(d["headerTooltip"], ""); - result.MinWidth = FromDuk>(d["minWidth"]); - result.MaxWidth = FromDuk>(d["maxWidth"]); - result.RatioWidth = FromDuk>(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 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 Items; - - public: - std::shared_ptr Owner; - std::vector Columns; - std::vector SortedItems; - std::optional HighlightedCell; - std::optional LastHighlightedCell; - std::optional SelectedCell; - std::optional 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& items) - { - Items = items; - SortItems(0, ColumnSortOrder::None); - } - - void SetItems(std::vector&& 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(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(HighlightedCell->Row)); - auto dukRow = DukValue::take_from_stack(ctx, -1); - duk_push_int(ctx, static_cast(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(hitResult->Row)); - auto dukRow = DukValue::take_from_stack(ctx, -1); - duk_push_int(ctx, static_cast(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::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(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(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(STR_STRING); - ft.Add(text); - gfx_draw_string_left_clipped(dpi, stringId, gCommonFormatArgs, COLOUR_BLACK, pos.x, pos.y, size.width); - } - - std::optional GetItemIndexAt(const ScreenCoordsXY& pos) - { - std::optional 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(Items.size())) - { - result = RowColumn(); - result->Row = static_cast(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(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 Widgets; std::vector WidgetIndexMap; - std::vector ListViews; + std::vector ListViews; CustomWindowInfo(std::shared_ptr 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(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(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(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(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(info.ListViews.size())) { info.ListViews[scrollIndex].Paint(w, dpi, &w->scrolls[scrollIndex]); } @@ -1421,6 +846,10 @@ namespace OpenRCT2::Ui::Windows { widget.string = const_cast(desc.Items[desc.SelectedIndex].c_str()); } + else + { + widget.string = const_cast(""); + } 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(info.ListViews.size())) + { + return &customInfo.ListViews[scrollIndex]; + } + } + return nullptr; + } + } // namespace OpenRCT2::Ui::Windows #endif diff --git a/src/openrct2-ui/scripting/CustomWindow.h b/src/openrct2-ui/scripting/CustomWindow.h index 5877673780..3a1bd2ceb4 100644 --- a/src/openrct2-ui/scripting/CustomWindow.h +++ b/src/openrct2-ui/scripting/CustomWindow.h @@ -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 FindWidgetIndexByName(rct_window* w, const std::string_view& name); + CustomListView* GetCustomListView(rct_window* w, rct_widgetindex widgetIndex); } // namespace OpenRCT2::Ui::Windows #endif diff --git a/src/openrct2-ui/scripting/ScWidget.hpp b/src/openrct2-ui/scripting/ScWidget.hpp index daeee14751..26987c71d0 100644 --- a/src/openrct2-ui/scripting/ScWidget.hpp +++ b/src/openrct2-ui/scripting/ScWidget.hpp @@ -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; } }; diff --git a/src/openrct2/scripting/Duktape.hpp b/src/openrct2/scripting/Duktape.hpp index 43d6ba3b53..02ec125821 100644 --- a/src/openrct2/scripting/Duktape.hpp +++ b/src/openrct2/scripting/Duktape.hpp @@ -190,6 +190,8 @@ namespace OpenRCT2::Scripting } } + std::string ProcessString(const DukValue& value); + template DukValue ToDuk(duk_context* ctx, const T& value) = delete; template T FromDuk(const DukValue& s) = delete; diff --git a/src/openrct2/scripting/ScriptEngine.cpp b/src/openrct2/scripting/ScriptEngine.cpp index fdb7f3dbec..8474d8b918 100644 --- a/src/openrct2/scripting/ScriptEngine.cpp +++ b/src/openrct2/scripting/ScriptEngine.cpp @@ -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 From 4701dd3b9b281246480cd1ec9bcc906cb0aa0d9b Mon Sep 17 00:00:00 2001 From: Ted John Date: Wed, 6 May 2020 22:34:56 +0100 Subject: [PATCH 06/16] Add get / set highlighted cell / selected cell --- distribution/openrct2.d.ts | 9 +++-- src/openrct2-ui/scripting/CustomListView.cpp | 22 ++++++++++++ src/openrct2-ui/scripting/CustomListView.h | 2 ++ src/openrct2-ui/scripting/CustomWindow.cpp | 4 +-- src/openrct2-ui/scripting/ScWidget.hpp | 38 ++++++++++++++++++-- src/openrct2-ui/scripting/ScWindow.hpp | 4 +-- src/openrct2/scripting/Duktape.hpp | 9 +++++ 7 files changed, 80 insertions(+), 8 deletions(-) diff --git a/distribution/openrct2.d.ts b/distribution/openrct2.d.ts index 7f1c169bcb..988652654f 100644 --- a/distribution/openrct2.d.ts +++ b/distribution/openrct2.d.ts @@ -1089,14 +1089,19 @@ declare global { type ListViewItem = string[]; + interface RowColumn { + row: number; + column: number; + } + interface ListView extends Widget { scroll?: ScrollType; isStriped?: boolean; showColumnHeaders?: boolean; columns?: ListViewColumn[]; items?: string[] | ListViewItem[]; - selectedIndex?: number; - highlightedIndex?: number; + selectedCell?: RowColumn; + readonly highlightedCell?: RowColumn; canSelect?: boolean; onHighlight: (item: number, column: number) => void; diff --git a/src/openrct2-ui/scripting/CustomListView.cpp b/src/openrct2-ui/scripting/CustomListView.cpp index 5d8b02610d..c4df956ce0 100644 --- a/src/openrct2-ui/scripting/CustomListView.cpp +++ b/src/openrct2-ui/scripting/CustomListView.cpp @@ -85,6 +85,28 @@ namespace OpenRCT2::Scripting } return result; } + + template<> std::optional 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(); + } } // namespace OpenRCT2::Scripting void CustomListView::SetItems(const std::vector& items) diff --git a/src/openrct2-ui/scripting/CustomListView.h b/src/openrct2-ui/scripting/CustomListView.h index dda7c69825..729d21a7ba 100644 --- a/src/openrct2-ui/scripting/CustomListView.h +++ b/src/openrct2-ui/scripting/CustomListView.h @@ -149,6 +149,8 @@ namespace OpenRCT2::Scripting template<> std::optional FromDuk(const DukValue& d); template<> ListViewColumn FromDuk(const DukValue& d); template<> ListViewItem FromDuk(const DukValue& d); + template<> std::optional FromDuk(const DukValue& d); + template<> DukValue ToDuk(duk_context* ctx, const RowColumn& value); } // namespace OpenRCT2::Scripting #endif diff --git a/src/openrct2-ui/scripting/CustomWindow.cpp b/src/openrct2-ui/scripting/CustomWindow.cpp index 992d0d8c7c..0b1fa7b928 100644 --- a/src/openrct2-ui/scripting/CustomWindow.cpp +++ b/src/openrct2-ui/scripting/CustomWindow.cpp @@ -1106,11 +1106,11 @@ namespace OpenRCT2::Ui::Windows { if (w->custom_info != nullptr) { - auto& customInfo = GetInfo(w); + auto& info = GetInfo(w); auto scrollIndex = window_get_scroll_data_index(w, widgetIndex); if (scrollIndex < static_cast(info.ListViews.size())) { - return &customInfo.ListViews[scrollIndex]; + return &info.ListViews[scrollIndex]; } } return nullptr; diff --git a/src/openrct2-ui/scripting/ScWidget.hpp b/src/openrct2-ui/scripting/ScWidget.hpp index 26987c71d0..85912da76b 100644 --- a/src/openrct2-ui/scripting/ScWidget.hpp +++ b/src/openrct2-ui/scripting/ScWidget.hpp @@ -40,7 +40,7 @@ namespace OpenRCT2::Scripting { } - static DukValue ToDuk(duk_context* ctx, rct_window* w, rct_widgetindex widgetIndex); + static DukValue ToDukValue(duk_context* ctx, rct_window* w, rct_widgetindex widgetIndex); private: std::string type_get() const @@ -347,6 +347,9 @@ namespace OpenRCT2::Scripting { dukglue_set_base_class(ctx); dukglue_register_property(ctx, &ScListViewWidget::isStriped_get, &ScListViewWidget::isStriped_set, "isStriped"); + dukglue_register_property(ctx, &ScListViewWidget::highlightedCell_get, nullptr, "highlightedCell"); + dukglue_register_property( + ctx, &ScListViewWidget::selectedCell_get, &ScListViewWidget::selectedCell_set, "selectedCell"); } private: @@ -369,6 +372,37 @@ namespace OpenRCT2::Scripting } } + 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>(value); + } + } + CustomListView* GetListView() const { auto w = GetWindow(); @@ -380,7 +414,7 @@ namespace OpenRCT2::Scripting } }; - inline DukValue ScWidget::ToDuk(duk_context* ctx, rct_window* w, rct_widgetindex widgetIndex) + inline DukValue ScWidget::ToDukValue(duk_context* ctx, rct_window* w, rct_widgetindex widgetIndex) { const auto& widget = w->widgets[widgetIndex]; auto c = w->classification; diff --git a/src/openrct2-ui/scripting/ScWindow.hpp b/src/openrct2-ui/scripting/ScWindow.hpp index 1f2dd412db..106b82281b 100644 --- a/src/openrct2-ui/scripting/ScWindow.hpp +++ b/src/openrct2-ui/scripting/ScWindow.hpp @@ -188,7 +188,7 @@ namespace OpenRCT2::Scripting rct_widgetindex widgetIndex = 0; for (auto widget = w->widgets; widget->type != WWT_LAST; widget++) { - result.push_back(ScWidget::ToDuk(ctx, w, widgetIndex)); + result.push_back(ScWidget::ToDukValue(ctx, w, widgetIndex)); widgetIndex++; } } @@ -268,7 +268,7 @@ namespace OpenRCT2::Scripting auto widgetIndex = FindWidgetIndexByName(w, name); if (widgetIndex) { - return ScWidget::ToDuk(ctx, w, *widgetIndex); + return ScWidget::ToDukValue(ctx, w, *widgetIndex); } } return GetObjectAsDukValue(ctx, nullptr); diff --git a/src/openrct2/scripting/Duktape.hpp b/src/openrct2/scripting/Duktape.hpp index 02ec125821..c36849ac4d 100644 --- a/src/openrct2/scripting/Duktape.hpp +++ b/src/openrct2/scripting/Duktape.hpp @@ -194,6 +194,15 @@ namespace OpenRCT2::Scripting template DukValue ToDuk(duk_context* ctx, const T& value) = delete; template T FromDuk(const DukValue& s) = delete; + template<> inline DukValue ToDuk(duk_context* ctx, const std::nullptr_t&) + { + duk_push_null(ctx); + return DukValue::take_from_stack(ctx); + } + template DukValue ToDuk(duk_context* ctx, const std::optional& value) + { + return value ? ToDuk(ctx, *value) : ToDuk(ctx, nullptr); + } template<> inline DukValue ToDuk(duk_context* ctx, const std::nullptr_t&) { From e63fcd010fff0dd13e4103e08e299c60b12c0fbf Mon Sep 17 00:00:00 2001 From: Ted John Date: Wed, 6 May 2020 22:46:03 +0100 Subject: [PATCH 07/16] Add name get / set to widget --- src/openrct2-ui/scripting/CustomListView.h | 2 +- src/openrct2-ui/scripting/CustomWindow.cpp | 27 ++++++++++++++++++++++ src/openrct2-ui/scripting/CustomWindow.h | 2 ++ src/openrct2-ui/scripting/ScWidget.hpp | 20 ++++++++++++++++ 4 files changed, 50 insertions(+), 1 deletion(-) diff --git a/src/openrct2-ui/scripting/CustomListView.h b/src/openrct2-ui/scripting/CustomListView.h index 729d21a7ba..69af8b3eaa 100644 --- a/src/openrct2-ui/scripting/CustomListView.h +++ b/src/openrct2-ui/scripting/CustomListView.h @@ -101,7 +101,7 @@ namespace OpenRCT2::Ui::Windows std::optional HighlightedCell; std::optional LastHighlightedCell; std::optional SelectedCell; - std::optional ColumnHeaderPressed; + std::optional ColumnHeaderPressed; bool ColumnHeaderPressedCurrentState{}; bool ShowColumnHeaders{}; bool IsStriped{}; diff --git a/src/openrct2-ui/scripting/CustomWindow.cpp b/src/openrct2-ui/scripting/CustomWindow.cpp index 0b1fa7b928..4dab81905f 100644 --- a/src/openrct2-ui/scripting/CustomWindow.cpp +++ b/src/openrct2-ui/scripting/CustomWindow.cpp @@ -1102,6 +1102,33 @@ 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) diff --git a/src/openrct2-ui/scripting/CustomWindow.h b/src/openrct2-ui/scripting/CustomWindow.h index 3a1bd2ceb4..a08a89984e 100644 --- a/src/openrct2-ui/scripting/CustomWindow.h +++ b/src/openrct2-ui/scripting/CustomWindow.h @@ -25,6 +25,8 @@ namespace OpenRCT2::Ui::Windows void UpdateWidgetText(rct_window* w, rct_widgetindex widget, const std::string_view& string_view); rct_window* FindCustomWindowByClassification(const std::string_view& classification); std::optional 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 diff --git a/src/openrct2-ui/scripting/ScWidget.hpp b/src/openrct2-ui/scripting/ScWidget.hpp index 85912da76b..b08a638352 100644 --- a/src/openrct2-ui/scripting/ScWidget.hpp +++ b/src/openrct2-ui/scripting/ScWidget.hpp @@ -43,6 +43,25 @@ 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(); @@ -253,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"); From 09416209914cefe391bcfa9d9a7535d2f48ebdd8 Mon Sep 17 00:00:00 2001 From: Ted John Date: Wed, 6 May 2020 22:48:48 +0100 Subject: [PATCH 08/16] Add a few more properties --- src/openrct2-ui/scripting/ScWidget.hpp | 41 ++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/src/openrct2-ui/scripting/ScWidget.hpp b/src/openrct2-ui/scripting/ScWidget.hpp index b08a638352..55537a455f 100644 --- a/src/openrct2-ui/scripting/ScWidget.hpp +++ b/src/openrct2-ui/scripting/ScWidget.hpp @@ -366,13 +366,35 @@ namespace OpenRCT2::Scripting static void Register(duk_context* ctx) { dukglue_set_base_class(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::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"); } 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(); @@ -392,6 +414,25 @@ namespace OpenRCT2::Scripting } } + 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(); From 12fb6411b1351209a827ff23996bb67276a95b82 Mon Sep 17 00:00:00 2001 From: Ted John Date: Wed, 6 May 2020 23:09:31 +0100 Subject: [PATCH 09/16] Fix 32-bit errors --- src/openrct2-ui/scripting/CustomListView.cpp | 8 ++++---- src/openrct2-ui/scripting/CustomListView.h | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/openrct2-ui/scripting/CustomListView.cpp b/src/openrct2-ui/scripting/CustomListView.cpp index c4df956ce0..35d38a6d1e 100644 --- a/src/openrct2-ui/scripting/CustomListView.cpp +++ b/src/openrct2-ui/scripting/CustomListView.cpp @@ -121,14 +121,14 @@ void CustomListView::SetItems(std::vector&& items) SortItems(0, ColumnSortOrder::None); } -bool CustomListView::SortItem(size_t indexA, size_t indexB, size_t column) +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(size_t column) +void CustomListView::SortItems(int32_t column) { auto sortOrder = ColumnSortOrder::Ascending; if (CurrentSortColumn == column) @@ -145,7 +145,7 @@ void CustomListView::SortItems(size_t column) SortItems(column, sortOrder); } -void CustomListView::SortItems(size_t column, ColumnSortOrder order) +void CustomListView::SortItems(int32_t column, ColumnSortOrder order) { // Reset the sorted index map SortedItems.resize(Items.size()); @@ -398,7 +398,7 @@ void CustomListView::Paint(rct_window* w, rct_drawpixelinfo* dpi, const rct_scro 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++) + for (int32_t j = 0; j < static_cast(Columns.size()); j++) { const auto& column = Columns[j]; auto columnWidth = column.Width; diff --git a/src/openrct2-ui/scripting/CustomListView.h b/src/openrct2-ui/scripting/CustomListView.h index 69af8b3eaa..343dd7b88c 100644 --- a/src/openrct2-ui/scripting/CustomListView.h +++ b/src/openrct2-ui/scripting/CustomListView.h @@ -108,7 +108,7 @@ namespace OpenRCT2::Ui::Windows ScreenSize LastKnownSize; ScrollbarType Scrollbars = ScrollbarType::Vertical; ColumnSortOrder CurrentSortOrder{}; - size_t CurrentSortColumn{}; + int32_t CurrentSortColumn{}; bool LastIsMouseDown{}; bool IsMouseDown{}; bool CanSelect{}; @@ -118,9 +118,9 @@ namespace OpenRCT2::Ui::Windows void SetItems(const std::vector& items); void SetItems(std::vector&& items); - bool SortItem(size_t indexA, size_t indexB, size_t column); - void SortItems(size_t column); - void SortItems(size_t column, ColumnSortOrder order); + 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); From c4f6dbe1cac58998c22b06029cb1a5da37b10173 Mon Sep 17 00:00:00 2001 From: Ted John Date: Thu, 7 May 2020 00:02:28 +0100 Subject: [PATCH 10/16] Allow items and columns get / set --- src/openrct2-ui/scripting/CustomListView.cpp | 74 ++++++++++++++++++++ src/openrct2-ui/scripting/CustomListView.h | 8 ++- src/openrct2-ui/scripting/CustomWindow.cpp | 20 +----- src/openrct2-ui/scripting/ScWidget.hpp | 49 +++++++++++++ src/openrct2/scripting/Duktape.hpp | 24 +++++++ 5 files changed, 157 insertions(+), 18 deletions(-) diff --git a/src/openrct2-ui/scripting/CustomListView.cpp b/src/openrct2-ui/scripting/CustomListView.cpp index 35d38a6d1e..bda9302dea 100644 --- a/src/openrct2-ui/scripting/CustomListView.cpp +++ b/src/openrct2-ui/scripting/CustomListView.cpp @@ -35,6 +35,19 @@ namespace OpenRCT2::Scripting 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 FromDuk(const DukValue& d) { if (d.type() == DukValue::Type::NUMBER) @@ -67,6 +80,20 @@ namespace OpenRCT2::Scripting 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; @@ -86,6 +113,34 @@ namespace OpenRCT2::Scripting return result; } + template<> std::vector FromDuk(const DukValue& d) + { + std::vector result; + if (d.is_array()) + { + auto dukColumns = d.as_array(); + for (const auto& dukColumn : dukColumns) + { + result.push_back(FromDuk(dukColumn)); + } + } + return result; + } + + template<> std::vector FromDuk(const DukValue& d) + { + std::vector result; + if (d.is_array()) + { + auto dukItems = d.as_array(); + for (const auto& dukItem : dukItems) + { + result.push_back(FromDuk(dukItem)); + } + } + return result; + } + template<> std::optional FromDuk(const DukValue& d) { if (d.type() == DukValue::Type::OBJECT) @@ -109,8 +164,27 @@ namespace OpenRCT2::Scripting } } // namespace OpenRCT2::Scripting +const std::vector& CustomListView::GetColumns() const +{ + return Columns; +} + +void CustomListView::SetColumns(const std::vector& columns) +{ + SelectedCell = std::nullopt; + Columns = columns; + LastKnownSize = {}; + SortItems(0, ColumnSortOrder::None); +} + +const std::vector& CustomListView::CustomListView::GetItems() const +{ + return Items; +} + void CustomListView::SetItems(const std::vector& items) { + SelectedCell = std::nullopt; Items = items; SortItems(0, ColumnSortOrder::None); } diff --git a/src/openrct2-ui/scripting/CustomListView.h b/src/openrct2-ui/scripting/CustomListView.h index 343dd7b88c..c4e9f69395 100644 --- a/src/openrct2-ui/scripting/CustomListView.h +++ b/src/openrct2-ui/scripting/CustomListView.h @@ -92,11 +92,11 @@ namespace OpenRCT2::Ui::Windows { private: static constexpr int32_t HEADER_ROW = -1; + std::vector Columns; std::vector Items; public: std::shared_ptr Owner; - std::vector Columns; std::vector SortedItems; std::optional HighlightedCell; std::optional LastHighlightedCell; @@ -116,6 +116,9 @@ namespace OpenRCT2::Ui::Windows DukValue OnClick; DukValue OnHighlight; + const std::vector& GetColumns() const; + void SetColumns(const std::vector& columns); + const std::vector& GetItems() const; void SetItems(const std::vector& items); void SetItems(std::vector&& items); bool SortItem(size_t indexA, size_t indexB, int32_t column); @@ -149,8 +152,11 @@ namespace OpenRCT2::Scripting template<> std::optional FromDuk(const DukValue& d); template<> ListViewColumn FromDuk(const DukValue& d); template<> ListViewItem FromDuk(const DukValue& d); + template<> std::vector FromDuk(const DukValue& d); + template<> std::vector FromDuk(const DukValue& d); template<> std::optional FromDuk(const DukValue& d); template<> DukValue ToDuk(duk_context* ctx, const RowColumn& value); + template<> DukValue ToDuk(duk_context* ctx, const ListViewColumn& value); } // namespace OpenRCT2::Scripting #endif diff --git a/src/openrct2-ui/scripting/CustomWindow.cpp b/src/openrct2-ui/scripting/CustomWindow.cpp index 4dab81905f..a94e51bf1c 100644 --- a/src/openrct2-ui/scripting/CustomWindow.cpp +++ b/src/openrct2-ui/scripting/CustomWindow.cpp @@ -171,22 +171,8 @@ namespace OpenRCT2::Ui::Windows } else if (result.Type == "listview") { - if (desc["columns"].is_array()) - { - auto dukColumns = desc["columns"].as_array(); - for (const auto& dukColumn : dukColumns) - { - result.ListViewColumns.push_back(FromDuk(dukColumn)); - } - } - if (desc["items"].is_array()) - { - auto dukItems = desc["items"].as_array(); - for (const auto& dukItem : dukItems) - { - result.ListViewItems.push_back(FromDuk(dukItem)); - } - } + result.ListViewColumns = FromDuk>(desc["columns"]); + result.ListViewItems = FromDuk>(desc["items"]); result.ShowColumnHeaders = AsOrDefault(desc["showColumnHeaders"], false); result.IsStriped = AsOrDefault(desc["isStriped"], false); result.OnClick = desc["onClick"]; @@ -986,7 +972,7 @@ namespace OpenRCT2::Ui::Windows if (widgetDesc.Type == "listview") { CustomListView listView; - listView.Columns = widgetDesc.ListViewColumns; + listView.SetColumns(widgetDesc.ListViewColumns); listView.SetItems(widgetDesc.ListViewItems); listView.ShowColumnHeaders = widgetDesc.ShowColumnHeaders; listView.IsStriped = widgetDesc.IsStriped; diff --git a/src/openrct2-ui/scripting/ScWidget.hpp b/src/openrct2-ui/scripting/ScWidget.hpp index 55537a455f..6677bc1796 100644 --- a/src/openrct2-ui/scripting/ScWidget.hpp +++ b/src/openrct2-ui/scripting/ScWidget.hpp @@ -373,6 +373,8 @@ namespace OpenRCT2::Scripting 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: @@ -464,6 +466,53 @@ namespace OpenRCT2::Scripting } } + std::vector> items_get() + { + std::vector> 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>(value)); + } + } + + std::vector columns_get() + { + std::vector 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>(value)); + } + } + CustomListView* GetListView() const { auto w = GetWindow(); diff --git a/src/openrct2/scripting/Duktape.hpp b/src/openrct2/scripting/Duktape.hpp index c36849ac4d..1a9eb0962d 100644 --- a/src/openrct2/scripting/Duktape.hpp +++ b/src/openrct2/scripting/Duktape.hpp @@ -113,6 +113,20 @@ namespace OpenRCT2::Scripting duk_put_prop_string(_ctx, _idx, name); } + template void Set(const char* name, const std::optional& value) + { + if (value) + { + EnsureObjectPushed(); + duk_push_null(_ctx); + duk_put_prop_string(_ctx, _idx, name); + } + else + { + Set(name, *value); + } + } + DukValue Take() { EnsureObjectPushed(); @@ -199,6 +213,16 @@ namespace OpenRCT2::Scripting duk_push_null(ctx); return DukValue::take_from_stack(ctx); } + template<> inline DukValue ToDuk(duk_context* ctx, const std::string_view& value) + { + duk_push_lstring(ctx, value.data(), value.size()); + return DukValue::take_from_stack(ctx); + } + template inline DukValue ToDuk(duk_context* ctx, const char (&value)[TLen]) + { + duk_push_string(ctx, value); + return DukValue::take_from_stack(ctx); + } template DukValue ToDuk(duk_context* ctx, const std::optional& value) { return value ? ToDuk(ctx, *value) : ToDuk(ctx, nullptr); From 5e7cf3bf9662448102e749c2912ecf89e0006f09 Mon Sep 17 00:00:00 2001 From: Ted John Date: Sat, 9 May 2020 14:30:06 +0100 Subject: [PATCH 11/16] Fix rebase --- src/openrct2/scripting/Duktape.hpp | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/src/openrct2/scripting/Duktape.hpp b/src/openrct2/scripting/Duktape.hpp index 1a9eb0962d..c3b0e8bf0a 100644 --- a/src/openrct2/scripting/Duktape.hpp +++ b/src/openrct2/scripting/Duktape.hpp @@ -208,38 +208,30 @@ namespace OpenRCT2::Scripting template DukValue ToDuk(duk_context* ctx, const T& value) = delete; template T FromDuk(const DukValue& s) = delete; + template<> inline DukValue ToDuk(duk_context* ctx, const std::nullptr_t&) { duk_push_null(ctx); return DukValue::take_from_stack(ctx); } + template<> inline DukValue ToDuk(duk_context* ctx, const std::string_view& value) { duk_push_lstring(ctx, value.data(), value.size()); return DukValue::take_from_stack(ctx); } + template inline DukValue ToDuk(duk_context* ctx, const char (&value)[TLen]) { duk_push_string(ctx, value); return DukValue::take_from_stack(ctx); } + template DukValue ToDuk(duk_context* ctx, const std::optional& value) { return value ? ToDuk(ctx, *value) : ToDuk(ctx, nullptr); } - template<> inline DukValue ToDuk(duk_context* ctx, const std::nullptr_t&) - { - duk_push_null(ctx); - return DukValue::take_from_stack(ctx); - } - - template<> inline DukValue ToDuk(duk_context* ctx, const std::string_view& value) - { - duk_push_lstring(ctx, value.data(), value.size()); - return DukValue::take_from_stack(ctx); - } - } // namespace OpenRCT2::Scripting #endif From 092fee076d8619b407090751f51bbc6c90787213 Mon Sep 17 00:00:00 2001 From: Ted John Date: Sat, 9 May 2020 14:42:57 +0100 Subject: [PATCH 12/16] Remove unimplemented APIs for list view --- distribution/openrct2.d.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/distribution/openrct2.d.ts b/distribution/openrct2.d.ts index 988652654f..ce6310469f 100644 --- a/distribution/openrct2.d.ts +++ b/distribution/openrct2.d.ts @@ -1106,9 +1106,6 @@ declare global { onHighlight: (item: number, column: number) => void; onClick: (item: number, column: number) => void; - - getCell(row: number, column: number): string; - setCell(row: number, column: number, value: string): void; } interface SpinnerWidget extends Widget { From 41950d75f08acd467dbebee5fdb3bd00ccb59684 Mon Sep 17 00:00:00 2001 From: Ted John Date: Sat, 9 May 2020 16:06:21 +0100 Subject: [PATCH 13/16] Implement scrollbars field on list view --- distribution/openrct2.d.ts | 4 +- src/openrct2-ui/scripting/CustomListView.cpp | 66 ++++++++++++++++++++ src/openrct2-ui/scripting/CustomListView.h | 10 ++- src/openrct2-ui/scripting/CustomWindow.cpp | 19 ++++-- src/openrct2-ui/scripting/ScWidget.hpp | 22 +++++++ 5 files changed, 114 insertions(+), 7 deletions(-) diff --git a/distribution/openrct2.d.ts b/distribution/openrct2.d.ts index ce6310469f..b2f6fc644a 100644 --- a/distribution/openrct2.d.ts +++ b/distribution/openrct2.d.ts @@ -1074,7 +1074,7 @@ declare global { type SortOrder = "none" | "ascending" | "descending"; - type ScrollType = "none" | "horizontal" | "vertical" | "both"; + type ScrollbarType = "none" | "horizontal" | "vertical" | "both"; interface ListViewColumn { canSort?: boolean; @@ -1095,7 +1095,7 @@ declare global { } interface ListView extends Widget { - scroll?: ScrollType; + scrollbars?: ScrollbarType; isStriped?: boolean; showColumnHeaders?: boolean; columns?: ListViewColumn[]; diff --git a/src/openrct2-ui/scripting/CustomListView.cpp b/src/openrct2-ui/scripting/CustomListView.cpp index bda9302dea..b66e10cc15 100644 --- a/src/openrct2-ui/scripting/CustomListView.cpp +++ b/src/openrct2-ui/scripting/CustomListView.cpp @@ -11,6 +11,7 @@ # include "CustomListView.h" +# include "../interface/Widget.h" # include "../interface/Window.h" # include @@ -162,8 +163,73 @@ namespace OpenRCT2::Scripting 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) +{ + Scrollbars = value; + + 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++; + } + } +} + const std::vector& CustomListView::GetColumns() const { return Columns; diff --git a/src/openrct2-ui/scripting/CustomListView.h b/src/openrct2-ui/scripting/CustomListView.h index c4e9f69395..068690a551 100644 --- a/src/openrct2-ui/scripting/CustomListView.h +++ b/src/openrct2-ui/scripting/CustomListView.h @@ -92,8 +92,12 @@ namespace OpenRCT2::Ui::Windows { private: static constexpr int32_t HEADER_ROW = -1; + + rct_window* ParentWindow{}; + size_t ScrollIndex{}; std::vector Columns; std::vector Items; + ScrollbarType Scrollbars = ScrollbarType::Vertical; public: std::shared_ptr Owner; @@ -106,7 +110,6 @@ namespace OpenRCT2::Ui::Windows bool ShowColumnHeaders{}; bool IsStriped{}; ScreenSize LastKnownSize; - ScrollbarType Scrollbars = ScrollbarType::Vertical; ColumnSortOrder CurrentSortOrder{}; int32_t CurrentSortColumn{}; bool LastIsMouseDown{}; @@ -116,6 +119,9 @@ namespace OpenRCT2::Ui::Windows DukValue OnClick; DukValue OnHighlight; + CustomListView(rct_window* parent, size_t scrollIndex); + ScrollbarType GetScrollbars() const; + void SetScrollbars(ScrollbarType value); const std::vector& GetColumns() const; void SetColumns(const std::vector& columns); const std::vector& GetItems() const; @@ -157,6 +163,8 @@ namespace OpenRCT2::Scripting template<> std::optional 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 diff --git a/src/openrct2-ui/scripting/CustomWindow.cpp b/src/openrct2-ui/scripting/CustomWindow.cpp index a94e51bf1c..d18491a118 100644 --- a/src/openrct2-ui/scripting/CustomWindow.cpp +++ b/src/openrct2-ui/scripting/CustomWindow.cpp @@ -108,6 +108,7 @@ namespace OpenRCT2::Ui::Windows std::vector Items; std::vector ListViewItems; std::vector ListViewColumns; + ScrollbarType Scrollbars{}; int32_t SelectedIndex{}; bool IsChecked{}; bool IsDisabled{}; @@ -178,6 +179,10 @@ namespace OpenRCT2::Ui::Windows 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(desc["scrollbars"]); } else if (result.Type == "spinner") { @@ -663,11 +668,11 @@ namespace OpenRCT2::Ui::Windows auto& listView = info.ListViews[scrollIndex]; auto width = widget->right - widget->left + 1 - 2; auto height = widget->bottom - widget->top + 1 - 2; - if (listView.Scrollbars == ScrollbarType::Horizontal || listView.Scrollbars == ScrollbarType::Both) + if (listView.GetScrollbars() == ScrollbarType::Horizontal || listView.GetScrollbars() == ScrollbarType::Both) { height -= SCROLLBAR_WIDTH + 1; } - if (listView.Scrollbars == ScrollbarType::Vertical || listView.Scrollbars == ScrollbarType::Both) + if (listView.GetScrollbars() == ScrollbarType::Vertical || listView.GetScrollbars() == ScrollbarType::Both) { width -= SCROLLBAR_WIDTH + 1; } @@ -869,7 +874,13 @@ namespace OpenRCT2::Ui::Windows else if (desc.Type == "listview") { widget.type = WWT_SCROLL; - widget.text = SCROLL_VERTICAL; + 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") @@ -971,7 +982,7 @@ namespace OpenRCT2::Ui::Windows if (widgetDesc.Type == "listview") { - CustomListView listView; + CustomListView listView(w, info.ListViews.size()); listView.SetColumns(widgetDesc.ListViewColumns); listView.SetItems(widgetDesc.ListViewItems); listView.ShowColumnHeaders = widgetDesc.ShowColumnHeaders; diff --git a/src/openrct2-ui/scripting/ScWidget.hpp b/src/openrct2-ui/scripting/ScWidget.hpp index 6677bc1796..e9162117b3 100644 --- a/src/openrct2-ui/scripting/ScWidget.hpp +++ b/src/openrct2-ui/scripting/ScWidget.hpp @@ -368,6 +368,7 @@ namespace OpenRCT2::Scripting dukglue_set_base_class(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"); @@ -416,6 +417,27 @@ namespace OpenRCT2::Scripting } } + 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(value)); + } + } + bool showColumnHeaders_get() const { auto listView = GetListView(); From f492827c8a6787a2999f88ad290df5c53f557c7b Mon Sep 17 00:00:00 2001 From: Ted John Date: Sat, 9 May 2020 17:09:47 +0100 Subject: [PATCH 14/16] Implement horizontal scrollbars and fix resizing --- src/openrct2-ui/scripting/CustomListView.cpp | 76 ++++++++++++++------ src/openrct2-ui/scripting/CustomListView.h | 8 +-- src/openrct2-ui/scripting/CustomWindow.cpp | 10 +-- 3 files changed, 64 insertions(+), 30 deletions(-) diff --git a/src/openrct2-ui/scripting/CustomListView.cpp b/src/openrct2-ui/scripting/CustomListView.cpp index b66e10cc15..3bf74f717b 100644 --- a/src/openrct2-ui/scripting/CustomListView.cpp +++ b/src/openrct2-ui/scripting/CustomListView.cpp @@ -14,6 +14,7 @@ # include "../interface/Widget.h" # include "../interface/Window.h" +# include # include # include # include @@ -205,28 +206,32 @@ ScrollbarType CustomListView::GetScrollbars() const return Scrollbars; } -void CustomListView::SetScrollbars(ScrollbarType value) +void CustomListView::SetScrollbars(ScrollbarType value, bool initialising) { Scrollbars = value; - size_t scrollIndex = 0; - for (auto widget = ParentWindow->widgets; widget->type != WWT_LAST; widget++) + if (!initialising) { - if (widget->type == WWT_SCROLL) + size_t scrollIndex = 0; + for (auto widget = ParentWindow->widgets; widget->type != WWT_LAST; widget++) { - if (scrollIndex == ScrollIndex) + if (widget->type == WWT_SCROLL) { - 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; + 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++; } - scrollIndex++; } + window_init_scroll_widgets(ParentWindow); } } @@ -235,12 +240,14 @@ const std::vector& CustomListView::GetColumns() const return Columns; } -void CustomListView::SetColumns(const std::vector& columns) +void CustomListView::SetColumns(const std::vector& columns, bool initialising) { SelectedCell = std::nullopt; Columns = columns; LastKnownSize = {}; SortItems(0, ColumnSortOrder::None); + if (!initialising) + window_init_scroll_widgets(ParentWindow); } const std::vector& CustomListView::CustomListView::GetItems() const @@ -248,17 +255,21 @@ const std::vector& CustomListView::CustomListView::GetItems() cons return Items; } -void CustomListView::SetItems(const std::vector& items) +void CustomListView::SetItems(const std::vector& items, bool initialising) { SelectedCell = std::nullopt; Items = items; SortItems(0, ColumnSortOrder::None); + if (!initialising) + window_update_scroll_widgets(ParentWindow); } -void CustomListView::SetItems(std::vector&& items) +void CustomListView::SetItems(std::vector&& 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) @@ -328,11 +339,13 @@ void CustomListView::Resize(const ScreenSize& size) } // 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]; - if (c == Columns.size() - 1) + auto isLastColumn = c == Columns.size() - 1; + if (!hasHorizontalScroll && isLastColumn) { column.Width = widthRemaining; } @@ -341,7 +354,14 @@ void CustomListView::Resize(const ScreenSize& size) column.Width = 0; if (column.RatioWidth && *column.RatioWidth > 0) { - column.Width = (size.width * *column.RatioWidth) / totalRatio; + if (isLastColumn) + { + column.Width = widthRemaining; + } + else + { + column.Width = (size.width * *column.RatioWidth) / totalRatio; + } } if (column.MinWidth) { @@ -352,8 +372,10 @@ void CustomListView::Resize(const ScreenSize& size) column.Width = std::min(column.Width, *column.MaxWidth); } } - widthRemaining -= column.Width; + widthRemaining = std::max(0, widthRemaining - column.Width); } + + window_init_scroll_widgets(ParentWindow); } ScreenSize CustomListView::GetSize() @@ -365,8 +387,18 @@ ScreenSize CustomListView::GetSize() IsMouseDown = false; ScreenSize result; - result.width = 0; - result.height = static_cast(Items.size() * LIST_ROW_HEIGHT); + 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(Items.size() * LIST_ROW_HEIGHT); + } return result; } diff --git a/src/openrct2-ui/scripting/CustomListView.h b/src/openrct2-ui/scripting/CustomListView.h index 068690a551..a6cef7d641 100644 --- a/src/openrct2-ui/scripting/CustomListView.h +++ b/src/openrct2-ui/scripting/CustomListView.h @@ -121,12 +121,12 @@ namespace OpenRCT2::Ui::Windows CustomListView(rct_window* parent, size_t scrollIndex); ScrollbarType GetScrollbars() const; - void SetScrollbars(ScrollbarType value); + void SetScrollbars(ScrollbarType value, bool initialising = false); const std::vector& GetColumns() const; - void SetColumns(const std::vector& columns); + void SetColumns(const std::vector& columns, bool initialising = false); const std::vector& GetItems() const; - void SetItems(const std::vector& items); - void SetItems(std::vector&& items); + void SetItems(const std::vector& items, bool initialising = false); + void SetItems(std::vector&& 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); diff --git a/src/openrct2-ui/scripting/CustomWindow.cpp b/src/openrct2-ui/scripting/CustomWindow.cpp index d18491a118..26e9899fc5 100644 --- a/src/openrct2-ui/scripting/CustomWindow.cpp +++ b/src/openrct2-ui/scripting/CustomWindow.cpp @@ -417,8 +417,6 @@ namespace OpenRCT2::Ui::Windows window->max_height = desc.MaxHeight.value_or(std::numeric_limits::max()); } RefreshWidgets(window); - window_init_scroll_widgets(window); - window_custom_update_viewport(window); return window; } @@ -983,8 +981,9 @@ namespace OpenRCT2::Ui::Windows if (widgetDesc.Type == "listview") { CustomListView listView(w, info.ListViews.size()); - listView.SetColumns(widgetDesc.ListViewColumns); - listView.SetItems(widgetDesc.ListViewItems); + 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; @@ -1014,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& owner, const DukValue& dukHandler) From cc759e0bcefaaae69f151013aa762de6d5d40935 Mon Sep 17 00:00:00 2001 From: Ted John Date: Sat, 9 May 2020 17:14:06 +0100 Subject: [PATCH 15/16] Improve selected highlight on list view --- src/openrct2-ui/scripting/CustomListView.cpp | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/openrct2-ui/scripting/CustomListView.cpp b/src/openrct2-ui/scripting/CustomListView.cpp index 3bf74f717b..397b6a1d44 100644 --- a/src/openrct2-ui/scripting/CustomListView.cpp +++ b/src/openrct2-ui/scripting/CustomListView.cpp @@ -512,16 +512,14 @@ void CustomListView::Paint(rct_window* w, rct_drawpixelinfo* dpi, const rct_scro auto isStriped = IsStriped && (i & 1); auto isHighlighted = (HighlightedCell && itemIndex == HighlightedCell->Row); auto isSelected = (SelectedCell && itemIndex == SelectedCell->Row); - if (isHighlighted) + 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 (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( From 2770668752bf8c8b9ed59fc33f0c96face9e176d Mon Sep 17 00:00:00 2001 From: Michael Steenbeek Date: Sat, 9 May 2020 20:24:48 +0200 Subject: [PATCH 16/16] Fix Xcode project --- OpenRCT2.xcodeproj/project.pbxproj | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/OpenRCT2.xcodeproj/project.pbxproj b/OpenRCT2.xcodeproj/project.pbxproj index 7ee65cc2bd..10a5355d0b 100644 --- a/OpenRCT2.xcodeproj/project.pbxproj +++ b/OpenRCT2.xcodeproj/project.pbxproj @@ -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 = ""; }; 4C7B547A2010DF4C00A52E21 /* Windows.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Windows.cpp; sourceTree = ""; }; 4C7B547E2010DFF700A52E21 /* Crash.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Crash.h; sourceTree = ""; }; + 4C81F7DF24672C4D000E61BF /* CustomListView.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = CustomListView.cpp; path = scripting/CustomListView.cpp; sourceTree = ""; }; + 4C81F7E024672C4D000E61BF /* CustomListView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CustomListView.h; path = scripting/CustomListView.h; sourceTree = ""; }; + 4C81F7E224672C58000E61BF /* ScTileSelection.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = ScTileSelection.hpp; path = scripting/ScTileSelection.hpp; sourceTree = ""; }; 4C8667801EEFDCDF0024AAB8 /* RideGroupManager.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = RideGroupManager.cpp; sourceTree = ""; }; 4C8667811EEFDCDF0024AAB8 /* RideGroupManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RideGroupManager.h; sourceTree = ""; }; 4C8A6FF123EB5325001A8255 /* Http.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Http.h; sourceTree = ""; }; @@ -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 */,