diff --git a/distribution/openrct2.d.ts b/distribution/openrct2.d.ts index 58e6d137f8..326f16dfc6 100644 --- a/distribution/openrct2.d.ts +++ b/distribution/openrct2.d.ts @@ -1792,6 +1792,8 @@ declare global { activateTool(tool: ToolDesc): void; registerMenuItem(text: string, callback: () => void): void; + + registerShortcut(desc: ShortcutDesc): void; } /** @@ -1960,6 +1962,31 @@ declare global { "label" | "banner"; + interface ShortcutDesc { + /** + * The unique identifier for the shortcut. + * If the identifier already exists, the shortcut will not be registered. + * Use full stops to group shortcuts together, e.g. `yourplugin.somewindow.apply`. + */ + id: string; + + /** + * The display text for the shortcut. + */ + text: string; + + /** + * Default bindings for the shortcut. + * E.g. `["CTRL+SHIFT+L", "MOUSE 3"]` + */ + bindings?: string[]; + + /** + * Function to call when the shortcut is invoked. + */ + callback: () => void; + } + /** * Represents the type of a widget, e.g. button or label. */ diff --git a/src/openrct2-ui/input/ShortcutManager.cpp b/src/openrct2-ui/input/ShortcutManager.cpp index 2e4f31423a..d13a5a1215 100644 --- a/src/openrct2-ui/input/ShortcutManager.cpp +++ b/src/openrct2-ui/input/ShortcutManager.cpp @@ -424,7 +424,10 @@ ShortcutManager::ShortcutManager() void ShortcutManager::RegisterShortcut(RegisteredShortcut&& shortcut) { - Shortcuts.push_back(shortcut); + if (!shortcut.Id.empty() && GetShortcut(shortcut.Id) == nullptr) + { + Shortcuts.push_back(shortcut); + } } RegisteredShortcut* ShortcutManager::GetShortcut(std::string_view id) @@ -433,6 +436,12 @@ RegisteredShortcut* ShortcutManager::GetShortcut(std::string_view id) return result == Shortcuts.end() ? nullptr : &(*result); } +void ShortcutManager::RemoveShortcut(std::string_view id) +{ + Shortcuts.erase(std::remove_if( + Shortcuts.begin(), Shortcuts.end(), [id](const RegisteredShortcut& shortcut) { return shortcut.Id == id; })); +} + void ShortcutManager::SetPendingShortcutChange(std::string_view id) { _pendingShortcutChange = id; diff --git a/src/openrct2-ui/input/ShortcutManager.h b/src/openrct2-ui/input/ShortcutManager.h index 5ecfed0d67..5efd42fe92 100644 --- a/src/openrct2-ui/input/ShortcutManager.h +++ b/src/openrct2-ui/input/ShortcutManager.h @@ -46,11 +46,19 @@ namespace OpenRCT2::Ui public: std::string Id; rct_string_id LocalisedName = STR_NONE; + std::string CustomName; std::vector Default; std::vector Current; std::function Action; RegisteredShortcut() = default; + RegisteredShortcut(const std::string_view& id, std::string_view name, const std::function& action) + : Id(id) + , CustomName(name) + , Action(action) + { + } + RegisteredShortcut(const std::string_view& id, rct_string_id localisedName, const std::function& action) : Id(id) , LocalisedName(localisedName) @@ -105,6 +113,7 @@ namespace OpenRCT2::Ui } void RegisterDefaultShortcuts(); RegisteredShortcut* GetShortcut(std::string_view id); + void RemoveShortcut(std::string_view id); void SetPendingShortcutChange(std::string_view id); void ProcessEvent(const InputEvent& e); bool ProcessEventForSpecificShortcut(const InputEvent& e, std::string_view id); diff --git a/src/openrct2-ui/scripting/CustomMenu.cpp b/src/openrct2-ui/scripting/CustomMenu.cpp index b56f13150e..ca8a4bf1c9 100644 --- a/src/openrct2-ui/scripting/CustomMenu.cpp +++ b/src/openrct2-ui/scripting/CustomMenu.cpp @@ -11,14 +11,50 @@ # include "CustomMenu.h" +# include # include # include # include +using namespace OpenRCT2; +using namespace OpenRCT2::Ui; + namespace OpenRCT2::Scripting { std::optional ActiveCustomTool; std::vector CustomMenuItems; + std::vector CustomShortcuts; + + CustomShortcut::CustomShortcut( + std::shared_ptr owner, std::string_view id, std::string_view text, const std::vector& bindings, + DukValue callback) + : Owner(owner) + , Id(id) + , Text(text) + , Bindings(bindings) + , Callback(callback) + { + auto& shortcutManager = GetShortcutManager(); + + RegisteredShortcut registeredShortcut(Id, Text, [this]() { Invoke(); }); + for (const auto& binding : bindings) + { + registeredShortcut.Default.emplace_back(binding); + } + shortcutManager.RegisterShortcut(std::move(registeredShortcut)); + } + + CustomShortcut::~CustomShortcut() + { + auto& shortcutManager = GetShortcutManager(); + shortcutManager.RemoveShortcut(Id); + } + + void CustomShortcut::Invoke() const + { + auto& scriptEngine = GetContext()->GetScriptEngine(); + scriptEngine.ExecutePluginCall(Owner, Callback, {}, false); + } static constexpr std::array CursorNames = { "arrow", "blank", "up_arrow", "up_down_arrow", "hand_point", "zzz", "diagonal_arrows", @@ -89,6 +125,19 @@ namespace OpenRCT2::Scripting it++; } } + + auto& shortcuts = CustomShortcuts; + for (auto it = shortcuts.begin(); it != shortcuts.end();) + { + if (it->Owner == owner) + { + it = shortcuts.erase(it); + } + else + { + it++; + } + } } void InitialiseCustomMenuItems(ScriptEngine& scriptEngine) diff --git a/src/openrct2-ui/scripting/CustomMenu.h b/src/openrct2-ui/scripting/CustomMenu.h index cae8f07cea..270e279c73 100644 --- a/src/openrct2-ui/scripting/CustomMenu.h +++ b/src/openrct2-ui/scripting/CustomMenu.h @@ -44,6 +44,28 @@ namespace OpenRCT2::Scripting } }; + class CustomShortcut + { + public: + std::shared_ptr Owner; + std::string Id; + std::string Text; + std::vector Bindings; + DukValue Callback; + + CustomShortcut( + std::shared_ptr owner, std::string_view id, std::string_view text, const std::vector& bindings, + DukValue callback); + CustomShortcut(CustomShortcut&&) = default; + CustomShortcut(const CustomShortcut&) = delete; + ~CustomShortcut(); + + CustomShortcut& operator=(const CustomShortcut&) = delete; + CustomShortcut& operator=(CustomShortcut&& other) = default; + + void Invoke() const; + }; + struct CustomTool { std::shared_ptr Owner; @@ -72,6 +94,7 @@ namespace OpenRCT2::Scripting extern std::optional ActiveCustomTool; extern std::vector CustomMenuItems; + extern std::vector CustomShortcuts; void InitialiseCustomMenuItems(ScriptEngine& scriptEngine); void InitialiseCustomTool(ScriptEngine& scriptEngine, const DukValue& dukValue); diff --git a/src/openrct2-ui/scripting/ScUi.hpp b/src/openrct2-ui/scripting/ScUi.hpp index 75d4225f04..188f6a098b 100644 --- a/src/openrct2-ui/scripting/ScUi.hpp +++ b/src/openrct2-ui/scripting/ScUi.hpp @@ -307,6 +307,34 @@ namespace OpenRCT2::Scripting CustomMenuItems.emplace_back(owner, text, callback); } + void registerShortcut(DukValue desc) + { + try + { + auto& execInfo = _scriptEngine.GetExecInfo(); + auto owner = execInfo.GetCurrentPlugin(); + auto id = desc["id"].as_string(); + auto text = desc["text"].as_string(); + + std::vector bindings; + auto dukBindings = desc["bindings"]; + if (dukBindings.is_array()) + { + for (auto binding : dukBindings.as_array()) + { + bindings.push_back(binding.as_string()); + } + } + + auto callback = desc["callback"]; + CustomShortcuts.emplace_back(owner, id, text, bindings, callback); + } + catch (const DukException&) + { + duk_error(_scriptEngine.GetContext(), DUK_ERR_ERROR, "Invalid parameters."); + } + } + public: static void Register(duk_context* ctx) { @@ -326,6 +354,7 @@ namespace OpenRCT2::Scripting dukglue_register_method(ctx, &ScUi::showScenarioSelect, "showScenarioSelect"); dukglue_register_method(ctx, &ScUi::activateTool, "activateTool"); dukglue_register_method(ctx, &ScUi::registerMenuItem, "registerMenuItem"); + dukglue_register_method(ctx, &ScUi::registerShortcut, "registerShortcut"); } private: diff --git a/src/openrct2-ui/windows/ShortcutKeyChange.cpp b/src/openrct2-ui/windows/ShortcutKeyChange.cpp index a76b5b5861..837f9c7ced 100644 --- a/src/openrct2-ui/windows/ShortcutKeyChange.cpp +++ b/src/openrct2-ui/windows/ShortcutKeyChange.cpp @@ -49,6 +49,7 @@ static rct_window_event_list window_shortcut_change_events([](auto& events) // clang-format on static rct_string_id _shortcutLocalisedName{}; +static std::string _shortcutCustomName{}; rct_window* window_shortcut_change_open(const std::string_view& shortcutId) { @@ -58,6 +59,7 @@ rct_window* window_shortcut_change_open(const std::string_view& shortcutId) if (registeredShortcut != nullptr) { _shortcutLocalisedName = registeredShortcut->LocalisedName; + _shortcutCustomName = registeredShortcut->CustomName; window_close_by_class(WC_CHANGE_KEYBOARD_SHORTCUT); auto w = WindowCreateCentred(WW, WH, &window_shortcut_change_events, WC_CHANGE_KEYBOARD_SHORTCUT, 0); @@ -105,6 +107,14 @@ static void window_shortcut_change_paint(rct_window* w, rct_drawpixelinfo* dpi) ScreenCoordsXY stringCoords(w->windowPos.x + 125, w->windowPos.y + 30); auto ft = Formatter(); - ft.Add(_shortcutLocalisedName); + if (_shortcutCustomName.empty()) + { + ft.Add(_shortcutLocalisedName); + } + else + { + ft.Add(STR_STRING); + ft.Add(_shortcutCustomName.c_str()); + } gfx_draw_string_centred_wrapped(dpi, ft.Data(), stringCoords, 242, STR_SHORTCUT_CHANGE_PROMPT, COLOUR_BLACK); } diff --git a/src/openrct2-ui/windows/ShortcutKeys.cpp b/src/openrct2-ui/windows/ShortcutKeys.cpp index 4bbc9e8864..a97a353e39 100644 --- a/src/openrct2-ui/windows/ShortcutKeys.cpp +++ b/src/openrct2-ui/windows/ShortcutKeys.cpp @@ -71,6 +71,7 @@ struct ShortcutStringPair size_t ShortcutIndex; std::string ShortcutId; rct_string_id StringId = STR_NONE; + std::string CustomString; }; static std::vector _shortcutList; @@ -103,6 +104,7 @@ static void InitialiseShortcutList() ssp.ShortcutIndex = index; ssp.ShortcutId = shortcut.Id; ssp.StringId = shortcut.LocalisedName; + ssp.CustomString = shortcut.CustomName; _shortcutList.push_back(std::move(ssp)); index++; } @@ -288,10 +290,20 @@ static void window_shortcut_scrollpaint(rct_window* w, rct_drawpixelinfo* dpi, i gfx_filter_rect(dpi, 0, y - 1, scrollWidth, y + (SCROLLABLE_ROW_HEIGHT - 2), FilterPaletteID::PaletteDarken1); } + const auto& shortcut = _shortcutList[i]; + const int32_t bindingOffset = scrollWidth - 150; auto ft = Formatter(); ft.Add(STR_SHORTCUT_ENTRY_FORMAT); - ft.Add(_shortcutList[i].StringId); + if (shortcut.CustomString.empty()) + { + ft.Add(shortcut.StringId); + } + else + { + ft.Add(STR_STRING); + ft.Add(shortcut.CustomString.c_str()); + } DrawTextEllipsised(dpi, { 0, y - 1 }, bindingOffset, format, ft, COLOUR_BLACK); char keybinding[128];