mirror of
https://github.com/OpenRCT2/OpenRCT2
synced 2025-12-24 00:03:11 +01:00
Add plugin API for shortcuts
This commit is contained in:
27
distribution/openrct2.d.ts
vendored
27
distribution/openrct2.d.ts
vendored
@@ -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.
|
||||
*/
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -46,11 +46,19 @@ namespace OpenRCT2::Ui
|
||||
public:
|
||||
std::string Id;
|
||||
rct_string_id LocalisedName = STR_NONE;
|
||||
std::string CustomName;
|
||||
std::vector<ShortcutInput> Default;
|
||||
std::vector<ShortcutInput> Current;
|
||||
std::function<void()> Action;
|
||||
|
||||
RegisteredShortcut() = default;
|
||||
RegisteredShortcut(const std::string_view& id, std::string_view name, const std::function<void()>& action)
|
||||
: Id(id)
|
||||
, CustomName(name)
|
||||
, Action(action)
|
||||
{
|
||||
}
|
||||
|
||||
RegisteredShortcut(const std::string_view& id, rct_string_id localisedName, const std::function<void()>& 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);
|
||||
|
||||
@@ -11,14 +11,50 @@
|
||||
|
||||
# include "CustomMenu.h"
|
||||
|
||||
# include <openrct2-ui/input/ShortcutManager.h>
|
||||
# include <openrct2/Input.h>
|
||||
# include <openrct2/world/Map.h>
|
||||
# include <openrct2/world/Sprite.h>
|
||||
|
||||
using namespace OpenRCT2;
|
||||
using namespace OpenRCT2::Ui;
|
||||
|
||||
namespace OpenRCT2::Scripting
|
||||
{
|
||||
std::optional<CustomTool> ActiveCustomTool;
|
||||
std::vector<CustomToolbarMenuItem> CustomMenuItems;
|
||||
std::vector<CustomShortcut> CustomShortcuts;
|
||||
|
||||
CustomShortcut::CustomShortcut(
|
||||
std::shared_ptr<Plugin> owner, std::string_view id, std::string_view text, const std::vector<std::string>& 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<std::string_view, EnumValue(CursorID::Count)> 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)
|
||||
|
||||
@@ -44,6 +44,28 @@ namespace OpenRCT2::Scripting
|
||||
}
|
||||
};
|
||||
|
||||
class CustomShortcut
|
||||
{
|
||||
public:
|
||||
std::shared_ptr<Plugin> Owner;
|
||||
std::string Id;
|
||||
std::string Text;
|
||||
std::vector<std::string> Bindings;
|
||||
DukValue Callback;
|
||||
|
||||
CustomShortcut(
|
||||
std::shared_ptr<Plugin> owner, std::string_view id, std::string_view text, const std::vector<std::string>& 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<Plugin> Owner;
|
||||
@@ -72,6 +94,7 @@ namespace OpenRCT2::Scripting
|
||||
|
||||
extern std::optional<CustomTool> ActiveCustomTool;
|
||||
extern std::vector<CustomToolbarMenuItem> CustomMenuItems;
|
||||
extern std::vector<CustomShortcut> CustomShortcuts;
|
||||
|
||||
void InitialiseCustomMenuItems(ScriptEngine& scriptEngine);
|
||||
void InitialiseCustomTool(ScriptEngine& scriptEngine, const DukValue& dukValue);
|
||||
|
||||
@@ -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<std::string> 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:
|
||||
|
||||
@@ -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<rct_string_id>(_shortcutLocalisedName);
|
||||
if (_shortcutCustomName.empty())
|
||||
{
|
||||
ft.Add<rct_string_id>(_shortcutLocalisedName);
|
||||
}
|
||||
else
|
||||
{
|
||||
ft.Add<rct_string_id>(STR_STRING);
|
||||
ft.Add<const char*>(_shortcutCustomName.c_str());
|
||||
}
|
||||
gfx_draw_string_centred_wrapped(dpi, ft.Data(), stringCoords, 242, STR_SHORTCUT_CHANGE_PROMPT, COLOUR_BLACK);
|
||||
}
|
||||
|
||||
@@ -71,6 +71,7 @@ struct ShortcutStringPair
|
||||
size_t ShortcutIndex;
|
||||
std::string ShortcutId;
|
||||
rct_string_id StringId = STR_NONE;
|
||||
std::string CustomString;
|
||||
};
|
||||
|
||||
static std::vector<ShortcutStringPair> _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<rct_string_id>(STR_SHORTCUT_ENTRY_FORMAT);
|
||||
ft.Add<rct_string_id>(_shortcutList[i].StringId);
|
||||
if (shortcut.CustomString.empty())
|
||||
{
|
||||
ft.Add<rct_string_id>(shortcut.StringId);
|
||||
}
|
||||
else
|
||||
{
|
||||
ft.Add<rct_string_id>(STR_STRING);
|
||||
ft.Add<const char*>(shortcut.CustomString.c_str());
|
||||
}
|
||||
DrawTextEllipsised(dpi, { 0, y - 1 }, bindingOffset, format, ft, COLOUR_BLACK);
|
||||
|
||||
char keybinding[128];
|
||||
|
||||
Reference in New Issue
Block a user