Skip to content

Commit

Permalink
Add player customizable button hotkeys
Browse files Browse the repository at this point in the history
Fix potential button generation fail for invalid hotkeys
Add Keychain action grouping organization
Change default action map_zoom_in from Q to Shift+Q
Change default action map_zoom_out from E to Shift+E

Add `Keychain.reload_keychain` signal
Add `keep_binding_check` Callable to Keychain.gd
	Prevents removal of unfounded action bindings

Update Orama-Interactive/Keychain@0ddeb6f
  • Loading branch information
Spartan322 committed Dec 16, 2024
1 parent 8e911c8 commit 7aafc8c
Show file tree
Hide file tree
Showing 21 changed files with 200 additions and 49 deletions.
102 changes: 77 additions & 25 deletions extension/src/openvic-extension/utility/UITools.cpp
Original file line number Diff line number Diff line change
@@ -1,14 +1,23 @@
#include "UITools.hpp"

#include <cctype>

#include <godot_cpp/classes/base_button.hpp>
#include <godot_cpp/classes/color_rect.hpp>
#include <godot_cpp/classes/global_constants.hpp>
#include <godot_cpp/classes/input_event_action.hpp>
#include <godot_cpp/classes/input_event_key.hpp>
#include <godot_cpp/classes/input_map.hpp>
#include <godot_cpp/classes/line_edit.hpp>
#include <godot_cpp/classes/os.hpp>
#include <godot_cpp/classes/panel.hpp>
#include <godot_cpp/classes/shortcut.hpp>
#include <godot_cpp/classes/style_box_empty.hpp>
#include <godot_cpp/classes/style_box_texture.hpp>
#include <godot_cpp/classes/theme.hpp>
#include <godot_cpp/core/error_macros.hpp>
#include <godot_cpp/variant/utility_functions.hpp>
#include <godot_cpp/variant/variant.hpp>

#include "openvic-extension/classes/GUIButton.hpp"
#include "openvic-extension/classes/GUIIcon.hpp"
Expand All @@ -24,10 +33,6 @@
#include "openvic-extension/singletons/AssetManager.hpp"
#include "openvic-extension/singletons/GameSingleton.hpp"
#include "openvic-extension/utility/Utilities.hpp"
#include "godot_cpp/classes/global_constants.hpp"
#include "godot_cpp/classes/input_event_key.hpp"
#include "godot_cpp/classes/shortcut.hpp"
#include "godot_cpp/core/error_macros.hpp"

