2024-04-14 14:40:26 +02:00
|
|
|
# (c) 2023-present Eroax
|
|
|
|
# (c) 2023-present Yagich
|
|
|
|
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
|
|
|
class_name RendererShortcuts
|
|
|
|
## @experimental
|
|
|
|
## A manager for shortcuts/hotkeys for the default renderer.
|
|
|
|
##
|
|
|
|
## Allows overriding shortcuts and provides an API for the renderer's various interfaces to check
|
|
|
|
## that a shortcut has been pressed. Also handles saving the overrides using [RendererPersistence].[br]
|
2024-09-03 10:13:27 +02:00
|
|
|
## Shortcuts are mapped to an [code]action[/code] String, much like [InputMap], with the difference
|
2024-04-14 14:40:26 +02:00
|
|
|
## being that only one shortcut can be assigned to any one action. This may change in the future.
|
|
|
|
|
|
|
|
## The map of overriden shortcuts. See [member defaults].
|
|
|
|
static var map = {}
|
|
|
|
|
|
|
|
const PERSISTENCE_NAMESPACE := "default"
|
|
|
|
const PERSISTENCE_CHANNEL := "shortcuts"
|
|
|
|
const PERSISTENCE_KEY := "overrides"
|
|
|
|
|
|
|
|
## Notification that is emitted when the overrides have finished loading from [RendererPersistence].
|
|
|
|
const NOTIFICATION_SHORTCUTS_LOADED := 99999
|
|
|
|
## Notification that is emitted when any shortcut has been updated (i.e., overridden or reset).
|
|
|
|
const NOTIFICATION_SHORTCUTS_UPDATED := 99998
|
|
|
|
|
|
|
|
## Map of default shortcuts.[br]
|
|
|
|
## The structure is:[br]
|
|
|
|
##[codeblock]
|
|
|
|
##{
|
|
|
|
## action: Shortcut|String
|
|
|
|
##}
|
|
|
|
##[/codeblock]
|
|
|
|
## where [code]action[/code] is any String.[br]
|
|
|
|
## If [code]action[/code] starts with [code]"_sep"[/code], the value must also be a String,
|
|
|
|
## which should be interpreted as a category separator with the value being the category name.[br]
|
|
|
|
## Shortcuts can be created more easily with [method create_shortcut_from_dict].
|
|
|
|
static var defaults = {
|
|
|
|
"_sep_file": "file",
|
|
|
|
"new_deck": create_shortcut_from_dict({"ctrl": true, "key": "n"}),
|
|
|
|
"open_deck": create_shortcut_from_dict({"ctrl": true, "key": "o"}),
|
|
|
|
"save_deck": create_shortcut_from_dict({"ctrl": true, "key": "s"}),
|
|
|
|
"save_deck_as": create_shortcut_from_dict({"ctrl": true, "shift": true, "key": "s"}),
|
|
|
|
"close_deck": create_shortcut_from_dict({"ctrl": true, "key": "w"}),
|
|
|
|
|
|
|
|
"open_recent_deck_1": create_shortcut_from_dict({"ctrl": true, "key": "1"}),
|
|
|
|
"open_recent_deck_2": create_shortcut_from_dict({"ctrl": true, "key": "2"}),
|
|
|
|
"open_recent_deck_3": create_shortcut_from_dict({"ctrl": true, "key": "3"}),
|
|
|
|
"open_recent_deck_4": create_shortcut_from_dict({"ctrl": true, "key": "4"}),
|
|
|
|
"open_recent_deck_5": create_shortcut_from_dict({"ctrl": true, "key": "5"}),
|
|
|
|
|
|
|
|
"_sep_deck": "deck",
|
|
|
|
"add_node": create_shortcut_from_dict({"shift": true, "key": "a"}),
|
|
|
|
"group_nodes": create_shortcut_from_dict({"ctrl": true, "key": "g"}),
|
|
|
|
"enter_group": create_shortcut_from_dict({"key": "tab"}),
|
|
|
|
"rename_node": create_shortcut_from_dict({"key": "f2"}),
|
2024-04-16 17:16:38 +02:00
|
|
|
"focus_nodes": create_shortcut_from_dict({"key": "f"}),
|
2024-04-14 14:40:26 +02:00
|
|
|
|
|
|
|
"_sep_nodes": "nodes",
|
|
|
|
"copy_nodes": create_shortcut_from_dict({"ctrl": true, "key": "c"}),
|
|
|
|
#"cut_nodes": create_shortcut_from_dict({"ctrl": true, "key": "x"}),
|
|
|
|
"paste_nodes": create_shortcut_from_dict({"ctrl": true, "key": "v"}),
|
|
|
|
"duplicate_nodes": create_shortcut_from_dict({"ctrl": true, "key": "d"}),
|
|
|
|
|
|
|
|
"_sep_misc": "misc",
|
|
|
|
"settings": create_shortcut_from_dict({"ctrl": true, "key": "comma"}),
|
|
|
|
"toggle_bottom_dock": create_shortcut_from_dict({"key": "n"}),
|
2024-04-16 17:16:38 +02:00
|
|
|
"toggle_sidebar": create_shortcut_from_dict({"key": "t"}),
|
2024-04-14 14:40:26 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
## Loads shortcut overrides from [RendererPersistence].
|
|
|
|
static func load_overrides() -> void:
|
|
|
|
var loaded_map: Dictionary = RendererPersistence.get_or_create(PERSISTENCE_NAMESPACE, PERSISTENCE_CHANNEL, PERSISTENCE_KEY, {})
|
|
|
|
map_from_dict(loaded_map)
|
|
|
|
(Engine.get_main_loop() as SceneTree).get_root().propagate_notification(NOTIFICATION_SHORTCUTS_LOADED)
|
|
|
|
|
|
|
|
|
|
|
|
## Returns whether a shortcut [param action] has been pressed with the given [param event].
|
|
|
|
## To be called from [method Node._input] and similar.
|
|
|
|
static func check_shortcut(action: String, event: InputEvent) -> bool:
|
|
|
|
var over := (map.get(action) as Shortcut)
|
|
|
|
if over:
|
|
|
|
return over.matches_event(event) and event.pressed and not event.is_echo()
|
|
|
|
|
|
|
|
return (defaults.get(action) as Shortcut).matches_event(event) and event.pressed and not event.is_echo()
|
|
|
|
|
|
|
|
|
|
|
|
## Returns the [Shortcut] associated with the [param action].
|
|
|
|
static func get_shortcut(action: String) -> Shortcut:
|
|
|
|
var over := map.get(action, null) as Shortcut
|
|
|
|
if over:
|
|
|
|
return over
|
|
|
|
|
|
|
|
return defaults.get(action, null) as Shortcut
|
|
|
|
|
|
|
|
|
|
|
|
## Returns the full shortcut map, which is a combination of default and overridden [code]action[/code]s.
|
|
|
|
static func get_full_map() -> Dictionary:
|
|
|
|
var defaults_copy := defaults.duplicate()
|
|
|
|
defaults_copy.merge(map, true)
|
|
|
|
return defaults_copy
|
|
|
|
|
|
|
|
|
2024-05-24 15:17:07 +02:00
|
|
|
static func get_full_map_string() -> Dictionary:
|
|
|
|
var full_map := get_full_map()
|
|
|
|
var res := {}
|
|
|
|
for key: String in full_map:
|
|
|
|
if key.begins_with("_"):
|
|
|
|
continue
|
|
|
|
res[key] = (full_map[key] as Shortcut).get_as_text()
|
|
|
|
return res
|
|
|
|
|
|
|
|
|
|
|
|
|
2024-04-14 14:40:26 +02:00
|
|
|
## Returns the default [Shortcut] for the given [param action].
|
|
|
|
static func get_default(action: String) -> Shortcut:
|
|
|
|
return defaults.get(action)
|
|
|
|
|
|
|
|
|
|
|
|
## Overrides an [param action], setting its shortcut to the equivalent of the [param event].
|
|
|
|
static func add_override(action: String, event: InputEvent) -> Shortcut:
|
|
|
|
var e := {
|
|
|
|
"key": OS.get_keycode_string(event.keycode).to_lower(),
|
|
|
|
"ctrl": event.ctrl_pressed,
|
|
|
|
"alt": event.alt_pressed,
|
|
|
|
"shift": event.shift_pressed,
|
|
|
|
}
|
|
|
|
|
|
|
|
map[action] = create_shortcut_from_dict(e)
|
|
|
|
commit_overrides()
|
|
|
|
(Engine.get_main_loop() as SceneTree).get_root().propagate_notification(NOTIFICATION_SHORTCUTS_UPDATED)
|
|
|
|
return map[action]
|
|
|
|
|
|
|
|
|
|
|
|
## Returns [code]true[/code] if the [param action] has been overridden.
|
|
|
|
static func has_override(action: String) -> bool:
|
|
|
|
return map.has(action)
|
|
|
|
|
|
|
|
|
|
|
|
## Removes the override for [param action], resetting the shortcut to default.
|
|
|
|
static func remove_override(action: String) -> void:
|
|
|
|
map.erase(action)
|
|
|
|
commit_overrides()
|
|
|
|
(Engine.get_main_loop() as SceneTree).get_root().propagate_notification(NOTIFICATION_SHORTCUTS_UPDATED)
|
|
|
|
|
|
|
|
|
|
|
|
## Returns a [Shortcut] from the given [Dictionary].[br]
|
|
|
|
## The dictionary must contain at least one key, [code]key[/code], which is a Unicode
|
|
|
|
## String of the character that must be pressed to activate the shortcut.[br]
|
|
|
|
## The dictionary can also optionally contain the keys [code]ctrl[/code], [code]alt[/code], and [code]shift[/code]
|
|
|
|
## with their values being [bool] to indicate if those modifiers should be held along with the [code]key[/code].
|
|
|
|
static func create_shortcut_from_dict(d: Dictionary) -> Shortcut:
|
|
|
|
var s := Shortcut.new()
|
|
|
|
|
|
|
|
var input := InputEventKey.new()
|
|
|
|
input.ctrl_pressed = d.get("ctrl", false)
|
|
|
|
input.alt_pressed = d.get("alt", false)
|
|
|
|
input.shift_pressed = d.get("shift", false)
|
|
|
|
input.keycode = OS.find_keycode_from_string(d.key.to_upper())
|
|
|
|
|
|
|
|
s.events.append(input)
|
|
|
|
return s
|
|
|
|
|
|
|
|
|
|
|
|
## Serializes the overrides [member map] to a [Dictionary].
|
|
|
|
static func map_to_dict() -> Dictionary:
|
|
|
|
var res := {}
|
|
|
|
for action: String in map:
|
|
|
|
res[action] = {
|
|
|
|
"ctrl": (map[action] as Shortcut).events[0].ctrl_pressed,
|
|
|
|
"alt": (map[action] as Shortcut).events[0].alt_pressed,
|
|
|
|
"shift": (map[action] as Shortcut).events[0].shift_pressed,
|
|
|
|
"key": OS.get_keycode_string((map[action] as Shortcut).events[0].keycode),
|
|
|
|
}
|
|
|
|
return res
|
|
|
|
|
|
|
|
|
|
|
|
## Loads a dictionary such as one created by [method map_to_dict] into [member map].
|
|
|
|
static func map_from_dict(d: Dictionary) -> void:
|
|
|
|
for action: String in d:
|
|
|
|
map[action] = create_shortcut_from_dict(d[action])
|
|
|
|
|
|
|
|
|
|
|
|
## Saves the overrides [member map] to [RendererPersistence].
|
|
|
|
static func commit_overrides() -> void:
|
|
|
|
RendererPersistence.set_value(PERSISTENCE_NAMESPACE, PERSISTENCE_CHANNEL, PERSISTENCE_KEY, map_to_dict())
|
|
|
|
RendererPersistence.commit(PERSISTENCE_NAMESPACE, PERSISTENCE_CHANNEL)
|
2024-05-24 15:17:07 +02:00
|
|
|
|
|
|
|
|
|
|
|
## Replaces shortcut names with their keybinds in [param s].[br]
|
|
|
|
## Example usage: [code]interpolate_string("Press {new_deck} to open a new deck.")[/code]
|
|
|
|
static func interpolate_string(s: String) -> String:
|
|
|
|
return s.format(get_full_map_string())
|