From 82b1a5eb6db4d4b1797e9fb55dbaf62d3555dd6b Mon Sep 17 00:00:00 2001 From: Ted John Date: Sun, 31 Jan 2021 14:07:21 +0000 Subject: [PATCH 01/10] Convert CustomWindow into a class --- src/openrct2-ui/scripting/CustomWindow.cpp | 1416 ++++++++++---------- src/openrct2/interface/Window.cpp | 7 +- src/openrct2/interface/Window_internal.h | 3 + 3 files changed, 703 insertions(+), 723 deletions(-) diff --git a/src/openrct2-ui/scripting/CustomWindow.cpp b/src/openrct2-ui/scripting/CustomWindow.cpp index cd0683ae7a..522fb26ec5 100644 --- a/src/openrct2-ui/scripting/CustomWindow.cpp +++ b/src/openrct2-ui/scripting/CustomWindow.cpp @@ -18,6 +18,7 @@ # include # include # include +# include # include # include # include @@ -49,39 +50,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 @@ -402,474 +370,755 @@ 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 : public rct_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 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); + } + } 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) + { + 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; + } + } + + 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 == "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 rct_windownumber GetNewWindowNumber() + { + auto result = _nextWindowNumber++; + while (window_find_by_number(WC_CUSTOM, result) != nullptr) + { + result++; + } + return result; + } + }; + + 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 +1126,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/interface/Window.cpp b/src/openrct2/interface/Window.cpp index dc44f90544..207564a551 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_mouseover != 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_internal.h b/src/openrct2/interface/Window_internal.h index ab577334d0..7189c99c8d 100644 --- a/src/openrct2/interface/Window_internal.h +++ b/src/openrct2/interface/Window_internal.h @@ -154,6 +154,9 @@ struct rct_window { return {}; } + virtual void OnScrollMouseDrag(int32_t scrollIndex, const ScreenCoordsXY& screenCoords) + { + } virtual void OnScrollMouseOver(int32_t scrollIndex, const ScreenCoordsXY& screenCoords) { } From 93f17f11758bb3afdaedd881362f57d82fde260d Mon Sep 17 00:00:00 2001 From: Ted John Date: Sun, 31 Jan 2021 23:23:22 +0000 Subject: [PATCH 02/10] Add custom widget logic and network stats API --- distribution/openrct2.d.ts | 31 ++++++- src/openrct2-ui/interface/Window.cpp | 24 ++++- src/openrct2-ui/interface/Window.h | 4 + src/openrct2-ui/libopenrct2ui.vcxproj | 1 + src/openrct2-ui/scripting/CustomWindow.cpp | 47 +++++++++- .../scripting/ScGraphicsContext.hpp | 90 +++++++++++++++++++ src/openrct2-ui/scripting/ScWidget.hpp | 5 +- src/openrct2-ui/scripting/UiExtensions.cpp | 7 ++ src/openrct2/interface/Widget.h | 1 + src/openrct2/interface/Window.cpp | 2 +- src/openrct2/interface/Window_internal.h | 8 ++ src/openrct2/scripting/ScNetwork.hpp | 34 +++++++ src/openrct2/scripting/ScriptEngine.cpp | 12 ++- src/openrct2/scripting/ScriptEngine.h | 3 + 14 files changed, 262 insertions(+), 7 deletions(-) create mode 100644 src/openrct2-ui/scripting/ScGraphicsContext.hpp diff --git a/distribution/openrct2.d.ts b/distribution/openrct2.d.ts index fd3dad75de..8c462582f7 100644 --- a/distribution/openrct2.d.ts +++ b/distribution/openrct2.d.ts @@ -1397,6 +1397,7 @@ declare global { readonly players: Player[]; readonly currentPlayer: Player; defaultGroup: number; + readonly stats: NetworkStats; getServerInfo(): ServerInfo; addGroup(): void; @@ -1442,6 +1443,11 @@ declare global { readonly providerWebsite: string; } + interface NetworkStats { + bytesReceived: number[]; + bytesSent: number[]; + } + type PermissionType = "chat" | "terraform" | @@ -1996,14 +2002,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 +2047,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[]; @@ -2191,6 +2203,21 @@ declare global { scrollTo(position: CoordsXY | CoordsXYZ): void; } + /** + * API for drawing graphics. + */ + interface GraphicsContext { + stroke: number; + fill: number; + + clear(): void; + clip(x: number, y: number, width: number, height: number): void; + line(x1: number, y1: number, x2: number, y2: number): void; + rect(x: number, y: number, width: number, height: number): void; + fillRect(x: number, y: number, width: number, height: number): void; + image(image: number, x: number, y: number): void; + } + /** * 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/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 522fb26ec5..c6306abc3c 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" @@ -83,6 +85,7 @@ namespace OpenRCT2::Ui::Windows // Event handlers DukValue OnClick; DukValue OnChange; + DukValue OnDraw; DukValue OnIncrement; DukValue OnDecrement; DukValue OnHighlight; @@ -131,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()) @@ -375,7 +382,7 @@ namespace OpenRCT2::Ui::Windows static void InvokeEventHandler( const std::shared_ptr& owner, const DukValue& dukHandler, const std::vector& args); - class CustomWindow : public rct_window + class CustomWindow : public Window { private: static rct_windownumber _nextWindowNumber; @@ -536,6 +543,37 @@ namespace OpenRCT2::Ui::Windows } } + 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); + } + // auto widgetDpi = dpi.Crop( + // { windowPos.x + widget.left, windowPos.y + widget.top }, { widget.width(), widget.height() }); + } + } + else + { + Window::OnDrawWidget(widgetIndex, dpi); + } + } + void OnMouseUp(rct_widgetindex widgetIndex) override { switch (widgetIndex) @@ -988,6 +1026,11 @@ namespace OpenRCT2::Ui::Windows 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; @@ -1101,6 +1144,8 @@ namespace OpenRCT2::Ui::Windows } }; + rct_windownumber CustomWindow::_nextWindowNumber; + rct_window* window_custom_open(std::shared_ptr owner, DukValue dukDesc) { auto desc = CustomWindowDesc::FromDukValue(dukDesc); diff --git a/src/openrct2-ui/scripting/ScGraphicsContext.hpp b/src/openrct2-ui/scripting/ScGraphicsContext.hpp new file mode 100644 index 0000000000..0313d0a6e8 --- /dev/null +++ b/src/openrct2-ui/scripting/ScGraphicsContext.hpp @@ -0,0 +1,90 @@ +/***************************************************************************** + * 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{}; + + 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::fill_get, &ScGraphicsContext::fill_set, "fill"); + dukglue_register_property(ctx, &ScGraphicsContext::stroke_get, &ScGraphicsContext::stroke_set, "stroke"); + dukglue_register_method(ctx, &ScGraphicsContext::clear, "clear"); + dukglue_register_method(ctx, &ScGraphicsContext::clip, "clip"); + dukglue_register_method(ctx, &ScGraphicsContext::line, "line"); + dukglue_register_method(ctx, &ScGraphicsContext::fillRect, "fillRect"); + } + + private: + 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; + } + + 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 line(int32_t x1, int32_t y1, int32_t x2, int32_t y2) + { + gfx_draw_line(&_dpi, { { x1, y1 }, { x2, y2 } }, _stroke); + } + + void fillRect(int32_t x, int32_t y, int32_t width, int32_t height) + { + gfx_fill_rect(&_dpi, { x, y, x + width - 1, y + height - 1 }, _fill); + } + }; +} // namespace OpenRCT2::Scripting + +#endif diff --git a/src/openrct2-ui/scripting/ScWidget.hpp b/src/openrct2-ui/scripting/ScWidget.hpp index 2973d7c7b2..6a85ceec98 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(); @@ -376,7 +379,7 @@ namespace OpenRCT2::Scripting public: static void Register(duk_context* ctx) { - // Common + 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"); diff --git a/src/openrct2-ui/scripting/UiExtensions.cpp b/src/openrct2-ui/scripting/UiExtensions.cpp index 316b9af0b2..ea3d4a7ce2 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,9 @@ void UiScriptExtensions::Extend(ScriptEngine& scriptEngine) InitialiseCustomMenuItems(scriptEngine); } +std::shared_ptr ScWidget::window_get() const +{ + return std::make_shared(_class, _number); +} + #endif diff --git a/src/openrct2/interface/Widget.h b/src/openrct2/interface/Widget.h index 565c203242..c4b7a7b333 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, }; diff --git a/src/openrct2/interface/Window.cpp b/src/openrct2/interface/Window.cpp index 207564a551..087cad7c5c 100644 --- a/src/openrct2/interface/Window.cpp +++ b/src/openrct2/interface/Window.cpp @@ -1532,7 +1532,7 @@ void window_event_scroll_mousedrag_call(rct_window* w, int32_t scrollIndex, cons { if (w->event_handlers == nullptr) w->OnScrollMouseDrag(scrollIndex, screenCoords); - else if (w->event_handlers->scroll_mouseover != nullptr) + else if (w->event_handlers->scroll_mousedrag != nullptr) w->event_handlers->scroll_mousedrag(w, scrollIndex, screenCoords); } diff --git a/src/openrct2/interface/Window_internal.h b/src/openrct2/interface/Window_internal.h index 7189c99c8d..ed4dbc64f1 100644 --- a/src/openrct2/interface/Window_internal.h +++ b/src/openrct2/interface/Window_internal.h @@ -112,6 +112,11 @@ struct rct_window rct_window() = default; virtual ~rct_window() = default; + virtual bool IsLegacy() + { + return true; + } + // Events virtual void OnOpen() { @@ -134,6 +139,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, {} }; 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..9f639dcbe6 100644 --- a/src/openrct2/scripting/ScriptEngine.cpp +++ b/src/openrct2/scripting/ScriptEngine.cpp @@ -691,17 +691,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); From 081c1895ce84a74e4b330a84a9a2161114f7b718 Mon Sep 17 00:00:00 2001 From: Ted John Date: Tue, 2 Feb 2021 18:50:53 +0000 Subject: [PATCH 03/10] Add box and well APIs --- distribution/openrct2.d.ts | 6 ++-- .../scripting/ScGraphicsContext.hpp | 28 +++++++++++++++++-- 2 files changed, 30 insertions(+), 4 deletions(-) diff --git a/distribution/openrct2.d.ts b/distribution/openrct2.d.ts index 8c462582f7..5edc75169f 100644 --- a/distribution/openrct2.d.ts +++ b/distribution/openrct2.d.ts @@ -2207,15 +2207,17 @@ declare global { * API for drawing graphics. */ interface GraphicsContext { + colour: number; stroke: number; fill: number; clear(): void; clip(x: number, y: number, width: number, height: number): void; + box(x: number, y: number, width: number, height: number): void; + image(image: 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; - fillRect(x: number, y: number, width: number, height: number): void; - image(image: number, x: number, y: number): void; + well(x: number, y: number, width: number, height: number): void; } /** diff --git a/src/openrct2-ui/scripting/ScGraphicsContext.hpp b/src/openrct2-ui/scripting/ScGraphicsContext.hpp index 0313d0a6e8..40c2f3e9d4 100644 --- a/src/openrct2-ui/scripting/ScGraphicsContext.hpp +++ b/src/openrct2-ui/scripting/ScGraphicsContext.hpp @@ -22,6 +22,7 @@ namespace OpenRCT2::Scripting duk_context* _ctx{}; rct_drawpixelinfo _dpi{}; + colour_t _colour{}; uint8_t _stroke{}; uint8_t _fill{}; @@ -34,15 +35,28 @@ namespace OpenRCT2::Scripting static void Register(duk_context* ctx) { + dukglue_register_property(ctx, &ScGraphicsContext::colour_get, &ScGraphicsContext::colour_set, "colour"); dukglue_register_property(ctx, &ScGraphicsContext::fill_get, &ScGraphicsContext::fill_set, "fill"); dukglue_register_property(ctx, &ScGraphicsContext::stroke_get, &ScGraphicsContext::stroke_set, "stroke"); + 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::line, "line"); - dukglue_register_method(ctx, &ScGraphicsContext::fillRect, "fillRect"); + dukglue_register_method(ctx, &ScGraphicsContext::rect, "rect"); + dukglue_register_method(ctx, &ScGraphicsContext::well, "well"); } private: + colour_t colour_get() const + { + return _colour; + } + + void colour_set(colour_t value) + { + _colour = value; + } + uint8_t fill_get() const { return _fill; @@ -63,6 +77,16 @@ namespace OpenRCT2::Scripting _stroke = value; } + 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, 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, INSET_RECT_FLAG_BORDER_INSET | INSET_RECT_FLAG_FILL_DONT_LIGHTEN); + } + void clear() { gfx_clear(&_dpi, _fill); @@ -80,7 +104,7 @@ namespace OpenRCT2::Scripting gfx_draw_line(&_dpi, { { x1, y1 }, { x2, y2 } }, _stroke); } - void fillRect(int32_t x, int32_t y, int32_t width, int32_t height) + void rect(int32_t x, int32_t y, int32_t width, int32_t height) { gfx_fill_rect(&_dpi, { x, y, x + width - 1, y + height - 1 }, _fill); } From c1b0df5de6d573cb42c1e6be51df30521179ab2d Mon Sep 17 00:00:00 2001 From: Ted John Date: Tue, 2 Feb 2021 23:48:46 +0000 Subject: [PATCH 04/10] Improve graphics plugin API --- distribution/openrct2.d.ts | 33 +++- .../scripting/ScGraphicsContext.hpp | 169 +++++++++++++++++- src/openrct2/drawing/Drawing.String.cpp | 4 +- src/openrct2/drawing/Drawing.h | 9 +- src/openrct2/scripting/Duktape.hpp | 32 ++++ 5 files changed, 234 insertions(+), 13 deletions(-) diff --git a/distribution/openrct2.d.ts b/distribution/openrct2.d.ts index 5edc75169f..5a707d838a 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. @@ -2207,19 +2215,40 @@ declare global { * API for drawing graphics. */ interface GraphicsContext { - colour: number; + 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(image: number, x: number, y: 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/scripting/ScGraphicsContext.hpp b/src/openrct2-ui/scripting/ScGraphicsContext.hpp index 40c2f3e9d4..a8470b1822 100644 --- a/src/openrct2-ui/scripting/ScGraphicsContext.hpp +++ b/src/openrct2-ui/scripting/ScGraphicsContext.hpp @@ -22,7 +22,10 @@ namespace OpenRCT2::Scripting duk_context* _ctx{}; rct_drawpixelinfo _dpi{}; - colour_t _colour{}; + std::optional _colour{}; + std::optional _secondaryColour{}; + std::optional _ternaryColour{}; + std::optional _paletteId{}; uint8_t _stroke{}; uint8_t _fill{}; @@ -36,25 +39,80 @@ namespace OpenRCT2::Scripting 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: - colour_t colour_get() const + DukValue colour_get() const { - return _colour; + return ToDuk(_ctx, _colour); } - void colour_set(colour_t value) + void colour_set(DukValue value) { - _colour = 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 @@ -77,14 +135,66 @@ namespace OpenRCT2::Scripting _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, 0); + 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, INSET_RECT_FLAG_BORDER_INSET | INSET_RECT_FLAG_FILL_DONT_LIGHTEN); + 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() @@ -99,6 +209,28 @@ namespace OpenRCT2::Scripting _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); @@ -106,7 +238,28 @@ namespace OpenRCT2::Scripting void rect(int32_t x, int32_t y, int32_t width, int32_t height) { - gfx_fill_rect(&_dpi, { x, y, x + width - 1, y + height - 1 }, _fill); + 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 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/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 From c2183989a5682edcf6b458f403f56b2a5c540654 Mon Sep 17 00:00:00 2001 From: Ted John Date: Thu, 4 Feb 2021 00:25:34 +0000 Subject: [PATCH 05/10] Allow spinners to be clicked and held --- distribution/openrct2.d.ts | 8 +++++++ src/openrct2-ui/interface/Widget.cpp | 17 ++++++++++++++ src/openrct2-ui/scripting/CustomWindow.cpp | 19 ++++++++++++++-- src/openrct2-ui/scripting/ScWidget.hpp | 21 +++++++++++++++++ src/openrct2/interface/Widget.h | 2 ++ src/openrct2/interface/Window.h | 1 + src/openrct2/scripting/ScriptEngine.cpp | 26 +++++++++++++--------- 7 files changed, 82 insertions(+), 12 deletions(-) diff --git a/distribution/openrct2.d.ts b/distribution/openrct2.d.ts index 5a707d838a..421322eb84 100644 --- a/distribution/openrct2.d.ts +++ b/distribution/openrct2.d.ts @@ -2125,8 +2125,16 @@ declare global { interface SpinnerWidget extends WidgetBase { type: 'spinner'; text?: string; + + /** + * If true, the user can click and hold the decrement and increment + * button for repeated calls. + */ + isHoldable?: boolean; + onDecrement?: () => void; onIncrement?: () => void; + onClick?: () => void; } interface TextBoxWidget extends WidgetBase { diff --git a/src/openrct2-ui/interface/Widget.cpp b/src/openrct2-ui/interface/Widget.cpp index 5e04864630..9d30152d64 100644 --- a/src/openrct2-ui/interface/Widget.cpp +++ b/src/openrct2-ui/interface/Widget.cpp @@ -856,6 +856,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 +1055,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/scripting/CustomWindow.cpp b/src/openrct2-ui/scripting/CustomWindow.cpp index c6306abc3c..1c47819df1 100644 --- a/src/openrct2-ui/scripting/CustomWindow.cpp +++ b/src/openrct2-ui/scripting/CustomWindow.cpp @@ -77,6 +77,7 @@ namespace OpenRCT2::Ui::Windows bool IsDisabled{}; bool IsVisible{}; bool IsPressed{}; + bool IsHoldable{}; bool HasBorder{}; bool ShowColumnHeaders{}; bool IsStriped{}; @@ -180,9 +181,11 @@ namespace OpenRCT2::Ui::Windows } else if (result.Type == "spinner") { + result.IsHoldable = AsOrDefault(desc["isHoldable"], false); result.Text = ProcessString(desc["text"]); result.OnIncrement = desc["onIncrement"]; result.OnDecrement = desc["onDecrement"]; + result.OnClick = desc["onClick"]; } else if (result.Type == "textbox") { @@ -564,8 +567,6 @@ namespace OpenRCT2::Ui::Windows auto& scriptEngine = GetContext()->GetScriptEngine(); scriptEngine.ExecutePluginCall(info.Owner, widgetDesc->OnDraw, dukWidget, { dukG }, false); } - // auto widgetDpi = dpi.Crop( - // { windowPos.x + widget.left, windowPos.y + widget.top }, { widget.width(), widget.height() }); } } else @@ -612,6 +613,14 @@ namespace OpenRCT2::Ui::Windows 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; } @@ -960,6 +969,10 @@ namespace OpenRCT2::Ui::Windows { disabled_widgets |= mask; } + if (widgetFlags & WIDGET_FLAGS::IS_HOLDABLE) + { + hold_down_widgets |= mask; + } } widgetList.push_back({ WIDGETS_END }); @@ -1110,6 +1123,8 @@ namespace OpenRCT2::Ui::Windows widget.flags |= WIDGET_FLAGS::IS_ENABLED; if (desc.IsDisabled) widget.flags |= WIDGET_FLAGS::IS_DISABLED; + if (desc.IsHoldable) + widget.flags |= WIDGET_FLAGS::IS_HOLDABLE; widgetList.push_back(widget); // Add the increment button diff --git a/src/openrct2-ui/scripting/ScWidget.hpp b/src/openrct2-ui/scripting/ScWidget.hpp index 6a85ceec98..48ff3ef17f 100644 --- a/src/openrct2-ui/scripting/ScWidget.hpp +++ b/src/openrct2-ui/scripting/ScWidget.hpp @@ -887,8 +887,29 @@ namespace OpenRCT2::Scripting static void Register(duk_context* ctx) { dukglue_set_base_class(ctx); + dukglue_register_property(ctx, &ScSpinnerWidget::isHoldable_get, &ScSpinnerWidget::isHoldable_set, "isHoldable"); dukglue_register_property(ctx, &ScSpinnerWidget::text_get, &ScSpinnerWidget::text_set, "text"); } + + private: + int32_t isHoldable_get() const + { + auto w = GetWindow(); + if (w != nullptr) + { + return WidgetIsHoldable(w, _widgetIndex); + } + return false; + } + + void isHoldable_set(int32_t value) + { + auto w = GetWindow(); + if (w != nullptr) + { + WidgetSetHoldable(w, _widgetIndex, value); + } + } }; class ScTextBoxWidget : public ScWidget diff --git a/src/openrct2/interface/Widget.h b/src/openrct2/interface/Widget.h index c4b7a7b333..3f8735f90b 100644 --- a/src/openrct2/interface/Widget.h +++ b/src/openrct2/interface/Widget.h @@ -138,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); @@ -148,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.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/scripting/ScriptEngine.cpp b/src/openrct2/scripting/ScriptEngine.cpp index 9f639dcbe6..2547160040 100644 --- a/src/openrct2/scripting/ScriptEngine.cpp +++ b/src/openrct2/scripting/ScriptEngine.cpp @@ -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(); } }; From d64bd602788a3bd91494c846c32c2c056163476c Mon Sep 17 00:00:00 2001 From: Ted John Date: Wed, 24 Feb 2021 22:54:53 +0000 Subject: [PATCH 06/10] Fix CI errors --- src/openrct2-ui/interface/Widget.cpp | 7 ++----- src/openrct2-ui/scripting/CustomWindow.cpp | 4 ++-- src/openrct2-ui/scripting/ScWidget.hpp | 2 ++ 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/src/openrct2-ui/interface/Widget.cpp b/src/openrct2-ui/interface/Widget.cpp index 9d30152d64..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; } } diff --git a/src/openrct2-ui/scripting/CustomWindow.cpp b/src/openrct2-ui/scripting/CustomWindow.cpp index 1c47819df1..685994b1f8 100644 --- a/src/openrct2-ui/scripting/CustomWindow.cpp +++ b/src/openrct2-ui/scripting/CustomWindow.cpp @@ -385,7 +385,7 @@ namespace OpenRCT2::Ui::Windows static void InvokeEventHandler( const std::shared_ptr& owner, const DukValue& dukHandler, const std::vector& args); - class CustomWindow : public Window + class CustomWindow final : public Window { private: static rct_windownumber _nextWindowNumber; @@ -694,7 +694,7 @@ namespace OpenRCT2::Ui::Windows } } - void OnTextInput(rct_widgetindex widgetIndex, std::string_view text) + void OnTextInput(rct_widgetindex widgetIndex, std::string_view text) override { auto& info = GetInfo(this); auto widgetDesc = info.GetCustomWidgetDesc(this, widgetIndex); diff --git a/src/openrct2-ui/scripting/ScWidget.hpp b/src/openrct2-ui/scripting/ScWidget.hpp index 48ff3ef17f..5b443c0aa5 100644 --- a/src/openrct2-ui/scripting/ScWidget.hpp +++ b/src/openrct2-ui/scripting/ScWidget.hpp @@ -111,6 +111,8 @@ namespace OpenRCT2::Scripting return "empty"; case WindowWidgetType::Placeholder: return "placeholder"; + case WindowWidgetType::Custom: + return "custom"; case WindowWidgetType::Last: return "last"; } From a98d9aaf4171271dfdfac557018701c724bb4d8a Mon Sep 17 00:00:00 2001 From: Ted John Date: Wed, 24 Feb 2021 22:56:17 +0000 Subject: [PATCH 07/10] Update changelog --- distribution/changelog.txt | 4 ++++ 1 file changed, 4 insertions(+) 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. From c7d9eba9dbe5bb2df8a5a87859e294627df81577 Mon Sep 17 00:00:00 2001 From: Ted John Date: Wed, 24 Feb 2021 22:56:29 +0000 Subject: [PATCH 08/10] Increment plugin API version --- src/openrct2/scripting/ScriptEngine.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/openrct2/scripting/ScriptEngine.cpp b/src/openrct2/scripting/ScriptEngine.cpp index 2547160040..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 { From f7650fdfbc88d674db5d5ffbd41c694ff8a164e2 Mon Sep 17 00:00:00 2001 From: Ted John Date: Thu, 25 Feb 2021 18:47:54 +0000 Subject: [PATCH 09/10] Fix CI failure --- src/openrct2-ui/scripting/ScWidget.hpp | 13 +------------ src/openrct2-ui/scripting/UiExtensions.cpp | 13 +++++++++++++ 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/src/openrct2-ui/scripting/ScWidget.hpp b/src/openrct2-ui/scripting/ScWidget.hpp index 5b443c0aa5..7be75bf5a0 100644 --- a/src/openrct2-ui/scripting/ScWidget.hpp +++ b/src/openrct2-ui/scripting/ScWidget.hpp @@ -379,18 +379,7 @@ namespace OpenRCT2::Scripting } public: - static void 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"); - } + 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 ea3d4a7ce2..6757a7c3a4 100644 --- a/src/openrct2-ui/scripting/UiExtensions.cpp +++ b/src/openrct2-ui/scripting/UiExtensions.cpp @@ -61,4 +61,17 @@ 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 From e000f06442e97a812b964cb26caa2535411c7288 Mon Sep 17 00:00:00 2001 From: Ted John Date: Thu, 25 Feb 2021 22:30:35 +0000 Subject: [PATCH 10/10] Remove holdable flag from spinner widget --- distribution/openrct2.d.ts | 6 ------ src/openrct2-ui/scripting/CustomWindow.cpp | 5 +---- src/openrct2-ui/scripting/ScWidget.hpp | 21 --------------------- 3 files changed, 1 insertion(+), 31 deletions(-) diff --git a/distribution/openrct2.d.ts b/distribution/openrct2.d.ts index 421322eb84..d190ba67bd 100644 --- a/distribution/openrct2.d.ts +++ b/distribution/openrct2.d.ts @@ -2126,12 +2126,6 @@ declare global { type: 'spinner'; text?: string; - /** - * If true, the user can click and hold the decrement and increment - * button for repeated calls. - */ - isHoldable?: boolean; - onDecrement?: () => void; onIncrement?: () => void; onClick?: () => void; diff --git a/src/openrct2-ui/scripting/CustomWindow.cpp b/src/openrct2-ui/scripting/CustomWindow.cpp index 685994b1f8..673c90a224 100644 --- a/src/openrct2-ui/scripting/CustomWindow.cpp +++ b/src/openrct2-ui/scripting/CustomWindow.cpp @@ -77,7 +77,6 @@ namespace OpenRCT2::Ui::Windows bool IsDisabled{}; bool IsVisible{}; bool IsPressed{}; - bool IsHoldable{}; bool HasBorder{}; bool ShowColumnHeaders{}; bool IsStriped{}; @@ -181,7 +180,6 @@ namespace OpenRCT2::Ui::Windows } else if (result.Type == "spinner") { - result.IsHoldable = AsOrDefault(desc["isHoldable"], false); result.Text = ProcessString(desc["text"]); result.OnIncrement = desc["onIncrement"]; result.OnDecrement = desc["onDecrement"]; @@ -1123,8 +1121,7 @@ namespace OpenRCT2::Ui::Windows widget.flags |= WIDGET_FLAGS::IS_ENABLED; if (desc.IsDisabled) widget.flags |= WIDGET_FLAGS::IS_DISABLED; - if (desc.IsHoldable) - widget.flags |= WIDGET_FLAGS::IS_HOLDABLE; + widget.flags |= WIDGET_FLAGS::IS_HOLDABLE; widgetList.push_back(widget); // Add the increment button diff --git a/src/openrct2-ui/scripting/ScWidget.hpp b/src/openrct2-ui/scripting/ScWidget.hpp index 7be75bf5a0..cc719a8312 100644 --- a/src/openrct2-ui/scripting/ScWidget.hpp +++ b/src/openrct2-ui/scripting/ScWidget.hpp @@ -878,29 +878,8 @@ namespace OpenRCT2::Scripting static void Register(duk_context* ctx) { dukglue_set_base_class(ctx); - dukglue_register_property(ctx, &ScSpinnerWidget::isHoldable_get, &ScSpinnerWidget::isHoldable_set, "isHoldable"); dukglue_register_property(ctx, &ScSpinnerWidget::text_get, &ScSpinnerWidget::text_set, "text"); } - - private: - int32_t isHoldable_get() const - { - auto w = GetWindow(); - if (w != nullptr) - { - return WidgetIsHoldable(w, _widgetIndex); - } - return false; - } - - void isHoldable_set(int32_t value) - { - auto w = GetWindow(); - if (w != nullptr) - { - WidgetSetHoldable(w, _widgetIndex, value); - } - } }; class ScTextBoxWidget : public ScWidget