using namespace godot;
using namespace OpenVic;
Expand Down Expand Up @@ -68,7 +73,7 @@ GUI::Position const* UITools::get_gui_position(String const& gui_scene, String c

static Array get_events_from_shortcut_key(String const& key) {
Array events;
if (key.length() == 0) {
if (key.is_empty()) {
return events;
}

Expand Down Expand Up @@ -123,6 +128,69 @@ static Array get_events_from_shortcut_key(String const& key) {
return events;
}

static Error try_create_shortcut_action_for_button(
GUIButton* gui_button, String const& shortcut_key_name, String const& shortcut_hotkey_name = ""
) {
Array event_array = get_events_from_shortcut_key(shortcut_key_name);

ERR_FAIL_COND_V_MSG(
shortcut_key_name.length() != 0 && event_array.size() == 0, ERR_INVALID_PARAMETER,
vformat("Unknown shortcut key '%s' for GUI button %s", shortcut_key_name, gui_button->get_name())
);

if (shortcut_key_name.is_empty()) {
return OK;
}

InputMap* const im = InputMap::get_singleton();
String action_name;
if (shortcut_hotkey_name.is_empty()) {
action_name = //
vformat("button_%s_hotkey", gui_button->get_name().to_lower().replace("button", "").replace("hotkey", ""))
.replace("__", "_");
} else {
action_name = vformat("button_%s_hotkey", shortcut_hotkey_name);
}
Ref<InputEventAction> action_event;
action_event.instantiate();
action_event->set_action(action_name);
action_event->set_pressed(true);

if (im->has_action(action_name)) {
TypedArray<InputEvent> events = im->action_get_events(action_name);
bool skip_warning = events.size() != event_array.size();
if (!skip_warning) {
for (std::size_t index = 0; index < events.size(); index++) {
if (!event_array.has(events[index])) {
skip_warning = false;
break;
}
}
}

if (!skip_warning) {
WARN_PRINT(vformat("'%s' already found in InputMap with different values, reusing hotkey", action_name));
}
} else {
im->add_action(action_name);
for (std::size_t index = 0; index < event_array.size(); index++) {
Ref<InputEvent> event = event_array[index];
ERR_CONTINUE(event.is_null());
im->action_add_event(action_name, event);
}
}

Array shortcut_array;
shortcut_array.push_back(action_event);

Ref<Shortcut> shortcut;
shortcut.instantiate();
shortcut->set_events(shortcut_array);
gui_button->set_shortcut(shortcut);

return OK;
}

/* GUI::Element tree -> godot::Control tree conversion code below: */

namespace OpenVic {
Expand Down Expand Up @@ -283,12 +351,7 @@ static bool generate_button(generate_gui_args_t&& args) {
// TODO - clicksound, rotation (?)
const String button_name = Utilities::std_to_godot_string(button.get_name());
const String shortcut_key_name = Utilities::std_to_godot_string(button.get_shortcut());
Array event_array = get_events_from_shortcut_key(shortcut_key_name);

ERR_FAIL_COND_V_MSG(
shortcut_key_name.length() != 0 && event_array.size() == 0, false,
vformat("Unknown shortcut key '%s' for GUI button %s", shortcut_key_name, button_name)
);
ERR_FAIL_NULL_V_MSG(button.get_sprite(), false, vformat("Null sprite for GUI button %s", button_name));

GUIButton* gui_button = nullptr;
Expand Down Expand Up @@ -335,11 +398,8 @@ static bool generate_button(generate_gui_args_t&& args) {
ret &= gui_button->set_gfx_font(button.get_font()) == OK;
}

if (shortcut_key_name.length() != 0) {
Ref<Shortcut> shortcut;
shortcut.instantiate();
shortcut->set_events(event_array);
gui_button->set_shortcut(shortcut);
if (try_create_shortcut_action_for_button(gui_button, shortcut_key_name) != OK) {
WARN_PRINT(vformat("Failed to create shortcut for GUI button '%s'", button_name));
}

gui_button->set_shortcut_feedback(false);
Expand All @@ -354,12 +414,7 @@ static bool generate_checkbox(generate_gui_args_t&& args) {

const String checkbox_name = Utilities::std_to_godot_string(checkbox.get_name());
const String shortcut_key_name = Utilities::std_to_godot_string(checkbox.get_shortcut());
Array event_array = get_events_from_shortcut_key(shortcut_key_name);

ERR_FAIL_COND_V_MSG(
shortcut_key_name.length() != 0 && event_array.size() == 0, false,
vformat("Unknown shortcut key '%s' for GUI checkbox %s", shortcut_key_name, checkbox_name)
);
ERR_FAIL_NULL_V_MSG(checkbox.get_sprite(), false, vformat("Null sprite for GUI checkbox %s", checkbox_name));

GFX::IconTextureSprite const* texture_sprite = checkbox.get_sprite()->cast_to<GFX::IconTextureSprite>();
Expand Down Expand Up @@ -390,11 +445,8 @@ static bool generate_checkbox(generate_gui_args_t&& args) {
ret &= gui_icon_button->set_gfx_font(checkbox.get_font()) == OK;
}

if (shortcut_key_name.length() != 0) {
Ref<Shortcut> shortcut;
shortcut.instantiate();
shortcut->set_events(event_array);
gui_icon_button->set_shortcut(shortcut);
if (try_create_shortcut_action_for_button(gui_icon_button, shortcut_key_name) != OK) {
WARN_PRINT(vformat("Failed to create shortcut hotkey for GUI checkbox '%s'", checkbox_name));
}

gui_icon_button->set_shortcut_feedback(false);
Expand Down
26 changes: 21 additions & 5 deletions game/addons/keychain/Keychain.gd
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ extends Node

const PROFILES_PATH := "user://shortcut_profiles"

signal reload_keychain()

## [Array] of [ShortcutProfile]s.
var profiles: Array[ShortcutProfile] = [preload("profiles/default.tres")]
var selected_profile := profiles[0] ## The currently selected [ShortcutProfile].
Expand All @@ -22,9 +24,12 @@ var ignore_ui_actions := true
## and the fourth for [InputEventJoypadMotion]s.
var changeable_types: PackedByteArray = [true, true, true, true]
## The file path of the [code]config_file[/code].
var config_path := "user://cache.ini"
var config_path := "user://config.ini"
## Used to store the settings to the filesystem.
var config_file: ConfigFile
## Used to check if unused binding check should be ignored for action
var keep_binding_check : Callable = func(action_name : StringName) -> bool:
return action_name.begins_with("button_") and action_name.ends_with("_hotkey")


class InputAction:
Expand All @@ -48,6 +53,10 @@ class InputGroup:
folded = _folded


func _init() -> void:
for locale in TranslationServer.get_loaded_locales():
load_translation(locale)

func _ready() -> void:
if !config_file:
config_file = ConfigFile.new()
Expand All @@ -58,11 +67,11 @@ func _ready() -> void:
DirAccess.make_dir_recursive_absolute(PROFILES_PATH)
var profile_dir := DirAccess.open(PROFILES_PATH)
profile_dir.list_dir_begin()
var file_name = profile_dir.get_next()
var file_name := profile_dir.get_next()
while file_name != "":
if !profile_dir.current_is_dir():
if file_name.get_extension() == "tres":
var file = load(PROFILES_PATH.path_join(file_name))
var file := load(PROFILES_PATH.path_join(file_name))
if file is ShortcutProfile:
profiles.append(file)
file_name = profile_dir.get_next()
Expand All @@ -76,19 +85,23 @@ func _ready() -> void:
if saved:
profiles.append(profile)

initialize_profiles()

func initialize_profiles() -> void:
for profile in profiles:
profile.fill_bindings()

profile_index = config_file.get_value("shortcuts", "shortcuts_profile", 0)
change_profile(profile_index)

Keychain.reload_keychain.emit()

func change_profile(index: int) -> void:
if index >= profiles.size():
index = profiles.size() - 1
profile_index = index
selected_profile = profiles[index]
for action in selected_profile.bindings:
if not InputMap.has_action(action): continue
action_erase_events(action)
for event in selected_profile.bindings[action]:
action_add_event(action, event)
Expand All @@ -107,7 +120,10 @@ func action_erase_events(action: StringName) -> void:


func load_translation(locale: String) -> void:
var translation = load("res://addons/keychain/translations".path_join(locale + ".po"))
var translation_file_path := "res://addons/keychain/translations".path_join(locale + ".po")
if not ResourceLoader.exists(translation_file_path, "Translation"):
return
var translation := load(translation_file_path)
if is_instance_valid(translation) and translation is Translation:
TranslationServer.add_translation(translation)

Expand Down
10 changes: 7 additions & 3 deletions game/addons/keychain/ShortcutEdit.gd
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ const MOUSE_BUTTON_NAMES: PackedStringArray = [
"Wheel Down Button",
"Wheel Left Button",
"Wheel Right Button",
"X Button 1",
"X Button 2",
"Mouse Thumb Button 1",
"Mouse Thumb Button 2",
]

const JOY_BUTTON_NAMES: PackedStringArray = [
Expand Down Expand Up @@ -110,6 +110,10 @@ func _ready() -> void:
if OS.get_name() == "Web":
$VBoxContainer/HBoxContainer/OpenProfileFolder.queue_free()

Keychain.reload_keychain.connect(_on_reload_keychain)

func _on_reload_keychain() -> void:
_on_ProfileOptionButton_item_selected(Keychain.profile_index)

func _construct_tree() -> void:
var buttons_disabled := false if Keychain.selected_profile.customizable else true
Expand Down Expand Up @@ -271,7 +275,7 @@ func _on_shortcut_tree_button_clicked(item: TreeItem, _column: int, id: int, _mb
rect.position.y += 42 - tree.get_scroll().y
rect.position += global_position
rect.size = Vector2(110, 23 * shortcut_type_menu.get_item_count())
shortcut_type_menu.popup(rect)
shortcut_type_menu.popup_on_parent(rect)
elif id == 1: # Delete
Keychain.action_erase_events(action)
Keychain.selected_profile.change_action(action)
Expand Down
5 changes: 5 additions & 0 deletions game/addons/keychain/ShortcutEdit.tscn
Original file line number Diff line number Diff line change
Expand Up @@ -35,25 +35,30 @@ text = "Shortcut profile:"

[node name="ProfileOptionButton" type="OptionButton" parent="VBoxContainer/HBoxContainer"]
layout_mode = 2
size_flags_horizontal = 3
mouse_default_cursor_shape = 2

[node name="NewProfile" type="Button" parent="VBoxContainer/HBoxContainer"]
layout_mode = 2
size_flags_horizontal = 3
mouse_default_cursor_shape = 2
text = "New"

[node name="RenameProfile" type="Button" parent="VBoxContainer/HBoxContainer"]
layout_mode = 2
size_flags_horizontal = 3
mouse_default_cursor_shape = 2
text = "Rename"

[node name="DeleteProfile" type="Button" parent="VBoxContainer/HBoxContainer"]
layout_mode = 2
size_flags_horizontal = 3
mouse_default_cursor_shape = 2
text = "Delete"

[node name="OpenProfileFolder" type="Button" parent="VBoxContainer/HBoxContainer"]
layout_mode = 2
size_flags_horizontal = 3
mouse_default_cursor_shape = 2
text = "Open Folder"

Expand Down
3 changes: 3 additions & 0 deletions game/addons/keychain/ShortcutProfile.gd
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ func fill_bindings() -> void:
bindings[action] = InputMap.action_get_events(action)
unnecessary_actions.erase(action)
for action in unnecessary_actions:
if Keychain.keep_binding_check.call(action):
unnecessary_actions.erase(action)
continue
bindings.erase(action)
save()

Expand Down
2 changes: 1 addition & 1 deletion game/addons/keychain/assets/add.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion game/addons/keychain/assets/close.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion game/addons/keychain/assets/edit.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion game/addons/keychain/assets/folder.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion game/addons/keychain/assets/keyboard.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion game/addons/keychain/assets/keyboard_physical.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion game/addons/keychain/assets/mouse.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion game/addons/keychain/assets/shortcut.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 2 additions & 2 deletions game/addons/keychain/translations/Translations.pot
Original file line number Diff line number Diff line change
Expand Up @@ -103,10 +103,10 @@ msgstr ""
msgid "Wheel Right Button"
msgstr ""

msgid "X Button 1"
msgid "Mouse Thumb Button 1"
msgstr ""

msgid "X Button 2"
msgid "Mouse Thumb Button 2"
msgstr ""

msgid "DualShock Cross, Xbox A, Nintendo B"
Expand Down
8 changes: 4 additions & 4 deletions game/addons/keychain/translations/el_GR.po
Original file line number Diff line number Diff line change
Expand Up @@ -109,11 +109,11 @@ msgstr "Τρόχος αριστερά"
msgid "Wheel Right Button"
msgstr "Τρόχος δεξιά"

msgid "X Button 1"
msgstr "Κουμπί X 1"
msgid "Mouse Thumb Button 1"
msgstr "Κουμπί αντίχειρα ποντικού 1"

msgid "X Button 2"
msgstr "Κουμπί X 2"
msgid "Mouse Thumb Button 2"
msgstr "Κουμπί αντίχειρα ποντικιού 2"

msgid "DualShock Cross, Xbox A, Nintendo B"
msgstr "DualShock Σταυρός, Xbox A, Nintendo B"
Expand Down
2 changes: 2 additions & 0 deletions game/assets/localisation/locales/helpers.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
keys;en;fr;de;es
Mapmode %s;Mapmode %s;Mode De Carte %s;Kartenmodus %s;Modo Mapa %s
Loading

0 comments on commit 7aafc8c

Please sign in to comment.