miggor-StreamGraph/graph_node_renderer/shortcuts/renderer_shortcuts.gd

192 lines
7.7 KiB
GDScript

# (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]
## Shortcuts are mapped to an [codee]action[/code] String, much like [InputMap], with the difference
## 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"}),
"focus_nodes": create_shortcut_from_dict({"key": "f"}),
"_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"}),
"toggle_sidebar": create_shortcut_from_dict({"key": "t"}),
}
## 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
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
## 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)
## 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())