1
0
mirror of https://github.com/OpenRCT2/OpenRCT2 synced 2025-12-22 23:33:04 +01:00

Implement custom menu items

This commit is contained in:
Ted John
2020-02-11 21:26:05 +00:00
parent a915cb0998
commit 4b95a3a9d0
8 changed files with 199 additions and 40 deletions

View File

@@ -439,6 +439,7 @@ export interface Ui {
closeAllWindows(): void;
activateTool(options: ToolDesc): IDisposable;
registerMenuItem(text: string, callback: () => void): void;
}
/**

View File

@@ -0,0 +1,36 @@
/*****************************************************************************
* Copyright (c) 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.
*****************************************************************************/
#include "CustomMenu.h"
namespace OpenRCT2::Scripting
{
std::vector<CustomToolbarMenuItem> CustomMenuItems;
static void RemoveMenuItems(std::shared_ptr<Plugin> owner)
{
auto& menuItems = CustomMenuItems;
for (auto it = menuItems.begin(); it != menuItems.end();)
{
if (it->Owner == owner)
{
it = menuItems.erase(it);
}
else
{
it++;
}
}
}
void InitialiseCustomMenuItems(ScriptEngine& scriptEngine)
{
scriptEngine.SubscribeToPluginStoppedEvent([](std::shared_ptr<Plugin> plugin) -> void { RemoveMenuItems(plugin); });
}
} // namespace OpenRCT2::Scripting

View File

