diff --git a/distribution/changelog.txt b/distribution/changelog.txt
index 3063b045ad..94d4104c01 100644
--- a/distribution/changelog.txt
+++ b/distribution/changelog.txt
@@ -24,6 +24,10 @@
- Feature: [#14002] [Plugin] Use allowed_hosts when checking the binding IP for listening.
- Feature: [#14059] [Plugin] Add optional filter to custom tools.
- Feature: [#14142] [Plugin] Add option for taking transparent screenshots.
+- Feature: [#14171] [Plugin] Add API for getting network traffic statistics.
+- Feature: [#14171] [Plugin] Add API for creating custom widgets.
+- Feature: [#14171] [Plugin] Add API for drawing graphics for custom widgets.
+- Feature: [#14171] [Plugin] Add click event to spinners and allow them to be held down.
- Change: [#13346] [Plugin] Renamed FootpathScenery to FootpathAddition, fix typos.
- Change: [#13857] Change Rotation Control Toggle to track element number 256
- Fix: [#4605, #11912] Water palettes are not updated properly when selected in Object Selection.
diff --git a/distribution/openrct2.d.ts b/distribution/openrct2.d.ts
index fd3dad75de..d190ba67bd 100644
--- a/distribution/openrct2.d.ts
+++ b/distribution/openrct2.d.ts
@@ -70,6 +70,14 @@ declare global {
y: number;
}
+ /**
+ * Represents the width and height in pixels.
+ */
+ interface ScreenSize {
+ width: number;
+ height: number;
+ }
+
/**
* A coordinate within the game.
* Each in-game tile is a size of 32x32.
@@ -1397,6 +1405,7 @@ declare global {
readonly players: Player[];
readonly currentPlayer: Player;
defaultGroup: number;
+ readonly stats: NetworkStats;
getServerInfo(): ServerInfo;
addGroup(): void;
@@ -1442,6 +1451,11 @@ declare global {
readonly providerWebsite: string;
}
+ interface NetworkStats {
+ bytesReceived: number[];
+ bytesSent: number[];
+ }
+
type PermissionType =
"chat" |
"terraform" |
@@ -1996,14 +2010,15 @@ declare global {
* Represents the type of a widget, e.g. button or label.
*/
type WidgetType =
- "button" | "checkbox" | "colourpicker" | "dropdown" | "groupbox" |
+ "button" | "checkbox" | "colourpicker" | "custom" | "dropdown" | "groupbox" |
"label" | "listview" | "spinner" | "textbox" | "viewport";
type Widget =
- ButtonWidget | CheckboxWidget | ColourPickerWidget | DropdownWidget | GroupBoxWidget |
+ ButtonWidget | CheckboxWidget | ColourPickerWidget | CustomWidget | DropdownWidget | GroupBoxWidget |
LabelWidget | ListView | SpinnerWidget | TextBoxWidget | ViewportWidget;
interface WidgetBase {
+ readonly window?: Window;
x: number;
y: number;
width: number;
@@ -2040,6 +2055,11 @@ declare global {
onChange?: (colour: number) => void;
}
+ interface CustomWidget extends WidgetBase {
+ type: 'custom';
+ onDraw?: (this: CustomWidget, g: GraphicsContext) => void;
+ }
+
interface DropdownWidget extends WidgetBase {
type: 'dropdown';
items?: string[];
@@ -2105,8 +2125,10 @@ declare global {
interface SpinnerWidget extends WidgetBase {
type: 'spinner';
text?: string;
+
onDecrement?: () => void;
onIncrement?: () => void;
+ onClick?: () => void;
}
interface TextBoxWidget extends WidgetBase {
@@ -2191,6 +2213,44 @@ declare global {
scrollTo(position: CoordsXY | CoordsXYZ): void;
}
+ /**
+ * API for drawing graphics.
+ */
+ interface GraphicsContext {
+ colour: number | undefined;
+ secondaryColour: number | undefined;
+ ternaryColour: number | undefined;
+ stroke: number;
+ fill: number;
+ paletteId: number | undefined;
+ readonly width: number;
+ readonly height: number;
+
+ getImage(id: number): ImageInfo | undefined;
+ measureText(text: string): ScreenSize;
+
+ clear(): void;
+ clip(x: number, y: number, width: number, height: number): void;
+ box(x: number, y: number, width: number, height: number): void;
+ image(id: number, x: number, y: number): void;
+ line(x1: number, y1: number, x2: number, y2: number): void;
+ rect(x: number, y: number, width: number, height: number): void;
+ text(text: string, x: number, y: number): void;
+ well(x: number, y: number, width: number, height: number): void;
+ }
+
+ interface ImageInfo {
+ readonly id: number;
+ readonly offset: ScreenCoordsXY;
+ readonly width: number;
+ readonly height: number;
+ readonly isBMP: boolean;
+ readonly isRLE: boolean;
+ readonly isPalette: boolean;
+ readonly noZoom: boolean;
+ readonly nextZoomId: number | undefined;
+ }
+
/**
* Listens for incoming connections.
* Based on node.js net.Server, see https://nodejs.org/api/net.html for more information.
diff --git a/src/openrct2-ui/interface/Widget.cpp b/src/openrct2-ui/interface/Widget.cpp
index 5e04864630..1451f53756 100644
--- a/src/openrct2-ui/interface/Widget.cpp
+++ b/src/openrct2-ui/interface/Widget.cpp
@@ -49,9 +49,6 @@ void WidgetDraw(rct_drawpixelinfo* dpi, rct_window* w, rct_widgetindex widgetInd
{
switch (w->widgets[widgetIndex].type)
{
- case WindowWidgetType::Empty:
- case WindowWidgetType::Last:
- break;
case WindowWidgetType::Frame:
WidgetFrameDraw(dpi, w, widgetIndex);
break;
@@ -99,11 +96,11 @@ void WidgetDraw(rct_drawpixelinfo* dpi, rct_window* w, rct_widgetindex widgetInd
case WindowWidgetType::Checkbox:
WidgetCheckboxDraw(dpi, w, widgetIndex);
break;
- case WindowWidgetType::Placeholder:
- break;
case WindowWidgetType::TextBox:
WidgetTextBoxDraw(dpi, w, widgetIndex);
break;
+ default:
+ break;
}
}
@@ -856,6 +853,11 @@ bool WidgetIsDisabled(rct_window* w, rct_widgetindex widgetIndex)
return (w->disabled_widgets & (1LL << widgetIndex)) != 0;
}
+bool WidgetIsHoldable(rct_window* w, rct_widgetindex widgetIndex)
+{
+ return (w->hold_down_widgets & (1LL << widgetIndex)) != 0;
+}
+
bool WidgetIsVisible(rct_window* w, rct_widgetindex widgetIndex)
{
return w->widgets[widgetIndex].IsVisible();
@@ -1050,6 +1052,18 @@ void WidgetSetDisabled(rct_window* w, rct_widgetindex widgetIndex, bool value)
}
}
+void WidgetSetHoldable(rct_window* w, rct_widgetindex widgetIndex, bool value)
+{
+ if (value)
+ {
+ w->hold_down_widgets |= (1ULL << widgetIndex);
+ }
+ else
+ {
+ w->hold_down_widgets &= ~(1ULL << widgetIndex);
+ }
+}
+
void WidgetSetVisible(rct_window* w, rct_widgetindex widgetIndex, bool value)
{
if (value)
diff --git a/src/openrct2-ui/interface/Window.cpp b/src/openrct2-ui/interface/Window.cpp
index c1f9d09b60..8942e803e2 100644
--- a/src/openrct2-ui/interface/Window.cpp
+++ b/src/openrct2-ui/interface/Window.cpp
@@ -632,8 +632,15 @@ void WindowDrawWidgets(rct_window* w, rct_drawpixelinfo* dpi)
{
// Check if widget is outside the draw region
if (w->windowPos.x + widget->left < dpi->x + dpi->width && w->windowPos.x + widget->right >= dpi->x)
+ {
if (w->windowPos.y + widget->top < dpi->y + dpi->height && w->windowPos.y + widget->bottom >= dpi->y)
- WidgetDraw(dpi, w, widgetIndex);
+ {
+ if (w->IsLegacy())
+ WidgetDraw(dpi, w, widgetIndex);
+ else
+ w->OnDrawWidget(widgetIndex, *dpi);
+ }
+ }
}
widgetIndex++;
}
@@ -681,6 +688,21 @@ void InvalidateAllWindowsAfterInput()
});
}
+bool Window::IsLegacy()
+{
+ return false;
+}
+
+void Window::OnDraw(rct_drawpixelinfo& dpi)
+{
+ WindowDrawWidgets(this, &dpi);
+}
+
+void Window::OnDrawWidget(rct_widgetindex widgetIndex, rct_drawpixelinfo& dpi)
+{
+ WidgetDraw(&dpi, this, widgetIndex);
+}
+
void Window::InvalidateWidget(rct_widgetindex widgetIndex)
{
widget_invalidate(this, widgetIndex);
diff --git a/src/openrct2-ui/interface/Window.h b/src/openrct2-ui/interface/Window.h
index ee1de33ffa..19b0bacc0a 100644
--- a/src/openrct2-ui/interface/Window.h
+++ b/src/openrct2-ui/interface/Window.h
@@ -14,6 +14,10 @@
struct Window : rct_window
{
+ virtual bool IsLegacy() override;
+ virtual void OnDraw(rct_drawpixelinfo& dpi) override;
+ virtual void OnDrawWidget(rct_widgetindex widgetIndex, rct_drawpixelinfo& dpi) override;
+
void InvalidateWidget(rct_widgetindex widgetIndex);
bool IsWidgetDisabled(rct_widgetindex widgetIndex) const;
bool IsWidgetPressed(rct_widgetindex widgetIndex) const;
diff --git a/src/openrct2-ui/libopenrct2ui.vcxproj b/src/openrct2-ui/libopenrct2ui.vcxproj
index d454e99e66..736db9ad95 100644
--- a/src/openrct2-ui/libopenrct2ui.vcxproj
+++ b/src/openrct2-ui/libopenrct2ui.vcxproj
@@ -54,6 +54,7 @@
+
diff --git a/src/openrct2-ui/scripting/CustomWindow.cpp b/src/openrct2-ui/scripting/CustomWindow.cpp
index cd0683ae7a..673c90a224 100644
--- a/src/openrct2-ui/scripting/CustomWindow.cpp
+++ b/src/openrct2-ui/scripting/CustomWindow.cpp
@@ -10,6 +10,8 @@
#ifdef ENABLE_SCRIPTING
# include "../interface/Dropdown.h"
+# include "../scripting/ScGraphicsContext.hpp"
+# include "../scripting/ScWidget.hpp"
# include "CustomListView.h"
# include "ScUi.hpp"
# include "ScWindow.hpp"
@@ -18,6 +20,7 @@
# include
# include
# include
+# include
# include
# include
# include
@@ -49,39 +52,6 @@ namespace OpenRCT2::Ui::Windows
{ WindowWidgetType::Resize, 1, 0, 0, 14, 0, 0xFFFFFFFF, STR_NONE }, // content panel
};
- static void window_custom_close(rct_window* w);
- static void window_custom_mouseup(rct_window* w, rct_widgetindex widgetIndex);
- static void window_custom_mousedown(rct_window* w, rct_widgetindex widgetIndex, rct_widget* widget);
- 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_textinput(rct_window* w, rct_widgetindex widgetIndex, char* text);
- static void window_custom_update(rct_window* w);
- static void window_custom_scrollgetsize(rct_window* w, int32_t scrollIndex, int32_t* width, int32_t* height);
- static void window_custom_scrollmousedrag(rct_window* w, int32_t scrollIndex, const ScreenCoordsXY& screenCoords);
- static void window_custom_scrollmousedown(rct_window* w, int32_t scrollIndex, const ScreenCoordsXY& screenCoords);
- static void window_custom_scrollmouseover(rct_window* w, int32_t scrollIndex, const ScreenCoordsXY& screenCoords);
- static void window_custom_invalidate(rct_window* w);
- static void window_custom_paint(rct_window* w, rct_drawpixelinfo* dpi);
- static void window_custom_scrollpaint(rct_window* w, rct_drawpixelinfo* dpi, int32_t scrollIndex);
- static void window_custom_update_viewport(rct_window* w);
-
- static rct_window_event_list window_custom_events([](auto& events) {
- events.close = &window_custom_close;
- events.mouse_up = &window_custom_mouseup;
- events.resize = &window_custom_resize;
- events.mouse_down = &window_custom_mousedown;
- events.dropdown = &window_custom_dropdown;
- events.text_input = &window_custom_textinput;
- events.update = &window_custom_update;
- events.get_scroll_size = &window_custom_scrollgetsize;
- events.scroll_mousedown = &window_custom_scrollmousedown;
- events.scroll_mousedrag = &window_custom_scrollmousedrag;
- events.scroll_mouseover = &window_custom_scrollmouseover;
- events.invalidate = &window_custom_invalidate;
- events.paint = &window_custom_paint;
- events.scroll_paint = &window_custom_scrollpaint;
- });
-
struct CustomWidgetDesc
{
// Properties
@@ -115,6 +85,7 @@ namespace OpenRCT2::Ui::Windows
// Event handlers
DukValue OnClick;
DukValue OnChange;
+ DukValue OnDraw;
DukValue OnIncrement;
DukValue OnDecrement;
DukValue OnHighlight;
@@ -163,6 +134,10 @@ namespace OpenRCT2::Ui::Windows
}
result.OnChange = desc["onChange"];
}
+ else if (result.Type == "custom")
+ {
+ result.OnDraw = desc["onDraw"];
+ }
else if (result.Type == "dropdown")
{
if (desc["items"].is_array())
@@ -208,6 +183,7 @@ namespace OpenRCT2::Ui::Windows
result.Text = ProcessString(desc["text"]);
result.OnIncrement = desc["onIncrement"];
result.OnDecrement = desc["onDecrement"];
+ result.OnClick = desc["onClick"];
}
else if (result.Type == "textbox")
{
@@ -402,474 +378,804 @@ namespace OpenRCT2::Ui::Windows
}
};
- static rct_windownumber _nextWindowNumber;
-
static CustomWindowInfo& GetInfo(rct_window* w);
- static rct_windownumber GetNewWindowNumber();
- static void RefreshWidgets(rct_window* w);
static void InvokeEventHandler(const std::shared_ptr& owner, const DukValue& dukHandler);
static void InvokeEventHandler(
const std::shared_ptr& owner, const DukValue& dukHandler, const std::vector& args);
- rct_window* window_custom_open(std::shared_ptr owner, DukValue dukDesc)
+ class CustomWindow final : public Window
{
- auto desc = CustomWindowDesc::FromDukValue(dukDesc);
+ private:
+ static rct_windownumber _nextWindowNumber;
- uint16_t windowFlags = WF_RESIZABLE | WF_TRANSPARENT;
-
- rct_window* window{};
- if (desc.X && desc.Y)
+ public:
+ void Initialise(std::shared_ptr owner, const CustomWindowDesc& desc)
{
- window = WindowCreate({ *desc.X, *desc.Y }, desc.Width, desc.Height, &window_custom_events, WC_CUSTOM, windowFlags);
- }
- else
- {
- window = WindowCreateAutoPos(desc.Width, desc.Height, &window_custom_events, WC_CUSTOM, windowFlags);
- }
+ number = GetNewWindowNumber();
+ custom_info = new CustomWindowInfo(owner, desc);
+ enabled_widgets = (1 << WIDX_CLOSE);
- window->number = GetNewWindowNumber();
- window->custom_info = new CustomWindowInfo(owner, desc);
- window->enabled_widgets = (1 << WIDX_CLOSE);
+ // Set window tab
+ page = desc.TabIndex.value_or(0);
- // Set window tab
- window->page = desc.TabIndex.value_or(0);
-
- // Set window colours
- window->colours[0] = COLOUR_GREY;
- window->colours[1] = COLOUR_GREY;
- window->colours[2] = COLOUR_GREY;
- auto numColours = std::min(std::size(window->colours), std::size(desc.Colours));
- for (size_t i = 0; i < numColours; i++)
- {
- window->colours[i] = desc.Colours[i];
- }
-
- if (desc.IsResizable())
- {
- window->min_width = desc.MinWidth.value_or(0);
- window->min_height = desc.MinHeight.value_or(0);
- window->max_width = desc.MaxWidth.value_or(std::numeric_limits::max());
- window->max_height = desc.MaxHeight.value_or(std::numeric_limits::max());
- }
- RefreshWidgets(window);
- return window;
- }
-
- static void window_custom_close(rct_window* w)
- {
- auto info = static_cast(w->custom_info);
- if (info != nullptr)
- {
- InvokeEventHandler(info->Owner, info->Desc.OnClose);
- delete info;
- w->custom_info = nullptr;
- }
- }
-
- static void window_custom_change_tab(rct_window* w, size_t tabIndex)
- {
- const auto& info = GetInfo(w);
-
- w->page = static_cast(tabIndex);
- w->frame_no = 0;
- RefreshWidgets(w);
-
- w->Invalidate();
- window_event_resize_call(w);
- window_event_invalidate_call(w);
- WindowInitScrollWidgets(w);
- w->Invalidate();
-
- InvokeEventHandler(info.Owner, info.Desc.OnTabChange);
- }
-
- static void window_custom_mouseup(rct_window* w, rct_widgetindex widgetIndex)
- {
- switch (widgetIndex)
- {
- case WIDX_CLOSE:
- window_close(w);
- break;
- default:
+ // Set window colours
+ colours[0] = COLOUR_GREY;
+ colours[1] = COLOUR_GREY;
+ colours[2] = COLOUR_GREY;
+ auto numColours = std::min(std::size(colours), std::size(desc.Colours));
+ for (size_t i = 0; i < numColours; i++)
{
- const auto& info = GetInfo(w);
- if (widgetIndex >= WIDX_TAB_0 && widgetIndex < static_cast(WIDX_TAB_0 + info.Desc.Tabs.size()))
+ colours[i] = desc.Colours[i];
+ }
+
+ if (desc.IsResizable())
+ {
+ min_width = desc.MinWidth.value_or(0);
+ min_height = desc.MinHeight.value_or(0);
+ max_width = desc.MaxWidth.value_or(std::numeric_limits::max());
+ max_height = desc.MaxHeight.value_or(std::numeric_limits::max());
+ }
+ RefreshWidgets();
+ }
+
+ void OnClose() override
+ {
+ auto info = static_cast(custom_info);
+ if (info != nullptr)
+ {
+ InvokeEventHandler(info->Owner, info->Desc.OnClose);
+ delete info;
+ custom_info = nullptr;
+ }
+ }
+
+ void OnResize() override
+ {
+ if (width < min_width)
+ {
+ Invalidate();
+ width = min_width;
+ }
+ if (height < min_height)
+ {
+ Invalidate();
+ height = min_height;
+ }
+ UpdateViewport();
+ }
+
+ void OnUpdate() override
+ {
+ const auto& info = GetInfo(this);
+ if (info.Desc.Tabs.size() > static_cast(page))
+ {
+ const auto& tab = info.Desc.Tabs[page];
+ if (tab.imageFrameCount != 0)
{
- window_custom_change_tab(w, widgetIndex - WIDX_TAB_0);
+ frame_no++;
+ if (frame_no >= tab.imageFrameCount * tab.imageFrameDuration)
+ {
+ frame_no = 0;
+ }
+ widget_invalidate(this, WIDX_TAB_0 + this->page);
+ }
+ }
+
+ InvokeEventHandler(info.Owner, info.Desc.OnUpdate);
+
+ // Since the plugin may alter widget positions and sizes during an update event,
+ // we need to force an update for all list view scrollbars
+ rct_widgetindex widgetIndex = 0;
+ for (auto widget = widgets; widget->type != WindowWidgetType::Empty; widget++)
+ {
+ if (widget->type == WindowWidgetType::Scroll)
+ {
+ WidgetScrollUpdateThumbs(this, widgetIndex);
+ }
+ widgetIndex++;
+ }
+ }
+
+ void OnPrepareDraw() override
+ {
+ widgets[WIDX_BACKGROUND].right = width - 1;
+ widgets[WIDX_BACKGROUND].bottom = height - 1;
+ widgets[WIDX_TITLE].right = width - 2;
+ widgets[WIDX_CLOSE].left = width - 13;
+ widgets[WIDX_CLOSE].right = width - 3;
+ widgets[WIDX_CONTENT_PANEL].right = width - 1;
+ widgets[WIDX_CONTENT_PANEL].bottom = height - 1;
+ widgets[WIDX_CLOSE].text = (colours[0] & COLOUR_FLAG_TRANSLUCENT) ? STR_CLOSE_X_WHITE : STR_CLOSE_X;
+
+ // Having the content panel visible for transparent windows makes the borders darker than they should be
+ // For now just hide it if there are no tabs and the window is not resizable
+ auto& info = GetInfo(this);
+ auto canResize = (flags & WF_RESIZABLE) != 0 && (min_width != max_width || min_height != max_height);
+ auto numTabs = info.Desc.Tabs.size();
+ if (canResize || numTabs != 0)
+ {
+ widgets[WIDX_CONTENT_PANEL].flags &= ~WIDGET_FLAGS::IS_HIDDEN;
+ }
+ else
+ {
+ widgets[WIDX_CONTENT_PANEL].flags |= WIDGET_FLAGS::IS_HIDDEN;
+ }
+
+ SetPressedTab();
+
+ const auto& desc = info.Desc;
+ auto ft = Formatter::Common();
+ ft.Add(desc.Title.c_str());
+
+ size_t scrollIndex = 0;
+ for (auto widget = widgets; widget->type != WindowWidgetType::Last; widget++)
+ {
+ if (widget->type == WindowWidgetType::Scroll)
+ {
+ auto& listView = info.ListViews[scrollIndex];
+ auto wwidth = widget->width() + 1 - 2;
+ auto wheight = widget->height() + 1 - 2;
+ if (listView.GetScrollbars() == ScrollbarType::Horizontal
+ || listView.GetScrollbars() == ScrollbarType::Both)
+ {
+ wheight -= SCROLLBAR_WIDTH + 1;
+ }
+ if (listView.GetScrollbars() == ScrollbarType::Vertical || listView.GetScrollbars() == ScrollbarType::Both)
+ {
+ wwidth -= SCROLLBAR_WIDTH + 1;
+ }
+ listView.Resize({ wwidth, wheight });
+ scrollIndex++;
+ }
+ }
+ }
+
+ void OnDraw(rct_drawpixelinfo& dpi) override
+ {
+ WindowDrawWidgets(this, &dpi);
+ DrawTabImages(dpi);
+ if (viewport != nullptr)
+ {
+ auto widgetIndex = GetViewportWidgetIndex();
+ if (WidgetIsVisible(this, widgetIndex.value_or(false)))
+ {
+ window_draw_viewport(&dpi, this);
+ }
+ }
+ }
+
+ void OnDrawWidget(rct_widgetindex widgetIndex, rct_drawpixelinfo& dpi) override
+ {
+ const auto& widget = widgets[widgetIndex];
+ const auto& info = GetInfo(this);
+ const auto widgetDesc = info.GetCustomWidgetDesc(this, widgetIndex);
+ if (widgetDesc != nullptr && widgetDesc->Type == "custom")
+ {
+ auto& onDraw = widgetDesc->OnDraw;
+ if (onDraw.is_function())
+ {
+ rct_drawpixelinfo widgetDpi;
+ if (clip_drawpixelinfo(
+ &widgetDpi, &dpi, { windowPos.x + widget.left, windowPos.y + widget.top }, widget.width(),
+ widget.height()))
+ {
+ auto ctx = onDraw.context();
+ auto dukWidget = ScWidget::ToDukValue(ctx, this, widgetIndex);
+ auto dukG = GetObjectAsDukValue(ctx, std::make_shared(ctx, widgetDpi));
+ auto& scriptEngine = GetContext()->GetScriptEngine();
+ scriptEngine.ExecutePluginCall(info.Owner, widgetDesc->OnDraw, dukWidget, { dukG }, false);
+ }
+ }
+ }
+ else
+ {
+ Window::OnDrawWidget(widgetIndex, dpi);
+ }
+ }
+
+ void OnMouseUp(rct_widgetindex widgetIndex) override
+ {
+ switch (widgetIndex)
+ {
+ case WIDX_CLOSE:
+ window_close(this);
+ break;
+ default:
+ {
+ const auto& info = GetInfo(this);
+ if (widgetIndex >= WIDX_TAB_0
+ && widgetIndex < static_cast(WIDX_TAB_0 + info.Desc.Tabs.size()))
+ {
+ ChangeTab(widgetIndex - WIDX_TAB_0);
+ break;
+ }
+
+ const auto widgetDesc = info.GetCustomWidgetDesc(this, widgetIndex);
+ if (widgetDesc != nullptr)
+ {
+ if (widgetDesc->Type == "button")
+ {
+ InvokeEventHandler(info.Owner, widgetDesc->OnClick);
+ }
+ else if (widgetDesc->Type == "checkbox")
+ {
+ auto& widget = widgets[widgetIndex];
+ widget.flags ^= WIDGET_FLAGS::IS_PRESSED;
+ bool isChecked = widget.flags & WIDGET_FLAGS::IS_PRESSED;
+
+ WidgetSetCheckboxValue(this, widgetIndex, isChecked);
+
+ std::vector args;
+ auto ctx = widgetDesc->OnChange.context();
+ duk_push_boolean(ctx, isChecked);
+ args.push_back(DukValue::take_from_stack(ctx));
+ InvokeEventHandler(info.Owner, widgetDesc->OnChange, args);
+ }
+ else if (widgetDesc->Type == "spinner")
+ {
+ auto& widget = widgets[widgetIndex];
+ if (widget.text != STR_NUMERIC_DOWN && widget.text != STR_NUMERIC_UP)
+ {
+ InvokeEventHandler(info.Owner, widgetDesc->OnClick);
+ }
+ }
+ }
break;
}
+ }
+ }
- const auto widgetDesc = info.GetCustomWidgetDesc(w, widgetIndex);
- if (widgetDesc != nullptr)
+ void OnMouseDown(rct_widgetindex widgetIndex) override
+ {
+ auto* widget = &widgets[widgetIndex];
+ const auto& info = GetInfo(this);
+ const auto widgetDesc = info.GetCustomWidgetDesc(this, widgetIndex);
+ if (widgetDesc != nullptr)
+ {
+ if (widgetDesc->Type == "colourpicker")
{
- if (widgetDesc->Type == "button")
+ WindowDropdownShowColour(this, widget, colours[widget->colour], widgetDesc->Colour);
+ }
+ else if (widgetDesc->Type == "dropdown")
+ {
+ widget--;
+ auto selectedIndex = widgetDesc->SelectedIndex;
+ const auto& items = widgetDesc->Items;
+ const auto numItems = std::min(items.size(), Dropdown::ItemsMaxSize);
+ for (size_t i = 0; i < numItems; i++)
{
- InvokeEventHandler(info.Owner, widgetDesc->OnClick);
+ gDropdownItemsFormat[i] = selectedIndex == static_cast(i) ? STR_OPTIONS_DROPDOWN_ITEM_SELECTED
+ : STR_OPTIONS_DROPDOWN_ITEM;
+ auto sz = items[i].c_str();
+ std::memcpy(&gDropdownItemsArgs[i], &sz, sizeof(const char*));
}
- else if (widgetDesc->Type == "checkbox")
+ WindowDropdownShowTextCustomWidth(
+ { windowPos.x + widget->left, windowPos.y + widget->top }, widget->height() + 1,
+ colours[widget->colour], 0, Dropdown::Flag::StayOpen, numItems, widget->width() - 3);
+ }
+ else if (widgetDesc->Type == "spinner")
+ {
+ if (widget->text == STR_NUMERIC_DOWN)
{
- auto& widget = w->widgets[widgetIndex];
- widget.flags ^= WIDGET_FLAGS::IS_PRESSED;
- bool isChecked = widget.flags & WIDGET_FLAGS::IS_PRESSED;
-
- WidgetSetCheckboxValue(w, widgetIndex, isChecked);
-
- std::vector args;
- auto ctx = widgetDesc->OnChange.context();
- duk_push_boolean(ctx, isChecked);
- args.push_back(DukValue::take_from_stack(ctx));
- InvokeEventHandler(info.Owner, widgetDesc->OnChange, args);
+ InvokeEventHandler(info.Owner, widgetDesc->OnDecrement);
+ }
+ else if (widget->text == STR_NUMERIC_UP)
+ {
+ InvokeEventHandler(info.Owner, widgetDesc->OnIncrement);
}
}
- break;
- }
- }
- }
-
- static void window_custom_resize(rct_window* w)
- {
- if (w->width < w->min_width)
- {
- w->Invalidate();
- w->width = w->min_width;
- }
- if (w->height < w->min_height)
- {
- w->Invalidate();
- w->height = w->min_height;
- }
- window_custom_update_viewport(w);
- }
-
- static void window_custom_mousedown(rct_window* w, rct_widgetindex widgetIndex, rct_widget* widget)
- {
- const auto& info = GetInfo(w);
- const auto widgetDesc = info.GetCustomWidgetDesc(w, widgetIndex);
- if (widgetDesc != nullptr)
- {
- if (widgetDesc->Type == "colourpicker")
- {
- WindowDropdownShowColour(w, widget, w->colours[widget->colour], widgetDesc->Colour);
- }
- else if (widgetDesc->Type == "dropdown")
- {
- widget--;
- auto selectedIndex = widgetDesc->SelectedIndex;
- const auto& items = widgetDesc->Items;
- const auto numItems = std::min(items.size(), Dropdown::ItemsMaxSize);
- for (size_t i = 0; i < numItems; i++)
+ else if (widgetDesc->Type == "textbox")
{
- gDropdownItemsFormat[i] = selectedIndex == static_cast(i) ? STR_OPTIONS_DROPDOWN_ITEM_SELECTED
- : STR_OPTIONS_DROPDOWN_ITEM;
- auto sz = items[i].c_str();
- std::memcpy(&gDropdownItemsArgs[i], &sz, sizeof(const char*));
- }
- WindowDropdownShowTextCustomWidth(
- { w->windowPos.x + widget->left, w->windowPos.y + widget->top }, widget->height() + 1,
- w->colours[widget->colour], 0, Dropdown::Flag::StayOpen, numItems, widget->width() - 3);
- }
- else if (widgetDesc->Type == "spinner")
- {
- if (widget->text == STR_NUMERIC_DOWN)
- {
- InvokeEventHandler(info.Owner, widgetDesc->OnDecrement);
- }
- else if (widget->text == STR_NUMERIC_UP)
- {
- InvokeEventHandler(info.Owner, widgetDesc->OnIncrement);
+ auto* text = const_cast(widgetDesc->Text.c_str());
+ window_start_textbox(this, widgetIndex, STR_STRING, text, widgetDesc->MaxLength + 1);
}
}
- else if (widgetDesc->Type == "textbox")
- {
- auto* text = const_cast(widgetDesc->Text.c_str());
- window_start_textbox(w, widgetIndex, STR_STRING, text, widgetDesc->MaxLength + 1);
- }
}
- }
- static void window_custom_dropdown(rct_window* w, rct_widgetindex widgetIndex, int32_t dropdownIndex)
- {
- if (dropdownIndex == -1)
- return;
-
- auto& info = GetInfo(w);
- auto widgetDesc = info.GetCustomWidgetDesc(w, widgetIndex);
- if (widgetDesc != nullptr)
+ void OnDropdown(rct_widgetindex widgetIndex, int32_t dropdownIndex) override
{
- if (widgetDesc->Type == "colourpicker")
- {
- UpdateWidgetColour(w, widgetIndex, dropdownIndex);
- }
- else if (widgetDesc->Type == "dropdown")
- {
- UpdateWidgetSelectedIndex(w, widgetIndex - 1, dropdownIndex);
- }
- }
- }
+ if (dropdownIndex == -1)
+ return;
- static void window_custom_textinput(rct_window* w, rct_widgetindex widgetIndex, char* text)
- {
- if (text == nullptr)
- return;
-
- auto& info = GetInfo(w);
- auto widgetDesc = info.GetCustomWidgetDesc(w, widgetIndex);
- if (widgetDesc != nullptr)
- {
- if (widgetDesc->Type == "textbox")
+ auto& info = GetInfo(this);
+ auto widgetDesc = info.GetCustomWidgetDesc(this, widgetIndex);
+ if (widgetDesc != nullptr)
{
- UpdateWidgetText(w, widgetIndex, text);
-
- std::vector args;
- auto ctx = widgetDesc->OnChange.context();
- duk_push_string(ctx, text);
- args.push_back(DukValue::take_from_stack(ctx));
- InvokeEventHandler(info.Owner, widgetDesc->OnChange, args);
- }
- }
- }
-
- static void window_custom_update(rct_window* w)
- {
- const auto& info = GetInfo(w);
- if (info.Desc.Tabs.size() > static_cast(w->page))
- {
- const auto& tab = info.Desc.Tabs[w->page];
- if (tab.imageFrameCount != 0)
- {
- w->frame_no++;
- if (w->frame_no >= tab.imageFrameCount * tab.imageFrameDuration)
+ if (widgetDesc->Type == "colourpicker")
{
- w->frame_no = 0;
+ UpdateWidgetColour(this, widgetIndex, dropdownIndex);
}
- widget_invalidate(w, WIDX_TAB_0 + w->page);
- }
- }
-
- InvokeEventHandler(info.Owner, info.Desc.OnUpdate);
-
- // Since the plugin may alter widget positions and sizes during an update event,
- // we need to force an update for all list view scrollbars
- rct_widgetindex widgetIndex = 0;
- for (auto widget = w->widgets; widget->type != WindowWidgetType::Empty; widget++)
- {
- if (widget->type == WindowWidgetType::Scroll)
- {
- WidgetScrollUpdateThumbs(w, widgetIndex);
- }
- widgetIndex++;
- }
- }
-
- static void window_custom_scrollgetsize(rct_window* w, int32_t scrollIndex, int32_t* width, int32_t* height)
- {
- auto& info = GetInfo(w);
- if (scrollIndex < static_cast(info.ListViews.size()))
- {
- auto size = info.ListViews[scrollIndex].GetSize();
- *width = size.width;
- *height = size.height;
- }
- }
-
- static void window_custom_scrollmousedown(rct_window* w, int32_t scrollIndex, const ScreenCoordsXY& screenCoords)
- {
- auto& info = GetInfo(w);
- if (scrollIndex < static_cast(info.ListViews.size()))
- {
- info.ListViews[scrollIndex].MouseDown(screenCoords);
- }
- }
-
- static void window_custom_scrollmousedrag(rct_window* w, int32_t scrollIndex, const ScreenCoordsXY& screenCoords)
- {
- auto& info = GetInfo(w);
- if (scrollIndex < static_cast(info.ListViews.size()))
- {
- info.ListViews[scrollIndex].MouseOver(screenCoords, true);
- }
- }
-
- static void window_custom_scrollmouseover(rct_window* w, int32_t scrollIndex, const ScreenCoordsXY& screenCoords)
- {
- auto& info = GetInfo(w);
- if (scrollIndex < static_cast(info.ListViews.size()))
- {
- info.ListViews[scrollIndex].MouseOver(screenCoords, false);
- }
- }
-
- static void window_custom_set_pressed_tab(rct_window* w)
- {
- const auto& info = GetInfo(w);
- auto numTabs = info.Desc.Tabs.size();
- if (numTabs != 0)
- {
- for (size_t i = 0; i < numTabs; i++)
- {
- w->pressed_widgets &= ~(1 << (WIDX_TAB_0 + i));
- }
- w->pressed_widgets |= 1LL << (WIDX_TAB_0 + w->page);
- }
- }
-
- static void window_custom_invalidate(rct_window* w)
- {
- w->widgets[WIDX_BACKGROUND].right = w->width - 1;
- w->widgets[WIDX_BACKGROUND].bottom = w->height - 1;
- w->widgets[WIDX_TITLE].right = w->width - 2;
- w->widgets[WIDX_CLOSE].left = w->width - 13;
- w->widgets[WIDX_CLOSE].right = w->width - 3;
- w->widgets[WIDX_CONTENT_PANEL].right = w->width - 1;
- w->widgets[WIDX_CONTENT_PANEL].bottom = w->height - 1;
- w->widgets[WIDX_CLOSE].text = (w->colours[0] & COLOUR_FLAG_TRANSLUCENT) ? STR_CLOSE_X_WHITE : STR_CLOSE_X;
-
- // Having the content panel visible for transparent windows makes the borders darker than they should be
- // For now just hide it if there are no tabs and the window is not resizable
- auto& info = GetInfo(w);
- auto canResize = (w->flags & WF_RESIZABLE) != 0 && (w->min_width != w->max_width || w->min_height != w->max_height);
- auto numTabs = info.Desc.Tabs.size();
- if (canResize || numTabs != 0)
- {
- w->widgets[WIDX_CONTENT_PANEL].flags &= ~WIDGET_FLAGS::IS_HIDDEN;
- }
- else
- {
- w->widgets[WIDX_CONTENT_PANEL].flags |= WIDGET_FLAGS::IS_HIDDEN;
- }
-
- window_custom_set_pressed_tab(w);
-
- const auto& desc = info.Desc;
- auto ft = Formatter::Common();
- ft.Add(desc.Title.c_str());
-
- size_t scrollIndex = 0;
- for (auto widget = w->widgets; widget->type != WindowWidgetType::Last; widget++)
- {
- if (widget->type == WindowWidgetType::Scroll)
- {
- auto& listView = info.ListViews[scrollIndex];
- auto width = widget->width() + 1 - 2;
- auto height = widget->height() + 1 - 2;
- if (listView.GetScrollbars() == ScrollbarType::Horizontal || listView.GetScrollbars() == ScrollbarType::Both)
+ else if (widgetDesc->Type == "dropdown")
{
- height -= SCROLLBAR_WIDTH + 1;
+ UpdateWidgetSelectedIndex(this, widgetIndex - 1, dropdownIndex);
}
- if (listView.GetScrollbars() == ScrollbarType::Vertical || listView.GetScrollbars() == ScrollbarType::Both)
+ }
+ }
+
+ void OnTextInput(rct_widgetindex widgetIndex, std::string_view text) override
+ {
+ auto& info = GetInfo(this);
+ auto widgetDesc = info.GetCustomWidgetDesc(this, widgetIndex);
+ if (widgetDesc != nullptr)
+ {
+ if (widgetDesc->Type == "textbox")
{
- width -= SCROLLBAR_WIDTH + 1;
+ UpdateWidgetText(this, widgetIndex, text);
+
+ std::vector args;
+ auto ctx = widgetDesc->OnChange.context();
+ duk_push_lstring(ctx, text.data(), text.size());
+ args.push_back(DukValue::take_from_stack(ctx));
+ InvokeEventHandler(info.Owner, widgetDesc->OnChange, args);
}
- listView.Resize({ width, height });
- scrollIndex++;
}
}
- }
- static void window_custom_draw_tab_images(rct_window* w, rct_drawpixelinfo* dpi)
- {
- const auto& customInfo = GetInfo(w);
- const auto& tabs = customInfo.Desc.Tabs;
- size_t tabIndex = 0;
- for (const auto& tab : tabs)
+ ScreenSize OnScrollGetSize(int32_t scrollIndex) override
{
- auto widgetIndex = static_cast(WIDX_TAB_0 + tabIndex);
- auto widget = &w->widgets[widgetIndex];
- if (WidgetIsEnabled(w, widgetIndex))
+ auto& info = GetInfo(this);
+ if (scrollIndex < static_cast(info.ListViews.size()))
{
- auto leftTop = w->windowPos + tab.offset + ScreenCoordsXY{ widget->left, widget->top };
- auto image = tab.imageFrameBase;
- if (static_cast(w->page) == tabIndex && tab.imageFrameDuration != 0 && tab.imageFrameCount != 0)
+ auto size = info.ListViews[scrollIndex].GetSize();
+ return { size.width, size.height };
+ }
+ return {};
+ }
+
+ void OnScrollMouseDown(int32_t scrollIndex, const ScreenCoordsXY& screenCoords) override
+ {
+ auto& info = GetInfo(this);
+ if (scrollIndex < static_cast(info.ListViews.size()))
+ {
+ info.ListViews[scrollIndex].MouseDown(screenCoords);
+ }
+ }
+
+ void OnScrollMouseDrag(int32_t scrollIndex, const ScreenCoordsXY& screenCoords) override
+ {
+ auto& info = GetInfo(this);
+ if (scrollIndex < static_cast(info.ListViews.size()))
+ {
+ info.ListViews[scrollIndex].MouseOver(screenCoords, true);
+ }
+ }
+
+ void OnScrollMouseOver(int32_t scrollIndex, const ScreenCoordsXY& screenCoords) override
+ {
+ auto& info = GetInfo(this);
+ if (scrollIndex < static_cast(info.ListViews.size()))
+ {
+ info.ListViews[scrollIndex].MouseOver(screenCoords, false);
+ }
+ }
+
+ void OnScrollDraw(int32_t scrollIndex, rct_drawpixelinfo& dpi) override
+ {
+ const auto& info = GetInfo(this);
+ if (scrollIndex < static_cast(info.ListViews.size()))
+ {
+ info.ListViews[scrollIndex].Paint(this, &dpi, &scrolls[scrollIndex]);
+ }
+ }
+
+ private:
+ std::optional GetViewportWidgetIndex()
+ {
+ rct_widgetindex widgetIndex = 0;
+ for (auto widget = widgets; widget->type != WindowWidgetType::Last; widget++)
+ {
+ if (widget->type == WindowWidgetType::Viewport)
{
- auto frame = w->frame_no / tab.imageFrameDuration;
- auto imageOffset = frame % tab.imageFrameCount;
- image = image.WithIndex(image.GetIndex() + imageOffset);
+ return widgetIndex;
}
- gfx_draw_sprite(dpi, image.ToUInt32(), leftTop, image.GetTertiary());
+ widgetIndex++;
}
- tabIndex++;
+ return std::nullopt;
}
- }
- static std::optional GetViewportWidgetIndex(rct_window* w)
- {
- rct_widgetindex widgetIndex = 0;
- for (auto widget = w->widgets; widget->type != WindowWidgetType::Last; widget++)
+ void UpdateViewport()
{
- if (widget->type == WindowWidgetType::Viewport)
+ auto viewportWidgetIndex = GetViewportWidgetIndex();
+ if (viewportWidgetIndex)
{
- return widgetIndex;
- }
- widgetIndex++;
- }
- return std::nullopt;
- }
-
- static void window_custom_paint(rct_window* w, rct_drawpixelinfo* dpi)
- {
- WindowDrawWidgets(w, dpi);
- window_custom_draw_tab_images(w, dpi);
- if (w->viewport != nullptr)
- {
- auto widgetIndex = GetViewportWidgetIndex(w);
- if (WidgetIsVisible(w, widgetIndex.value_or(false)))
- {
- window_draw_viewport(dpi, w);
- }
- }
- }
-
- static void window_custom_scrollpaint(rct_window* w, rct_drawpixelinfo* dpi, int32_t scrollIndex)
- {
- const auto& info = GetInfo(w);
- if (scrollIndex < static_cast(info.ListViews.size()))
- {
- info.ListViews[scrollIndex].Paint(w, dpi, &w->scrolls[scrollIndex]);
- }
- }
-
- static void window_custom_update_viewport(rct_window* w)
- {
- auto viewportWidgetIndex = GetViewportWidgetIndex(w);
- if (viewportWidgetIndex)
- {
- auto viewportWidget = &w->widgets[*viewportWidgetIndex];
- auto& customInfo = GetInfo(w);
- auto widgetInfo = customInfo.GetCustomWidgetDesc(w, *viewportWidgetIndex);
- if (widgetInfo != nullptr)
- {
- auto left = w->windowPos.x + viewportWidget->left + 1;
- auto top = w->windowPos.y + viewportWidget->top + 1;
- auto width = viewportWidget->width() - 1;
- auto height = viewportWidget->height() - 1;
- auto viewport = w->viewport;
- if (viewport == nullptr)
+ auto viewportWidget = &widgets[*viewportWidgetIndex];
+ auto& customInfo = GetInfo(this);
+ auto widgetInfo = customInfo.GetCustomWidgetDesc(this, *viewportWidgetIndex);
+ if (widgetInfo != nullptr)
{
- auto mapX = 0;
- auto mapY = 0;
- auto mapZ = 0;
- viewport_create(
- w, { left, top }, width, height, 0, { mapX, mapY, mapZ }, VIEWPORT_FOCUS_TYPE_COORDINATE,
- SPRITE_INDEX_NULL);
- w->flags |= WF_NO_SCROLLING;
- w->Invalidate();
+ auto left = windowPos.x + viewportWidget->left + 1;
+ auto top = windowPos.y + viewportWidget->top + 1;
+ auto wwidth = viewportWidget->width() - 1;
+ auto wheight = viewportWidget->height() - 1;
+ if (viewport == nullptr)
+ {
+ auto mapX = 0;
+ auto mapY = 0;
+ auto mapZ = 0;
+ viewport_create(
+ this, { left, top }, wwidth, wheight, 0, { mapX, mapY, mapZ }, VIEWPORT_FOCUS_TYPE_COORDINATE,
+ SPRITE_INDEX_NULL);
+ flags |= WF_NO_SCROLLING;
+ Invalidate();
+ }
+ else
+ {
+ if (viewport->pos.x != left || viewport->pos.y != top || viewport->width != wwidth
+ || viewport->height != wheight)
+ {
+ viewport->pos.x = left;
+ viewport->pos.y = top;
+ viewport->width = wwidth;
+ viewport->height = wheight;
+ viewport->view_width = wwidth * viewport->zoom;
+ viewport->view_height = wheight * viewport->zoom;
+ Invalidate();
+ }
+ }
+ }
+ }
+ }
+
+ void ChangeTab(size_t tabIndex)
+ {
+ const auto& info = GetInfo(this);
+
+ page = static_cast(tabIndex);
+ frame_no = 0;
+ RefreshWidgets();
+
+ Invalidate();
+ window_event_resize_call(this);
+ window_event_invalidate_call(this);
+ WindowInitScrollWidgets(this);
+ Invalidate();
+
+ InvokeEventHandler(info.Owner, info.Desc.OnTabChange);
+ }
+
+ void SetPressedTab()
+ {
+ const auto& info = GetInfo(this);
+ auto numTabs = info.Desc.Tabs.size();
+ if (numTabs != 0)
+ {
+ for (size_t i = 0; i < numTabs; i++)
+ {
+ pressed_widgets &= ~(1ULL << (WIDX_TAB_0 + i));
+ }
+ pressed_widgets |= 1ULL << (WIDX_TAB_0 + page);
+ }
+ }
+
+ void DrawTabImages(rct_drawpixelinfo& dpi)
+ {
+ const auto& customInfo = GetInfo(this);
+ const auto& tabs = customInfo.Desc.Tabs;
+ size_t tabIndex = 0;
+ for (const auto& tab : tabs)
+ {
+ auto widgetIndex = static_cast(WIDX_TAB_0 + tabIndex);
+ auto widget = &widgets[widgetIndex];
+ if (WidgetIsEnabled(this, widgetIndex))
+ {
+ auto leftTop = windowPos + tab.offset + ScreenCoordsXY{ widget->left, widget->top };
+ auto image = tab.imageFrameBase;
+ if (static_cast(page) == tabIndex && tab.imageFrameDuration != 0 && tab.imageFrameCount != 0)
+ {
+ auto frame = frame_no / tab.imageFrameDuration;
+ auto imageOffset = frame % tab.imageFrameCount;
+ image = image.WithIndex(image.GetIndex() + imageOffset);
+ }
+ gfx_draw_sprite(&dpi, image.ToUInt32(), leftTop, image.GetTertiary());
+ }
+ tabIndex++;
+ }
+ }
+
+ void RefreshWidgets()
+ {
+ enabled_widgets = 0;
+ pressed_widgets = 0;
+ disabled_widgets = 0;
+
+ auto& info = GetInfo(this);
+ auto& widgetList = info.Widgets;
+
+ widgetList.clear();
+ info.WidgetIndexMap.clear();
+ info.ListViews.clear();
+
+ // Add default widgets (window shim)
+ widgetList.insert(widgetList.begin(), std::begin(CustomDefaultWidgets), std::end(CustomDefaultWidgets));
+ for (size_t i = 0; i < widgetList.size(); i++)
+ {
+ info.WidgetIndexMap.push_back(std::numeric_limits::max());
+ }
+ enabled_widgets = 1ULL << WIDX_CLOSE;
+
+ // Add window tabs
+ if (info.Desc.Tabs.size() != 0)
+ {
+ widgetList[WIDX_CONTENT_PANEL].top = 43;
+ }
+ for (size_t tabDescIndex = 0; tabDescIndex < info.Desc.Tabs.size(); tabDescIndex++)
+ {
+ rct_widget widget{};
+ widget.type = WindowWidgetType::Tab;
+ widget.colour = 1;
+ widget.left = static_cast(3 + (tabDescIndex * 31));
+ widget.right = widget.left + 30;
+ widget.top = 17;
+ widget.bottom = 43;
+ widget.image = IMAGE_TYPE_REMAP | SPR_TAB;
+ widget.tooltip = STR_NONE;
+ widgetList.push_back(widget);
+ info.WidgetIndexMap.push_back(std::numeric_limits::max());
+ enabled_widgets |= 1ULL << (widgetList.size() - 1);
+ }
+
+ // Add custom widgets
+ auto firstCustomWidgetIndex = widgetList.size();
+ auto totalWidgets = info.Desc.Widgets.size();
+ auto tabWidgetsOffset = totalWidgets;
+ if (info.Desc.Tabs.size() != 0)
+ {
+ totalWidgets += info.Desc.Tabs[page].Widgets.size();
+ }
+ for (size_t widgetDescIndex = 0; widgetDescIndex < totalWidgets; widgetDescIndex++)
+ {
+ const auto& widgetDesc = widgetDescIndex < info.Desc.Widgets.size()
+ ? info.Desc.Widgets[widgetDescIndex]
+ : info.Desc.Tabs[page].Widgets[widgetDescIndex - tabWidgetsOffset];
+ auto preWidgetSize = widgetList.size();
+ CreateWidget(widgetList, widgetDesc);
+ auto numWidetsAdded = widgetList.size() - preWidgetSize;
+ for (size_t i = 0; i < numWidetsAdded; i++)
+ {
+ info.WidgetIndexMap.push_back(widgetDescIndex);
+ }
+
+ if (widgetDesc.Type == "listview")
+ {
+ CustomListView listView(this, info.ListViews.size());
+ listView.SetScrollbars(widgetDesc.Scrollbars, true);
+ listView.SetColumns(widgetDesc.ListViewColumns, true);
+ listView.SetItems(widgetDesc.ListViewItems, true);
+ listView.SelectedCell = widgetDesc.SelectedCell;
+ listView.ShowColumnHeaders = widgetDesc.ShowColumnHeaders;
+ listView.IsStriped = widgetDesc.IsStriped;
+ listView.OnClick = widgetDesc.OnClick;
+ listView.OnHighlight = widgetDesc.OnHighlight;
+ listView.CanSelect = widgetDesc.CanSelect;
+ info.ListViews.push_back(std::move(listView));
+ }
+ }
+
+ for (size_t i = firstCustomWidgetIndex; i < widgetList.size(); i++)
+ {
+ auto mask = 1ULL << i;
+ auto widgetFlags = widgetList[i].flags;
+ if (widgetFlags & WIDGET_FLAGS::IS_ENABLED)
+ {
+ enabled_widgets |= mask;
+ }
+ if (widgetFlags & WIDGET_FLAGS::IS_PRESSED)
+ {
+ pressed_widgets |= mask;
+ }
+ if (widgetFlags & WIDGET_FLAGS::IS_DISABLED)
+ {
+ disabled_widgets |= mask;
+ }
+ if (widgetFlags & WIDGET_FLAGS::IS_HOLDABLE)
+ {
+ hold_down_widgets |= mask;
+ }
+ }
+
+ widgetList.push_back({ WIDGETS_END });
+ widgets = widgetList.data();
+
+ WindowInitScrollWidgets(this);
+ UpdateViewport();
+ }
+
+ static void CreateWidget(std::vector& widgetList, const CustomWidgetDesc& desc)
+ {
+ rct_widget widget{};
+ widget.colour = 1;
+ widget.left = desc.X;
+ widget.top = desc.Y;
+ widget.right = desc.X + desc.Width - 1;
+ widget.bottom = desc.Y + desc.Height - 1;
+ widget.content = std::numeric_limits::max();
+ widget.tooltip = STR_NONE;
+ if (!desc.Tooltip.empty())
+ {
+ widget.sztooltip = const_cast(desc.Tooltip.c_str());
+ widget.flags |= WIDGET_FLAGS::TOOLTIP_IS_STRING;
+ }
+ widget.flags |= WIDGET_FLAGS::IS_ENABLED;
+ if (desc.IsDisabled)
+ widget.flags |= WIDGET_FLAGS::IS_DISABLED;
+ if (!desc.IsVisible)
+ widget.flags |= WIDGET_FLAGS::IS_HIDDEN;
+
+ if (desc.Type == "button")
+ {
+ if (desc.Image.HasValue())
+ {
+ widget.type = desc.HasBorder ? WindowWidgetType::ImgBtn : WindowWidgetType::FlatBtn;
+ widget.image = desc.Image.ToUInt32();
}
else
{
- if (viewport->pos.x != left || viewport->pos.y != top || viewport->width != width
- || viewport->height != height)
- {
- viewport->pos.x = left;
- viewport->pos.y = top;
- viewport->width = width;
- viewport->height = height;
- viewport->view_width = width * viewport->zoom;
- viewport->view_height = height * viewport->zoom;
- w->Invalidate();
- }
+ widget.type = WindowWidgetType::Button;
+ widget.string = const_cast(desc.Text.c_str());
+ widget.flags |= WIDGET_FLAGS::TEXT_IS_STRING;
}
+ if (desc.IsPressed)
+ {
+ widget.flags |= WIDGET_FLAGS::IS_PRESSED;
+ }
+ widgetList.push_back(widget);
+ }
+ else if (desc.Type == "checkbox")
+ {
+ widget.type = WindowWidgetType::Checkbox;
+ widget.string = const_cast(desc.Text.c_str());
+ widget.flags |= WIDGET_FLAGS::TEXT_IS_STRING;
+ if (desc.IsChecked)
+ {
+ widget.flags |= WIDGET_FLAGS::IS_PRESSED;
+ }
+ widgetList.push_back(widget);
+ }
+ else if (desc.Type == "colourpicker")
+ {
+ widget.type = WindowWidgetType::ColourBtn;
+ widget.image = GetColourButtonImage(desc.Colour);
+ widgetList.push_back(widget);
+ }
+ else if (desc.Type == "custom")
+ {
+ widget.type = WindowWidgetType::Custom;
+ widgetList.push_back(widget);
+ }
+ else if (desc.Type == "dropdown")
+ {
+ widget.type = WindowWidgetType::DropdownMenu;
+ if (desc.SelectedIndex >= 0 && static_cast(desc.SelectedIndex) < desc.Items.size())
+ {
+ 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);
+
+ // Add the dropdown button
+ widget = {};
+ widget.type = WindowWidgetType::Button;
+ widget.colour = 1;
+ widget.left = desc.X + desc.Width - 12;
+ widget.right = desc.X + desc.Width - 2;
+ widget.top = desc.Y + 1;
+ widget.bottom = desc.Y + desc.Height - 2;
+ widget.text = STR_DROPDOWN_GLYPH;
+ widget.tooltip = STR_NONE;
+ widget.flags |= WIDGET_FLAGS::IS_ENABLED;
+ if (desc.IsDisabled)
+ widget.flags |= WIDGET_FLAGS::IS_DISABLED;
+ widgetList.push_back(widget);
+ }
+ else if (desc.Type == "groupbox")
+ {
+ widget.type = WindowWidgetType::Groupbox;
+ widget.string = const_cast(desc.Text.c_str());
+ widget.flags |= WIDGET_FLAGS::TEXT_IS_STRING;
+ widgetList.push_back(widget);
+ }
+ else if (desc.Type == "label")
+ {
+ widget.type = WindowWidgetType::Label;
+ widget.string = const_cast(desc.Text.c_str());
+ widget.flags |= WIDGET_FLAGS::TEXT_IS_STRING;
+ if (desc.TextAlign == TextAlignment::CENTRE)
+ {
+ widget.type = WindowWidgetType::LabelCentred;
+ }
+ widgetList.push_back(widget);
+ }
+ else if (desc.Type == "listview")
+ {
+ widget.type = WindowWidgetType::Scroll;
+ widget.content = 0;
+ if (desc.Scrollbars == ScrollbarType::Horizontal)
+ widget.content = SCROLL_HORIZONTAL;
+ else if (desc.Scrollbars == ScrollbarType::Vertical)
+ widget.content = SCROLL_VERTICAL;
+ else if (desc.Scrollbars == ScrollbarType::Both)
+ widget.content = SCROLL_BOTH;
+ widgetList.push_back(widget);
+ }
+ else if (desc.Type == "spinner")
+ {
+ widget.type = WindowWidgetType::Spinner;
+ widget.string = const_cast(desc.Text.c_str());
+ widget.flags |= WIDGET_FLAGS::TEXT_IS_STRING;
+ widgetList.push_back(widget);
+
+ // Add the decrement button
+ widget = {};
+ widget.type = WindowWidgetType::Button;
+ widget.colour = 1;
+ widget.left = desc.X + desc.Width - 26;
+ widget.right = widget.left + 12;
+ widget.top = desc.Y + 1;
+ widget.bottom = desc.Y + desc.Height - 2;
+ widget.text = STR_NUMERIC_DOWN;
+ widget.tooltip = STR_NONE;
+ widget.flags |= WIDGET_FLAGS::IS_ENABLED;
+ if (desc.IsDisabled)
+ widget.flags |= WIDGET_FLAGS::IS_DISABLED;
+ widget.flags |= WIDGET_FLAGS::IS_HOLDABLE;
+ widgetList.push_back(widget);
+
+ // Add the increment button
+ widget.left = desc.X + desc.Width - 13;
+ widget.right = widget.left + 11;
+ widget.text = STR_NUMERIC_UP;
+ widgetList.push_back(widget);
+ }
+ else if (desc.Type == "textbox")
+ {
+ widget.type = WindowWidgetType::TextBox;
+ widget.string = const_cast(desc.Text.c_str());
+ widget.flags |= WIDGET_FLAGS::TEXT_IS_STRING;
+ widgetList.push_back(widget);
+ }
+ else if (desc.Type == "viewport")
+ {
+ widget.type = WindowWidgetType::Viewport;
+ widget.text = STR_NONE;
+ widgetList.push_back(widget);
}
}
+
+ static rct_windownumber GetNewWindowNumber()
+ {
+ auto result = _nextWindowNumber++;
+ while (window_find_by_number(WC_CUSTOM, result) != nullptr)
+ {
+ result++;
+ }
+ return result;
+ }
+ };
+
+ rct_windownumber CustomWindow::_nextWindowNumber;
+
+ rct_window* window_custom_open(std::shared_ptr owner, DukValue dukDesc)
+ {
+ auto desc = CustomWindowDesc::FromDukValue(dukDesc);
+ uint16_t windowFlags = WF_RESIZABLE | WF_TRANSPARENT;
+ CustomWindow* window{};
+ if (desc.X && desc.Y)
+ {
+ window = WindowCreate(WC_CUSTOM, { *desc.X, *desc.Y }, desc.Width, desc.Height, windowFlags);
+ }
+ else
+ {
+ window = WindowCreate(WC_CUSTOM, desc.Width, desc.Height, windowFlags);
+ }
+ if (window != nullptr)
+ {
+ window->Initialise(owner, desc);
+ }
+ return window;
}
static CustomWindowInfo& GetInfo(rct_window* w)
@@ -877,279 +1183,6 @@ namespace OpenRCT2::Ui::Windows
return *(static_cast(w->custom_info));
}
- static rct_windownumber GetNewWindowNumber()
- {
- auto result = _nextWindowNumber++;
- while (window_find_by_number(WC_CUSTOM, result) != nullptr)
- {
- result++;
- }
- return result;
- }
-
- static void CreateWidget(std::vector& widgetList, const CustomWidgetDesc& desc)
- {
- rct_widget widget{};
- widget.colour = 1;
- widget.left = desc.X;
- widget.top = desc.Y;
- widget.right = desc.X + desc.Width - 1;
- widget.bottom = desc.Y + desc.Height - 1;
- widget.content = std::numeric_limits::max();
- widget.tooltip = STR_NONE;
- if (!desc.Tooltip.empty())
- {
- widget.sztooltip = const_cast(desc.Tooltip.c_str());
- widget.flags |= WIDGET_FLAGS::TOOLTIP_IS_STRING;
- }
- widget.flags |= WIDGET_FLAGS::IS_ENABLED;
- if (desc.IsDisabled)
- widget.flags |= WIDGET_FLAGS::IS_DISABLED;
- if (!desc.IsVisible)
- widget.flags |= WIDGET_FLAGS::IS_HIDDEN;
-
- if (desc.Type == "button")
- {
- if (desc.Image.HasValue())
- {
- widget.type = desc.HasBorder ? WindowWidgetType::ImgBtn : WindowWidgetType::FlatBtn;
- widget.image = desc.Image.ToUInt32();
- }
- else
- {
- widget.type = WindowWidgetType::Button;
- widget.string = const_cast(desc.Text.c_str());
- widget.flags |= WIDGET_FLAGS::TEXT_IS_STRING;
- }
- if (desc.IsPressed)
- {
- widget.flags |= WIDGET_FLAGS::IS_PRESSED;
- }
- widgetList.push_back(widget);
- }
- else if (desc.Type == "checkbox")
- {
- widget.type = WindowWidgetType::Checkbox;
- widget.string = const_cast(desc.Text.c_str());
- widget.flags |= WIDGET_FLAGS::TEXT_IS_STRING;
- if (desc.IsChecked)
- {
- widget.flags |= WIDGET_FLAGS::IS_PRESSED;
- }
- widgetList.push_back(widget);
- }
- else if (desc.Type == "colourpicker")
- {
- widget.type = WindowWidgetType::ColourBtn;
- widget.image = GetColourButtonImage(desc.Colour);
- widgetList.push_back(widget);
- }
- else if (desc.Type == "dropdown")
- {
- widget.type = WindowWidgetType::DropdownMenu;
- if (desc.SelectedIndex >= 0 && static_cast(desc.SelectedIndex) < desc.Items.size())
- {
- 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);
-
- // Add the dropdown button
- widget = {};
- widget.type = WindowWidgetType::Button;
- widget.colour = 1;
- widget.left = desc.X + desc.Width - 12;
- widget.right = desc.X + desc.Width - 2;
- widget.top = desc.Y + 1;
- widget.bottom = desc.Y + desc.Height - 2;
- widget.text = STR_DROPDOWN_GLYPH;
- widget.tooltip = STR_NONE;
- widget.flags |= WIDGET_FLAGS::IS_ENABLED;
- if (desc.IsDisabled)
- widget.flags |= WIDGET_FLAGS::IS_DISABLED;
- widgetList.push_back(widget);
- }
- else if (desc.Type == "groupbox")
- {
- widget.type = WindowWidgetType::Groupbox;
- widget.string = const_cast(desc.Text.c_str());
- widget.flags |= WIDGET_FLAGS::TEXT_IS_STRING;
- widgetList.push_back(widget);
- }
- else if (desc.Type == "label")
- {
- widget.type = WindowWidgetType::Label;
- widget.string = const_cast(desc.Text.c_str());
- widget.flags |= WIDGET_FLAGS::TEXT_IS_STRING;
- if (desc.TextAlign == TextAlignment::CENTRE)
- {
- widget.type = WindowWidgetType::LabelCentred;
- }
- widgetList.push_back(widget);
- }
- else if (desc.Type == "listview")
- {
- widget.type = WindowWidgetType::Scroll;
- widget.content = 0;
- if (desc.Scrollbars == ScrollbarType::Horizontal)
- widget.content = SCROLL_HORIZONTAL;
- else if (desc.Scrollbars == ScrollbarType::Vertical)
- widget.content = SCROLL_VERTICAL;
- else if (desc.Scrollbars == ScrollbarType::Both)
- widget.content = SCROLL_BOTH;
- widgetList.push_back(widget);
- }
- else if (desc.Type == "spinner")
- {
- widget.type = WindowWidgetType::Spinner;
- widget.string = const_cast(desc.Text.c_str());
- widget.flags |= WIDGET_FLAGS::TEXT_IS_STRING;
- widgetList.push_back(widget);
-
- // Add the decrement button
- widget = {};
- widget.type = WindowWidgetType::Button;
- widget.colour = 1;
- widget.left = desc.X + desc.Width - 26;
- widget.right = widget.left + 12;
- widget.top = desc.Y + 1;
- widget.bottom = desc.Y + desc.Height - 2;
- widget.text = STR_NUMERIC_DOWN;
- widget.tooltip = STR_NONE;
- widget.flags |= WIDGET_FLAGS::IS_ENABLED;
- if (desc.IsDisabled)
- widget.flags |= WIDGET_FLAGS::IS_DISABLED;
- widgetList.push_back(widget);
-
- // Add the increment button
- widget.left = desc.X + desc.Width - 13;
- widget.right = widget.left + 11;
- widget.text = STR_NUMERIC_UP;
- widgetList.push_back(widget);
- }
- else if (desc.Type == "textbox")
- {
- widget.type = WindowWidgetType::TextBox;
- widget.string = const_cast(desc.Text.c_str());
- widget.flags |= WIDGET_FLAGS::TEXT_IS_STRING;
- widgetList.push_back(widget);
- }
- else if (desc.Type == "viewport")
- {
- widget.type = WindowWidgetType::Viewport;
- widget.text = STR_NONE;
- widgetList.push_back(widget);
- }
- }
-
- static void RefreshWidgets(rct_window* w)
- {
- w->enabled_widgets = 0;
- w->pressed_widgets = 0;
- w->disabled_widgets = 0;
-
- auto& info = GetInfo(w);
- auto& widgets = info.Widgets;
-
- widgets.clear();
- info.WidgetIndexMap.clear();
- info.ListViews.clear();
-
- // Add default widgets (window shim)
- widgets.insert(widgets.begin(), std::begin(CustomDefaultWidgets), std::end(CustomDefaultWidgets));
- for (size_t i = 0; i < widgets.size(); i++)
- {
- info.WidgetIndexMap.push_back(std::numeric_limits::max());
- }
- w->enabled_widgets = 1ULL << WIDX_CLOSE;
-
- // Add window tabs
- if (info.Desc.Tabs.size() != 0)
- {
- widgets[WIDX_CONTENT_PANEL].top = 43;
- }
- for (size_t tabDescIndex = 0; tabDescIndex < info.Desc.Tabs.size(); tabDescIndex++)
- {
- rct_widget widget{};
- widget.type = WindowWidgetType::Tab;
- widget.colour = 1;
- widget.left = static_cast(3 + (tabDescIndex * 31));
- widget.right = widget.left + 30;
- widget.top = 17;
- widget.bottom = 43;
- widget.image = IMAGE_TYPE_REMAP | SPR_TAB;
- widget.tooltip = STR_NONE;
- widgets.push_back(widget);
- info.WidgetIndexMap.push_back(std::numeric_limits::max());
- w->enabled_widgets |= 1ULL << (widgets.size() - 1);
- }
-
- // Add custom widgets
- auto firstCustomWidgetIndex = widgets.size();
- auto totalWidgets = info.Desc.Widgets.size();
- auto tabWidgetsOffset = totalWidgets;
- if (info.Desc.Tabs.size() != 0)
- {
- totalWidgets += info.Desc.Tabs[w->page].Widgets.size();
- }
- for (size_t widgetDescIndex = 0; widgetDescIndex < totalWidgets; widgetDescIndex++)
- {
- const auto& widgetDesc = widgetDescIndex < info.Desc.Widgets.size()
- ? info.Desc.Widgets[widgetDescIndex]
- : info.Desc.Tabs[w->page].Widgets[widgetDescIndex - tabWidgetsOffset];
- auto preWidgetSize = widgets.size();
- CreateWidget(widgets, widgetDesc);
- auto numWidetsAdded = widgets.size() - preWidgetSize;
- for (size_t i = 0; i < numWidetsAdded; i++)
- {
- info.WidgetIndexMap.push_back(widgetDescIndex);
- }
-
- if (widgetDesc.Type == "listview")
- {
- CustomListView listView(w, info.ListViews.size());
- listView.SetScrollbars(widgetDesc.Scrollbars, true);
- listView.SetColumns(widgetDesc.ListViewColumns, true);
- listView.SetItems(widgetDesc.ListViewItems, true);
- listView.SelectedCell = widgetDesc.SelectedCell;
- listView.ShowColumnHeaders = widgetDesc.ShowColumnHeaders;
- listView.IsStriped = widgetDesc.IsStriped;
- listView.OnClick = widgetDesc.OnClick;
- listView.OnHighlight = widgetDesc.OnHighlight;
- listView.CanSelect = widgetDesc.CanSelect;
- info.ListViews.push_back(std::move(listView));
- }
- }
-
- for (size_t i = firstCustomWidgetIndex; i < widgets.size(); i++)
- {
- auto mask = 1ULL << i;
- auto flags = widgets[i].flags;
- if (flags & WIDGET_FLAGS::IS_ENABLED)
- {
- w->enabled_widgets |= mask;
- }
- if (flags & WIDGET_FLAGS::IS_PRESSED)
- {
- w->pressed_widgets |= mask;
- }
- if (flags & WIDGET_FLAGS::IS_DISABLED)
- {
- w->disabled_widgets |= mask;
- }
- }
-
- widgets.push_back({ WIDGETS_END });
- w->widgets = widgets.data();
-
- WindowInitScrollWidgets(w);
- window_custom_update_viewport(w);
- }
-
static void InvokeEventHandler(const std::shared_ptr& owner, const DukValue& dukHandler)
{
std::vector args;
diff --git a/src/openrct2-ui/scripting/ScGraphicsContext.hpp b/src/openrct2-ui/scripting/ScGraphicsContext.hpp
new file mode 100644
index 0000000000..a8470b1822
--- /dev/null
+++ b/src/openrct2-ui/scripting/ScGraphicsContext.hpp
@@ -0,0 +1,267 @@
+/*****************************************************************************
+ * 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
+
+namespace OpenRCT2::Scripting
+{
+ class ScGraphicsContext
+ {
+ private:
+ duk_context* _ctx{};
+ rct_drawpixelinfo _dpi{};
+
+ std::optional _colour{};
+ std::optional _secondaryColour{};
+ std::optional _ternaryColour{};
+ std::optional _paletteId{};
+ uint8_t _stroke{};
+ uint8_t _fill{};
+
+ public:
+ ScGraphicsContext(duk_context* ctx, const rct_drawpixelinfo& dpi)
+ : _ctx(ctx)
+ , _dpi(dpi)
+ {
+ }
+
+ static void Register(duk_context* ctx)
+ {
+ dukglue_register_property(ctx, &ScGraphicsContext::colour_get, &ScGraphicsContext::colour_set, "colour");
+ dukglue_register_property(
+ ctx, &ScGraphicsContext::secondaryColour_get, &ScGraphicsContext::secondaryColour_set, "secondaryColour");
+ dukglue_register_property(
+ ctx, &ScGraphicsContext::ternaryColour_get, &ScGraphicsContext::ternaryColour_set, "ternaryColour");
+ dukglue_register_property(ctx, &ScGraphicsContext::paletteId_get, &ScGraphicsContext::paletteId_set, "paletteId");
+ dukglue_register_property(ctx, &ScGraphicsContext::fill_get, &ScGraphicsContext::fill_set, "fill");
+ dukglue_register_property(ctx, &ScGraphicsContext::stroke_get, &ScGraphicsContext::stroke_set, "stroke");
+ dukglue_register_property(ctx, &ScGraphicsContext::width_get, nullptr, "width");
+ dukglue_register_property(ctx, &ScGraphicsContext::height_get, nullptr, "height");
+
+ dukglue_register_method(ctx, &ScGraphicsContext::getImage, "getImage");
+ dukglue_register_method(ctx, &ScGraphicsContext::measureText, "measureText");
+
+ dukglue_register_method(ctx, &ScGraphicsContext::box, "box");
+ dukglue_register_method(ctx, &ScGraphicsContext::clear, "clear");
+ dukglue_register_method(ctx, &ScGraphicsContext::clip, "clip");
+ dukglue_register_method(ctx, &ScGraphicsContext::image, "image");
+ dukglue_register_method(ctx, &ScGraphicsContext::line, "line");
+ dukglue_register_method(ctx, &ScGraphicsContext::rect, "rect");
+ dukglue_register_method(ctx, &ScGraphicsContext::text, "text");
+ dukglue_register_method(ctx, &ScGraphicsContext::well, "well");
+ }
+
+ private:
+ DukValue colour_get() const
+ {
+ return ToDuk(_ctx, _colour);
+ }
+
+ void colour_set(DukValue value)
+ {
+ if (value.type() == DukValue::NUMBER)
+ _colour = static_cast(value.as_int());
+ else
+ _colour = {};
+ }
+
+ DukValue secondaryColour_get() const
+ {
+ return ToDuk(_ctx, _secondaryColour);
+ }
+
+ void secondaryColour_set(DukValue value)
+ {
+ if (value.type() == DukValue::NUMBER)
+ _secondaryColour = static_cast(value.as_int());
+ else
+ _secondaryColour = {};
+ }
+
+ DukValue ternaryColour_get() const
+ {
+ return ToDuk(_ctx, _ternaryColour);
+ }
+
+ void ternaryColour_set(DukValue value)
+ {
+ if (value.type() == DukValue::NUMBER)
+ _ternaryColour = static_cast(value.as_int());
+ else
+ _ternaryColour = {};
+ }
+
+ DukValue paletteId_get() const
+ {
+ return ToDuk(_ctx, _paletteId);
+ }
+
+ void paletteId_set(DukValue value)
+ {
+ if (value.type() == DukValue::NUMBER)
+ _paletteId = static_cast(value.as_int());
+ else
+ _paletteId = {};
+ }
+
+ uint8_t fill_get() const
+ {
+ return _fill;
+ }
+
+ void fill_set(uint8_t value)
+ {
+ _fill = value;
+ }
+
+ uint8_t stroke_get() const
+ {
+ return _stroke;
+ }
+
+ void stroke_set(uint8_t value)
+ {
+ _stroke = value;
+ }
+
+ int32_t width_get() const
+ {
+ return _dpi.width;
+ }
+
+ int32_t height_get() const
+ {
+ return _dpi.height;
+ }
+
+ DukValue getImage(uint32_t id)
+ {
+ auto* g1 = gfx_get_g1_element(id);
+ if (g1 == nullptr)
+ {
+ return ToDuk(_ctx, undefined);
+ }
+ else
+ {
+ DukObject obj(_ctx);
+ obj.Set("id", id);
+ obj.Set("offset", ToDuk(_ctx, { g1->x_offset, g1->y_offset }));
+ obj.Set("width", g1->width);
+ obj.Set("height", g1->height);
+
+ obj.Set("isBMP", (g1->flags & G1_FLAG_BMP) != 0);
+ obj.Set("isRLE", (g1->flags & G1_FLAG_RLE_COMPRESSION) != 0);
+ obj.Set("isPalette", (g1->flags & G1_FLAG_PALETTE) != 0);
+ obj.Set("noZoom", (g1->flags & G1_FLAG_NO_ZOOM_DRAW) != 0);
+
+ if (g1->flags & G1_FLAG_HAS_ZOOM_SPRITE)
+ {
+ obj.Set("nextZoomId", id - g1->zoomed_offset);
+ }
+ else
+ {
+ obj.Set("nextZoomId", undefined);
+ }
+ return obj.Take();
+ }
+ }
+
+ DukValue measureText(const std::string& text)
+ {
+ gCurrentFontSpriteBase = FONT_SPRITE_BASE_MEDIUM;
+ auto width = gfx_get_string_width(text);
+ auto height = string_get_height_raw(text.c_str());
+ return ToDuk(_ctx, { width, height });
+ }
+
+ void box(int32_t x, int32_t y, int32_t width, int32_t height)
+ {
+ gfx_fill_rect_inset(&_dpi, { x, y, x + width - 1, y + height - 1 }, _colour.value_or(0), 0);
+ }
+
+ void well(int32_t x, int32_t y, int32_t width, int32_t height)
+ {
+ gfx_fill_rect_inset(
+ &_dpi, { x, y, x + width - 1, y + height - 1 }, _colour.value_or(0),
+ INSET_RECT_FLAG_BORDER_INSET | INSET_RECT_FLAG_FILL_DONT_LIGHTEN);
+ }
+
+ void clear()
+ {
+ gfx_clear(&_dpi, _fill);
+ }
+
+ void clip(int32_t x, int32_t y, int32_t width, int32_t height)
+ {
+ rct_drawpixelinfo newDpi;
+ clip_drawpixelinfo(&newDpi, &_dpi, { x, y }, width, height);
+ _dpi = newDpi;
+ }
+
+ void image(uint32_t id, int32_t x, int32_t y)
+ {
+ ImageId img;
+ img = img.WithIndex(id);
+ if (_paletteId)
+ {
+ img = img.WithRemap(*_paletteId);
+ }
+ else
+ {
+ if (_colour)
+ {
+ img = img.WithPrimary(*_colour);
+ }
+ if (_secondaryColour)
+ {
+ img = img.WithSecondary(*_secondaryColour);
+ }
+ }
+ gfx_draw_sprite(&_dpi, static_cast(img.ToUInt32()), { x, y }, _ternaryColour.value_or(0));
+ }
+
+ void line(int32_t x1, int32_t y1, int32_t x2, int32_t y2)
+ {
+ gfx_draw_line(&_dpi, { { x1, y1 }, { x2, y2 } }, _stroke);
+ }
+
+ void rect(int32_t x, int32_t y, int32_t width, int32_t height)
+ {
+ if (_stroke != 0)
+ {
+ line(x, y, x + width, y);
+ line(x + width - 1, y + 1, x + width - 1, y + height - 1);
+ line(x, y + height - 1, x + width, y + height - 1);
+ line(x, y + 1, x, y + height - 1);
+
+ x++;
+ y++;
+ width -= 2;
+ height -= 2;
+ }
+ if (_fill != 0)
+ {
+ gfx_fill_rect(&_dpi, { x, y, x + width - 1, y + height - 1 }, _fill);
+ }
+ }
+
+ void text(const std::string& text, int32_t x, int32_t y)
+ {
+ gCurrentFontSpriteBase = FONT_SPRITE_BASE_MEDIUM;
+ gfx_draw_string(&_dpi, text.c_str(), _colour.value_or(0), { x, y });
+ }
+ };
+} // namespace OpenRCT2::Scripting
+
+#endif
diff --git a/src/openrct2-ui/scripting/ScWidget.hpp b/src/openrct2-ui/scripting/ScWidget.hpp
index 2973d7c7b2..cc719a8312 100644
--- a/src/openrct2-ui/scripting/ScWidget.hpp
+++ b/src/openrct2-ui/scripting/ScWidget.hpp
@@ -25,6 +25,7 @@
namespace OpenRCT2::Scripting
{
+ class ScWindow;
class ScWidget
{
protected:
@@ -43,6 +44,8 @@ namespace OpenRCT2::Scripting
static DukValue ToDukValue(duk_context* ctx, rct_window* w, rct_widgetindex widgetIndex);
private:
+ std::shared_ptr window_get() const;
+
std::string name_get() const
{
auto w = GetWindow();
@@ -108,6 +111,8 @@ namespace OpenRCT2::Scripting
return "empty";
case WindowWidgetType::Placeholder:
return "placeholder";
+ case WindowWidgetType::Custom:
+ return "custom";
case WindowWidgetType::Last:
return "last";
}
@@ -374,18 +379,7 @@ namespace OpenRCT2::Scripting
}
public:
- 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");
- dukglue_register_property(ctx, &ScWidget::width_get, &ScWidget::width_set, "width");
- dukglue_register_property(ctx, &ScWidget::height_get, &ScWidget::height_set, "height");
- dukglue_register_property(ctx, &ScWidget::isDisabled_get, &ScWidget::isDisabled_set, "isDisabled");
- dukglue_register_property(ctx, &ScWidget::isVisible_get, &ScWidget::isVisible_set, "isVisible");
- }
+ static void Register(duk_context* ctx);
protected:
rct_window* GetWindow() const
diff --git a/src/openrct2-ui/scripting/UiExtensions.cpp b/src/openrct2-ui/scripting/UiExtensions.cpp
index 316b9af0b2..6757a7c3a4 100644
--- a/src/openrct2-ui/scripting/UiExtensions.cpp
+++ b/src/openrct2-ui/scripting/UiExtensions.cpp
@@ -12,6 +12,7 @@
# include "UiExtensions.h"
# include "CustomMenu.h"
+# include "ScGraphicsContext.hpp"
# include "ScTileSelection.hpp"
# include "ScTitleSequence.hpp"
# include "ScUi.hpp"
@@ -29,6 +30,7 @@ void UiScriptExtensions::Extend(ScriptEngine& scriptEngine)
dukglue_register_global(ctx, std::make_shared(), "titleSequenceManager");
dukglue_register_global(ctx, std::make_shared(scriptEngine), "ui");
+ ScGraphicsContext::Register(ctx);
ScTileSelection::Register(ctx);
ScTool::Register(ctx);
ScUi::Register(ctx);
@@ -54,4 +56,22 @@ void UiScriptExtensions::Extend(ScriptEngine& scriptEngine)
InitialiseCustomMenuItems(scriptEngine);
}
+std::shared_ptr ScWidget::window_get() const
+{
+ return std::make_shared(_class, _number);
+}
+
+void ScWidget::Register(duk_context* ctx)
+{
+ dukglue_register_property(ctx, &ScWidget::window_get, nullptr, "window");
+ 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");
+ dukglue_register_property(ctx, &ScWidget::width_get, &ScWidget::width_set, "width");
+ dukglue_register_property(ctx, &ScWidget::height_get, &ScWidget::height_set, "height");
+ dukglue_register_property(ctx, &ScWidget::isDisabled_get, &ScWidget::isDisabled_set, "isDisabled");
+ dukglue_register_property(ctx, &ScWidget::isVisible_get, &ScWidget::isVisible_set, "isVisible");
+}
+
#endif
diff --git a/src/openrct2/drawing/Drawing.String.cpp b/src/openrct2/drawing/Drawing.String.cpp
index 84f9eea202..a793c19dce 100644
--- a/src/openrct2/drawing/Drawing.String.cpp
+++ b/src/openrct2/drawing/Drawing.String.cpp
@@ -356,7 +356,7 @@ void draw_string_centred_raw(rct_drawpixelinfo* dpi, const ScreenCoordsXY& coord
}
}
-int32_t string_get_height_raw(char* buffer)
+int32_t string_get_height_raw(std::string_view text)
{
uint16_t fontBase = gCurrentFontSpriteBase;
@@ -366,7 +366,7 @@ int32_t string_get_height_raw(char* buffer)
else if (fontBase == FONT_SPRITE_BASE_TINY)
height += 6;
- FmtString fmt(buffer);
+ FmtString fmt(text);
for (const auto& token : fmt)
{
switch (token.kind)
diff --git a/src/openrct2/drawing/Drawing.h b/src/openrct2/drawing/Drawing.h
index 8143c7f65d..221eaa5e1a 100644
--- a/src/openrct2/drawing/Drawing.h
+++ b/src/openrct2/drawing/Drawing.h
@@ -459,6 +459,13 @@ public:
return result;
}
+ constexpr ImageId WithRemap(uint8_t paletteId)
+ {
+ ImageId result = *this;
+ result._value = (_value & ~MASK_REMAP) | ((paletteId << SHIFT_REMAP) & MASK_REMAP) | FLAG_PRIMARY;
+ return result;
+ }
+
constexpr ImageId WithPrimary(colour_t colour)
{
ImageId result = *this;
@@ -757,7 +764,7 @@ int32_t gfx_wrap_string(char* buffer, int32_t width, int32_t* num_lines, int32_t
int32_t gfx_get_string_width(std::string_view text);
int32_t gfx_get_string_width_new_lined(std::string_view text);
int32_t gfx_get_string_width_no_formatting(std::string_view text);
-int32_t string_get_height_raw(char* buffer);
+int32_t string_get_height_raw(std::string_view text);
int32_t gfx_clip_string(char* buffer, int32_t width);
void shorten_path(utf8* buffer, size_t bufferSize, const utf8* path, int32_t availableWidth);
void ttf_draw_string(
diff --git a/src/openrct2/interface/Widget.h b/src/openrct2/interface/Widget.h
index 565c203242..3f8735f90b 100644
--- a/src/openrct2/interface/Widget.h
+++ b/src/openrct2/interface/Widget.h
@@ -36,6 +36,7 @@ enum class WindowWidgetType : uint8_t
Scroll = 22,
Checkbox = 23,
Placeholder = 25,
+ Custom = 28,
TextBox = 27,
Last = 26,
};
@@ -137,6 +138,7 @@ void WidgetDraw(rct_drawpixelinfo* dpi, rct_window* w, rct_widgetindex widgetInd
bool WidgetIsEnabled(rct_window* w, rct_widgetindex widgetIndex);
bool WidgetIsDisabled(rct_window* w, rct_widgetindex widgetIndex);
+bool WidgetIsHoldable(rct_window* w, rct_widgetindex widgetIndex);
bool WidgetIsVisible(rct_window* w, rct_widgetindex widgetIndex);
bool WidgetIsPressed(rct_window* w, rct_widgetindex widgetIndex);
bool WidgetIsHighlighted(rct_window* w, rct_widgetindex widgetIndex);
@@ -147,6 +149,7 @@ void WidgetScrollGetPart(
void WidgetSetEnabled(rct_window* w, rct_widgetindex widgetIndex, bool enabled);
void WidgetSetDisabled(rct_window* w, rct_widgetindex widgetIndex, bool value);
+void WidgetSetHoldable(rct_window* w, rct_widgetindex widgetIndex, bool value);
void WidgetSetVisible(rct_window* w, rct_widgetindex widgetIndex, bool value);
void WidgetSetCheckboxValue(rct_window* w, rct_widgetindex widgetIndex, int32_t value);
diff --git a/src/openrct2/interface/Window.cpp b/src/openrct2/interface/Window.cpp
index dd9a29a1b0..da2abfa055 100644
--- a/src/openrct2/interface/Window.cpp
+++ b/src/openrct2/interface/Window.cpp
@@ -1530,9 +1530,10 @@ void window_event_scroll_mousedown_call(rct_window* w, int32_t scrollIndex, cons
void window_event_scroll_mousedrag_call(rct_window* w, int32_t scrollIndex, const ScreenCoordsXY& screenCoords)
{
- if (w->event_handlers != nullptr)
- if (w->event_handlers->scroll_mousedrag != nullptr)
- w->event_handlers->scroll_mousedrag(w, scrollIndex, screenCoords);
+ if (w->event_handlers == nullptr)
+ w->OnScrollMouseDrag(scrollIndex, screenCoords);
+ else if (w->event_handlers->scroll_mousedrag != nullptr)
+ w->event_handlers->scroll_mousedrag(w, scrollIndex, screenCoords);
}
void window_event_scroll_mouseover_call(rct_window* w, int32_t scrollIndex, const ScreenCoordsXY& screenCoords)
diff --git a/src/openrct2/interface/Window.h b/src/openrct2/interface/Window.h
index 0aa1795afa..a83aadc053 100644
--- a/src/openrct2/interface/Window.h
+++ b/src/openrct2/interface/Window.h
@@ -77,6 +77,7 @@ namespace WIDGET_FLAGS
const WidgetFlags IS_DISABLED = 1 << 3;
const WidgetFlags TOOLTIP_IS_STRING = 1 << 4;
const WidgetFlags IS_HIDDEN = 1 << 5;
+ const WidgetFlags IS_HOLDABLE = 1 << 6;
} // namespace WIDGET_FLAGS
enum class WindowWidgetType : uint8_t;
diff --git a/src/openrct2/interface/Window_internal.h b/src/openrct2/interface/Window_internal.h
index 60a4a01ffe..8272f45d95 100644
--- a/src/openrct2/interface/Window_internal.h
+++ b/src/openrct2/interface/Window_internal.h
@@ -111,6 +111,11 @@ struct rct_window
rct_window() = default;
virtual ~rct_window() = default;
+ virtual bool IsLegacy()
+ {
+ return true;
+ }
+
// Events
virtual void OnOpen()
{
@@ -133,6 +138,9 @@ struct rct_window
virtual void OnDraw(rct_drawpixelinfo& dpi)
{
}
+ virtual void OnDrawWidget(rct_widgetindex widgetIndex, rct_drawpixelinfo& dpi)
+ {
+ }
virtual OpenRCT2String OnTooltip(rct_widgetindex widgetIndex, rct_string_id fallback)
{
return { fallback, {} };
@@ -153,6 +161,9 @@ struct rct_window
{
return {};
}
+ virtual void OnScrollMouseDrag(int32_t scrollIndex, const ScreenCoordsXY& screenCoords)
+ {
+ }
virtual void OnScrollMouseOver(int32_t scrollIndex, const ScreenCoordsXY& screenCoords)
{
}
diff --git a/src/openrct2/scripting/Duktape.hpp b/src/openrct2/scripting/Duktape.hpp
index ced2950f18..73cddc4e52 100644
--- a/src/openrct2/scripting/Duktape.hpp
+++ b/src/openrct2/scripting/Duktape.hpp
@@ -50,6 +50,11 @@ namespace OpenRCT2::Scripting
return value.type() == DukValue::BOOLEAN ? value.as_bool() : defaultValue;
}
+ enum class DukUndefined
+ {
+ };
+ constexpr DukUndefined undefined{};
+
/**
* Allows creation of an object on the duktape stack and setting properties on it before
* retrieving the DukValue instance of it.
@@ -88,6 +93,13 @@ namespace OpenRCT2::Scripting
duk_put_prop_string(_ctx, _idx, name);
}
+ void Set(const char* name, DukUndefined)
+ {
+ EnsureObjectPushed();
+ duk_push_undefined(_ctx);
+ duk_put_prop_string(_ctx, _idx, name);
+ }
+
void Set(const char* name, bool value)
{
EnsureObjectPushed();
@@ -277,12 +289,24 @@ namespace OpenRCT2::Scripting
return DukValue::take_from_stack(ctx);
}
+ template<> inline DukValue ToDuk(duk_context* ctx, const DukUndefined&)
+ {
+ duk_push_undefined(ctx);
+ return DukValue::take_from_stack(ctx);
+ }
+
template<> inline DukValue ToDuk(duk_context* ctx, const bool& value)
{
duk_push_boolean(ctx, value);
return DukValue::take_from_stack(ctx);
}
+ template<> inline DukValue ToDuk(duk_context* ctx, const uint8_t& value)
+ {
+ duk_push_int(ctx, value);
+ return DukValue::take_from_stack(ctx);
+ }
+
template<> inline DukValue ToDuk(duk_context* ctx, const int32_t& value)
{
duk_push_int(ctx, value);
@@ -401,6 +425,14 @@ namespace OpenRCT2::Scripting
return result;
}
+ template<> inline DukValue ToDuk(duk_context* ctx, const ScreenSize& value)
+ {
+ DukObject dukCoords(ctx);
+ dukCoords.Set("width", value.width);
+ dukCoords.Set("height", value.height);
+ return dukCoords.Take();
+ }
+
} // namespace OpenRCT2::Scripting
#endif
diff --git a/src/openrct2/scripting/ScNetwork.hpp b/src/openrct2/scripting/ScNetwork.hpp
index 9497eaf600..ae8ddec4b1 100644
--- a/src/openrct2/scripting/ScNetwork.hpp
+++ b/src/openrct2/scripting/ScNetwork.hpp
@@ -369,6 +369,39 @@ namespace OpenRCT2::Scripting
return nullptr;
}
+ DukValue stats_get() const
+ {
+# ifndef DISABLE_NETWORK
+ auto obj = OpenRCT2::Scripting::DukObject(_context);
+ auto networkStats = network_get_stats();
+ {
+ duk_push_array(_context);
+ duk_uarridx_t index = 0;
+ for (auto v : networkStats.bytesReceived)
+ {
+ duk_push_number(_context, v);
+ duk_put_prop_index(_context, -2, index);
+ index++;
+ }
+ obj.Set("bytesReceived", DukValue::take_from_stack(_context));
+ }
+ {
+ duk_push_array(_context);
+ duk_uarridx_t index = 0;
+ for (auto v : networkStats.bytesSent)
+ {
+ duk_push_number(_context, v);
+ duk_put_prop_index(_context, -2, index);
+ index++;
+ }
+ obj.Set("bytesSent", DukValue::take_from_stack(_context));
+ }
+ return obj.Take();
+# else
+ return ToDuk(_context, nullptr);
+# endif
+ }
+
std::shared_ptr getGroup(int32_t index) const
{
# ifndef DISABLE_NETWORK
@@ -490,6 +523,7 @@ namespace OpenRCT2::Scripting
dukglue_register_property(ctx, &ScNetwork::players_get, nullptr, "players");
dukglue_register_property(ctx, &ScNetwork::currentPlayer_get, nullptr, "currentPlayer");
dukglue_register_property(ctx, &ScNetwork::defaultGroup_get, &ScNetwork::defaultGroup_set, "defaultGroup");
+ dukglue_register_property(ctx, &ScNetwork::stats_get, nullptr, "stats");
dukglue_register_method(ctx, &ScNetwork::addGroup, "addGroup");
dukglue_register_method(ctx, &ScNetwork::getGroup, "getGroup");
dukglue_register_method(ctx, &ScNetwork::removeGroup, "removeGroup");
diff --git a/src/openrct2/scripting/ScriptEngine.cpp b/src/openrct2/scripting/ScriptEngine.cpp
index b7c3911337..1892c0d48b 100644
--- a/src/openrct2/scripting/ScriptEngine.cpp
+++ b/src/openrct2/scripting/ScriptEngine.cpp
@@ -44,7 +44,7 @@
using namespace OpenRCT2;
using namespace OpenRCT2::Scripting;
-static constexpr int32_t OPENRCT2_PLUGIN_API_VERSION = 22;
+static constexpr int32_t OPENRCT2_PLUGIN_API_VERSION = 23;
struct ExpressionStringifier final
{
@@ -73,8 +73,14 @@ private:
_ss << "\n" << std::string(_indent, ' ');
}
- void Stringify(const DukValue& val, bool canStartWithNewLine)
+ void Stringify(const DukValue& val, bool canStartWithNewLine, int32_t nestLevel)
{
+ if (nestLevel >= 8)
+ {
+ _ss << "[...]";
+ return;
+ }
+
switch (val.type())
{
case DukValue::Type::UNDEFINED:
@@ -99,11 +105,11 @@ private:
}
else if (val.is_array())
{
- StringifyArray(val, canStartWithNewLine);
+ StringifyArray(val, canStartWithNewLine, nestLevel);
}
else
{
- StringifyObject(val, canStartWithNewLine);
+ StringifyObject(val, canStartWithNewLine, nestLevel);
}
break;
case DukValue::Type::BUFFER:
@@ -118,7 +124,7 @@ private:
}
}
- void StringifyArray(const DukValue& val, bool canStartWithNewLine)
+ void StringifyArray(const DukValue& val, bool canStartWithNewLine, int32_t nestLevel)
{
constexpr auto maxItemsToShow = 4;
@@ -139,7 +145,7 @@ private:
{
_ss << ", ";
}
- Stringify(DukValue::take_from_stack(_context), false);
+ Stringify(DukValue::take_from_stack(_context), false, nestLevel + 1);
}
}
_ss << " ]";
@@ -177,7 +183,7 @@ private:
{
if (duk_get_prop_index(_context, -1, i))
{
- Stringify(DukValue::take_from_stack(_context), false);
+ Stringify(DukValue::take_from_stack(_context), false, nestLevel + 1);
}
}
}
@@ -191,7 +197,7 @@ private:
duk_pop(_context);
}
- void StringifyObject(const DukValue& val, bool canStartWithNewLine)
+ void StringifyObject(const DukValue& val, bool canStartWithNewLine, int32_t nestLevel)
{
auto numEnumerables = GetNumEnumerablesOnObject(val);
if (numEnumerables == 0)
@@ -222,7 +228,7 @@ private:
// For some reason the key was not a string
_ss << "?: ";
}
- Stringify(value, true);
+ Stringify(value, true, nestLevel + 1);
index++;
}
duk_pop_2(_context);
@@ -261,7 +267,7 @@ private:
// For some reason the key was not a string
_ss << "?: ";
}
- Stringify(value, true);
+ Stringify(value, true, nestLevel + 1);
index++;
}
duk_pop_2(_context);
@@ -343,7 +349,7 @@ public:
static std::string StringifyExpression(const DukValue& val)
{
ExpressionStringifier instance(val.context());
- instance.Stringify(val, false);
+ instance.Stringify(val, false, 0);
return instance._ss.str();
}
};
@@ -691,17 +697,27 @@ std::future ScriptEngine::Eval(const std::string& s)
DukValue ScriptEngine::ExecutePluginCall(
const std::shared_ptr& plugin, const DukValue& func, const std::vector& args, bool isGameStateMutable)
+{
+ duk_push_undefined(_context);
+ auto dukUndefined = DukValue::take_from_stack(_context);
+ return ExecutePluginCall(plugin, func, dukUndefined, args, isGameStateMutable);
+}
+
+DukValue ScriptEngine::ExecutePluginCall(
+ const std::shared_ptr& plugin, const DukValue& func, const DukValue& thisValue, const std::vector& args,
+ bool isGameStateMutable)
{
DukStackFrame frame(_context);
if (func.is_function())
{
ScriptExecutionInfo::PluginScope scope(_execInfo, plugin, isGameStateMutable);
func.push();
+ thisValue.push();
for (const auto& arg : args)
{
arg.push();
}
- auto result = duk_pcall(_context, static_cast(args.size()));
+ auto result = duk_pcall_method(_context, static_cast(args.size()));
if (result == DUK_EXEC_SUCCESS)
{
return DukValue::take_from_stack(_context);
diff --git a/src/openrct2/scripting/ScriptEngine.h b/src/openrct2/scripting/ScriptEngine.h
index 9a4c034b90..0c5ba8df20 100644
--- a/src/openrct2/scripting/ScriptEngine.h
+++ b/src/openrct2/scripting/ScriptEngine.h
@@ -202,6 +202,9 @@ namespace OpenRCT2::Scripting
DukValue ExecutePluginCall(
const std::shared_ptr& plugin, const DukValue& func, const std::vector& args,
bool isGameStateMutable);
+ DukValue ExecutePluginCall(
+ const std::shared_ptr& plugin, const DukValue& func, const DukValue& thisValue,
+ const std::vector& args, bool isGameStateMutable);
void LogPluginInfo(const std::shared_ptr& plugin, std::string_view message);