@@ -0,0 +1,52 @@
/*****************************************************************************
* Copyright (c) 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
#include <memory>
#include <openrct2/Context.h>
#include <openrct2/scripting/Duktape.hpp>
#include <openrct2/scripting/ScriptEngine.h>
#include <string>
#include <vector>
namespace OpenRCT2::Scripting
{
class CustomToolbarMenuItem
{
public:
std::shared_ptr<Plugin> Owner;
std::string Text;
DukValue Callback;
CustomToolbarMenuItem(std::shared_ptr<Plugin> owner, std::string text, DukValue callback)
: Owner(owner)
, Text(text)
, Callback(callback)
{
}
void Invoke() const
{
auto& scriptEngine = GetContext()->GetScriptEngine();
auto& execInfo = scriptEngine.GetExecInfo();
auto ctx = scriptEngine.GetContext();
ScriptExecutionInfo::PluginScope scope(execInfo, Owner);
Callback.push();
duk_pcall(ctx, 0);
duk_pop(ctx);
}
};
extern std::vector<CustomToolbarMenuItem> CustomMenuItems;
void InitialiseCustomMenuItems(ScriptEngine& scriptEngine);
} // namespace OpenRCT2::Scripting

View File

@@ -9,6 +9,7 @@
#pragma once
#include "CustomMenu.h"
#include "ScWindow.hpp"
#include <memory>
@@ -105,6 +106,13 @@ namespace OpenRCT2::Scripting
return nullptr;
}
void registerMenuItem(std::string text, DukValue callback)
{
auto& execInfo = _scriptEngine.GetExecInfo();
auto owner = execInfo.GetCurrentPlugin();
CustomMenuItems.emplace_back(owner, text, callback);
}
static void Register(duk_context* ctx)
{
dukglue_register_property(ctx, &ScUi::height_get, nullptr, "height");
@@ -114,6 +122,7 @@ namespace OpenRCT2::Scripting
dukglue_register_method(ctx, &ScUi::closeWindows, "closeWindows");
dukglue_register_method(ctx, &ScUi::closeAllWindows, "closeAllWindows");
dukglue_register_method(ctx, &ScUi::getWindow, "getWindow");
dukglue_register_method(ctx, &ScUi::registerMenuItem, "registerMenuItem");
}
private:

View File

@@ -9,6 +9,7 @@
#include "UiExtensions.h"
#include "CustomMenu.h"
#include "ScUi.hpp"
#include "ScWindow.hpp"
@@ -24,4 +25,6 @@ void UiScriptExtensions::Extend(ScriptEngine& scriptEngine)
ScUi::Register(ctx);
ScWindow::Register(ctx);
InitialiseCustomMenuItems(scriptEngine);
}

View File

@@ -9,6 +9,7 @@
#include "../UiContext.h"
#include "../interface/InGameConsole.h"
#include "../scripting/CustomMenu.h"
#include <algorithm>
#include <iterator>
@@ -305,6 +306,8 @@ static rct_window_event_list window_top_toolbar_events = {
static void top_toolbar_init_view_menu(rct_window* window, rct_widget* widget);
static void top_toolbar_view_menu_dropdown(int16_t dropdownIndex);
static void top_toolbar_init_map_menu(rct_window* window, rct_widget* widget);
static void top_toolbar_map_menu_dropdown(int16_t dropdownIndex);
static void top_toolbar_init_fastforward_menu(rct_window* window, rct_widget* widget);
static void top_toolbar_fastforward_menu_dropdown(int16_t dropdownIndex);
static void top_toolbar_init_rotate_menu(rct_window* window, rct_widget* widget);
@@ -521,20 +524,7 @@ static void window_top_toolbar_mousedown(rct_window* w, rct_widgetindex widgetIn
top_toolbar_init_view_menu(w, widget);
break;
case WIDX_MAP:
gDropdownItemsFormat[0] = STR_SHORTCUT_SHOW_MAP;
gDropdownItemsFormat[1] = STR_EXTRA_VIEWPORT;
numItems = 2;
if ((gScreenFlags & SCREEN_FLAGS_SCENARIO_EDITOR) && gS6Info.editor_step == EDITOR_STEP_LANDSCAPE_EDITOR)
{
gDropdownItemsFormat[2] = STR_MAPGEN_WINDOW_TITLE;
numItems++;
}
window_dropdown_show_text(
w->windowPos.x + widget->left, w->windowPos.y + widget->top, widget->bottom - widget->top + 1,
w->colours[1] | 0x80, 0, numItems);
gDropdownDefaultIndex = DDIDX_SHOW_MAP;
top_toolbar_init_map_menu(w, widget);
break;
case WIDX_FASTFORWARD:
top_toolbar_init_fastforward_menu(w, widget);
@@ -651,18 +641,7 @@ static void window_top_toolbar_dropdown(rct_window* w, rct_widgetindex widgetInd
top_toolbar_view_menu_dropdown(dropdownIndex);
break;
case WIDX_MAP:
switch (dropdownIndex)
{
case 0:
context_open_window(WC_MAP);
break;
case 1:
context_open_window(WC_VIEWPORT);
break;
case 2:
context_open_window(WC_MAPGEN);
break;
}
top_toolbar_map_menu_dropdown(dropdownIndex);
break;
case WIDX_FASTFORWARD:
top_toolbar_fastforward_menu_dropdown(dropdownIndex);
@@ -3293,6 +3272,68 @@ static void window_top_toolbar_tool_abort(rct_window* w, rct_widgetindex widgetI
}
}
static void top_toolbar_init_map_menu(rct_window* w, rct_widget* widget)
{
auto i = 0;
gDropdownItemsFormat[i++] = STR_SHORTCUT_SHOW_MAP;
gDropdownItemsFormat[i++] = STR_EXTRA_VIEWPORT;
if ((gScreenFlags & SCREEN_FLAGS_SCENARIO_EDITOR) && gS6Info.editor_step == EDITOR_STEP_LANDSCAPE_EDITOR)
{
gDropdownItemsFormat[i++] = STR_MAPGEN_WINDOW_TITLE;
}
const auto& customMenuItems = OpenRCT2::Scripting::CustomMenuItems;
if (!customMenuItems.empty())
{
gDropdownItemsFormat[i++] = STR_EMPTY;
for (const auto& item : customMenuItems)
{
gDropdownItemsFormat[i] = STR_STRING;
set_format_arg_on((uint8_t*)&gDropdownItemsArgs[i], 0, const char*, item.Text.c_str());
i++;
}
}
window_dropdown_show_text(
w->windowPos.x + widget->left, w->windowPos.y + widget->top, widget->bottom - widget->top + 1, w->colours[1] | 0x80, 0,
i);
gDropdownDefaultIndex = DDIDX_SHOW_MAP;
}
static void top_toolbar_map_menu_dropdown(int16_t dropdownIndex)
{
int32_t customStartIndex = 3;
if ((gScreenFlags & SCREEN_FLAGS_SCENARIO_EDITOR) && gS6Info.editor_step == EDITOR_STEP_LANDSCAPE_EDITOR)
{
customStartIndex++;
}
if (dropdownIndex < customStartIndex)
{
switch (dropdownIndex)
{
case 0:
context_open_window(WC_MAP);
break;
case 1:
context_open_window(WC_VIEWPORT);
break;
case 2:
context_open_window(WC_MAPGEN);
break;
}
}
else
{
const auto& customMenuItems = OpenRCT2::Scripting::CustomMenuItems;
auto customIndex = dropdownIndex - customStartIndex;
if (customMenuItems.size() > customIndex)
{
customMenuItems[customIndex].Invoke();
}
}
}
static void top_toolbar_init_fastforward_menu(rct_window* w, rct_widget* widget)
{
int32_t num_items = 4;

View File

@@ -136,6 +136,28 @@ void ScriptEngine::LoadPlugin(const std::string& path)
}
}
void ScriptEngine::StopPlugin(std::shared_ptr<Plugin> plugin)
{
if (plugin->HasStarted())
{
_hookEngine.UnsubscribeAll(plugin);
for (auto callback : _pluginStoppedSubscriptions)
{
callback(plugin);
}
ScriptExecutionInfo::PluginScope scope(_execInfo, plugin);
try
{
plugin->Stop();
}
catch (const std::exception& e)
{
_console.WriteLineError(e.what());
}
}
}
bool ScriptEngine::ShouldLoadScript(const std::string& path)
{
// A lot of JavaScript is often found in a node_modules directory tree and is most likely unwanted, so ignore it
@@ -174,7 +196,7 @@ void ScriptEngine::AutoReloadPlugins()
auto& plugin = *findResult;
try
{
_hookEngine.UnsubscribeAll(plugin);
StopPlugin(plugin);
ScriptExecutionInfo::PluginScope scope(_execInfo, plugin);
plugin->Load();
@@ -225,21 +247,9 @@ void ScriptEngine::StartPlugins()
void ScriptEngine::StopPlugins()
{
_hookEngine.UnsubscribeAll();
for (auto& plugin : _plugins)
{
if (plugin->HasStarted())
{
ScriptExecutionInfo::PluginScope scope(_execInfo, plugin);
try
{
plugin->Stop();
}
catch (const std::exception& e)
{
_console.WriteLineError(e.what());
}
}
StopPlugin(plugin);
}
_pluginsStarted = false;
}

View File

@@ -105,6 +105,7 @@ namespace OpenRCT2::Scripting
std::unique_ptr<FileWatcher> _pluginFileWatcher;
std::unordered_set<std::string> _changedPluginFiles;
std::mutex _changedPluginFilesMutex;
std::vector<std::function<void(std::shared_ptr<Plugin>)>> _pluginStoppedSubscriptions;
public:
ScriptEngine(InteractiveConsole& console, IPlatformEnvironment& env);
@@ -130,11 +131,17 @@ namespace OpenRCT2::Scripting
void LogPluginInfo(const std::shared_ptr<Plugin>& plugin, const std::string_view& message);
void SubscribeToPluginStoppedEvent(std::function<void(std::shared_ptr<Plugin>)> callback)
{
_pluginStoppedSubscriptions.push_back(callback);
}
private:
void Initialise();
void StartPlugins();
void StopPlugins();
void LoadPlugin(const std::string& path);
void StopPlugin(std::shared_ptr<Plugin> plugin);
bool ShouldLoadScript(const std::string& path);
void SetupHotReloading();
void AutoReloadPlugins();