add shortcuts (#145)

closes #133

Reviewed-on: https://codeberg.org/StreamGraph/StreamGraph/pulls/145
Co-authored-by: Lera Elvoé <yagich@poto.cafe>
Co-committed-by: Lera Elvoé <yagich@poto.cafe>
This commit is contained in:
Lera Elvoé 2024-04-14 12:40:26 +00:00 committed by yagich
parent a56280f00a
commit ed5a0f427b
13 changed files with 501 additions and 110 deletions

View file

@ -20,12 +20,6 @@ const PERSISTENCE_NAMESPACE := "default"
## Reference to the [FileDialog] used for File operations through the program. ## Reference to the [FileDialog] used for File operations through the program.
@onready var file_dialog: FileDialog = $FileDialog @onready var file_dialog: FileDialog = $FileDialog
@export var new_deck_shortcut: Shortcut
@export var open_deck_shortcut: Shortcut
@export var save_deck_shortcut: Shortcut
@export var save_deck_as_shortcut: Shortcut
@export var close_deck_shortcut: Shortcut
## Enum for storing the Options in the "File" PopupMenu. ## Enum for storing the Options in the "File" PopupMenu.
enum FileMenuId { enum FileMenuId {
NEW, NEW,
@ -62,8 +56,12 @@ enum HelpMenuId {
@onready var about_dialog: AcceptDialog = %AboutDialog @onready var about_dialog: AcceptDialog = %AboutDialog
enum EditMenuId { enum EditMenuId {
COPY,
PASTE,
DUPLICATE,
SETTINGS, SETTINGS,
} }
@onready var edit_popup_menu: PopupMenu = %Edit
@onready var settings_dialog: SettingsDialog = %SettingsDialog @onready var settings_dialog: SettingsDialog = %SettingsDialog
## Weak Reference to the Deck that is currently going to be saved. ## Weak Reference to the Deck that is currently going to be saved.
@ -105,6 +103,11 @@ func _ready() -> void:
PERSISTENCE_NAMESPACE, "config", PERSISTENCE_NAMESPACE, "config",
"recent_path", OS.get_system_dir(OS.SYSTEM_DIR_DOCUMENTS) "recent_path", OS.get_system_dir(OS.SYSTEM_DIR_DOCUMENTS)
) )
RendererShortcuts.load_overrides()
reset_popup_menu_shortcuts()
add_recents_to_menu() add_recents_to_menu()
var rpc_port: int = RendererPersistence.get_or_create( var rpc_port: int = RendererPersistence.get_or_create(
@ -153,12 +156,6 @@ func _ready() -> void:
Connections.twitch.chat_received_rich.connect(Connections._twitch_chat_received) Connections.twitch.chat_received_rich.connect(Connections._twitch_chat_received)
file_popup_menu.set_item_shortcut(FileMenuId.NEW, new_deck_shortcut)
file_popup_menu.set_item_shortcut(FileMenuId.OPEN, open_deck_shortcut)
file_popup_menu.set_item_shortcut(FileMenuId.SAVE, save_deck_shortcut)
file_popup_menu.set_item_shortcut(FileMenuId.SAVE_AS, save_deck_as_shortcut)
file_popup_menu.set_item_shortcut(FileMenuId.CLOSE, close_deck_shortcut)
bottom_dock.variable_viewer.top_field_edited.connect( bottom_dock.variable_viewer.top_field_edited.connect(
func(old_name: String, new_name: String, new_value: Variant) -> void: func(old_name: String, new_name: String, new_value: Variant) -> void:
get_active_deck_renderer().dirty = true get_active_deck_renderer().dirty = true
@ -172,6 +169,30 @@ func _ready() -> void:
) )
func reset_popup_menu_shortcuts() -> void:
file_popup_menu.set_item_shortcut(FileMenuId.NEW, RendererShortcuts.get_shortcut("new_deck"))
file_popup_menu.set_item_shortcut(FileMenuId.OPEN, RendererShortcuts.get_shortcut("open_deck"))
file_popup_menu.set_item_shortcut(FileMenuId.SAVE, RendererShortcuts.get_shortcut("save_deck"))
file_popup_menu.set_item_shortcut(FileMenuId.SAVE_AS, RendererShortcuts.get_shortcut("save_deck_as"))
file_popup_menu.set_item_shortcut(FileMenuId.CLOSE, RendererShortcuts.get_shortcut("close_deck"))
edit_popup_menu.set_item_shortcut(EditMenuId.COPY, RendererShortcuts.get_shortcut("copy_nodes"))
edit_popup_menu.set_item_shortcut(EditMenuId.PASTE, RendererShortcuts.get_shortcut("paste_nodes"))
edit_popup_menu.set_item_shortcut(EditMenuId.DUPLICATE, RendererShortcuts.get_shortcut("duplicate_nodes"))
edit_popup_menu.set_item_shortcut(EditMenuId.SETTINGS, RendererShortcuts.get_shortcut("settings"))
func reset_recents_shortcuts() -> void:
for i in recent_files.size():
var s := RendererShortcuts.get_shortcut("open_recent_deck_%s" % [i + 1])
file_popup_menu.set_item_shortcut(file_popup_menu.get_item_count() - recent_files.size() + i, s)
func _notification(what: int) -> void:
if what == RendererShortcuts.NOTIFICATION_SHORTCUTS_UPDATED:
reset_popup_menu_shortcuts()
func _on_tab_container_tab_about_to_change(previous_tab: int) -> void: func _on_tab_container_tab_about_to_change(previous_tab: int) -> void:
var deck := get_deck_at_tab(previous_tab) var deck := get_deck_at_tab(previous_tab)
if deck.variables_updated.is_connected(bottom_dock.rebuild_variable_tree): if deck.variables_updated.is_connected(bottom_dock.rebuild_variable_tree):
@ -209,6 +230,29 @@ func _on_edit_id_pressed(id: int) -> void:
match id: match id:
EditMenuId.SETTINGS: EditMenuId.SETTINGS:
settings_dialog.popup_centered() settings_dialog.popup_centered()
EditMenuId.COPY:
var r := get_active_deck_renderer()
r._on_copy_nodes_request()
EditMenuId.PASTE:
var r := get_active_deck_renderer()
r._on_paste_nodes_request()
EditMenuId.DUPLICATE:
var r := get_active_deck_renderer()
r._on_duplicate_nodes_request()
func _on_edit_about_to_popup() -> void:
# enable/disable the copy/paste buttons depending on if there's a deck renderer and if it has nodes selected
if tab_container.get_tab_count() == 0:
edit_popup_menu.set_item_disabled(EditMenuId.COPY, true)
edit_popup_menu.set_item_disabled(EditMenuId.PASTE, true)
edit_popup_menu.set_item_disabled(EditMenuId.DUPLICATE, true)
return
edit_popup_menu.set_item_disabled(EditMenuId.PASTE, false)
var r := get_active_deck_renderer()
edit_popup_menu.set_item_disabled(EditMenuId.COPY, r.get_selected_nodes().size() == 0)
edit_popup_menu.set_item_disabled(EditMenuId.DUPLICATE, r.get_selected_nodes().size() == 0)
## Adds an empty [DeckRendererGraphEdit] with a corresponding [Deck] for it's data. ## Adds an empty [DeckRendererGraphEdit] with a corresponding [Deck] for it's data.
@ -225,6 +269,7 @@ func add_empty_deck() -> void:
tab_container.set_current_tab(tab) tab_container.set_current_tab(tab)
bottom_dock.variable_viewer.enable_new_button() bottom_dock.variable_viewer.enable_new_button()
## Closes the current tab in [member tab_container] ## Closes the current tab in [member tab_container]
func close_current_tab() -> void: func close_current_tab() -> void:
tab_container.close_tab(tab_container.get_current_tab()) tab_container.close_tab(tab_container.get_current_tab())
@ -510,14 +555,8 @@ func add_recents_to_menu() -> void:
file = base.path_join(filename) file = base.path_join(filename)
file_popup_menu.add_item(file) file_popup_menu.add_item(file)
var s := Shortcut.new()
var k := InputEventKey.new()
@warning_ignore("int_as_enum_without_cast")
k.keycode = KEY_1 + i
k.ctrl_pressed = true
s.events.append(k)
file_popup_menu.set_item_shortcut(file_popup_menu.get_item_count() - 1, s)
reset_recents_shortcuts()
RendererPersistence.set_value(PERSISTENCE_NAMESPACE, "config", "recent_files", recent_files) RendererPersistence.set_value(PERSISTENCE_NAMESPACE, "config", "recent_files", recent_files)
@ -552,8 +591,8 @@ func _on_unsaved_changes_dialog_confirmed() -> void:
quit_completed.emit() quit_completed.emit()
func _unhandled_input(event: InputEvent) -> void: func _unhandled_key_input(event: InputEvent) -> void:
if event.is_action_pressed("toggle_bottom_dock"): if RendererShortcuts.check_shortcut("toggle_bottom_dock", event):
bottom_dock.visible = !bottom_dock.visible bottom_dock.visible = !bottom_dock.visible
accept_event() accept_event()
@ -564,3 +603,4 @@ func _on_help_id_pressed(id: int) -> void:
about_dialog.show() about_dialog.show()
HelpMenuId.DOCS: HelpMenuId.DOCS:
OS.shell_open("https://codeberg.org/Eroax/StreamGraph/wiki") OS.shell_open("https://codeberg.org/Eroax/StreamGraph/wiki")

View file

@ -1,4 +1,4 @@
[gd_scene load_steps=27 format=3 uid="uid://duaah5x0jhkn6"] [gd_scene load_steps=17 format=3 uid="uid://duaah5x0jhkn6"]
[ext_resource type="Script" path="res://graph_node_renderer/deck_holder_renderer.gd" id="1_67g2g"] [ext_resource type="Script" path="res://graph_node_renderer/deck_holder_renderer.gd" id="1_67g2g"]
[ext_resource type="PackedScene" uid="uid://b84f2ngtcm5b8" path="res://graph_node_renderer/tab_container_custom.tscn" id="1_s3ug2"] [ext_resource type="PackedScene" uid="uid://b84f2ngtcm5b8" path="res://graph_node_renderer/tab_container_custom.tscn" id="1_s3ug2"]
@ -17,52 +17,6 @@
[ext_resource type="PackedScene" uid="uid://brfrufvkjwcor" path="res://graph_node_renderer/rpc_setup_dialog.tscn" id="12_1xrfk"] [ext_resource type="PackedScene" uid="uid://brfrufvkjwcor" path="res://graph_node_renderer/rpc_setup_dialog.tscn" id="12_1xrfk"]
[ext_resource type="PackedScene" uid="uid://dodqetbke5wji" path="res://graph_node_renderer/settings_dialog.tscn" id="16_rktri"] [ext_resource type="PackedScene" uid="uid://dodqetbke5wji" path="res://graph_node_renderer/settings_dialog.tscn" id="16_rktri"]
[sub_resource type="InputEventKey" id="InputEventKey_giamc"]
device = -1
ctrl_pressed = true
keycode = 78
unicode = 110
[sub_resource type="Shortcut" id="Shortcut_30rq6"]
events = [SubResource("InputEventKey_giamc")]
[sub_resource type="InputEventKey" id="InputEventKey_cyjf4"]
device = -1
ctrl_pressed = true
keycode = 79
unicode = 111
[sub_resource type="Shortcut" id="Shortcut_m48tj"]
events = [SubResource("InputEventKey_cyjf4")]
[sub_resource type="InputEventKey" id="InputEventKey_jgr3p"]
device = -1
ctrl_pressed = true
keycode = 83
unicode = 115
[sub_resource type="Shortcut" id="Shortcut_xr6s4"]
events = [SubResource("InputEventKey_jgr3p")]
[sub_resource type="InputEventKey" id="InputEventKey_762xj"]
device = -1
shift_pressed = true
ctrl_pressed = true
keycode = 83
unicode = 83
[sub_resource type="Shortcut" id="Shortcut_myxuq"]
events = [SubResource("InputEventKey_762xj")]
[sub_resource type="InputEventKey" id="InputEventKey_exx3o"]
device = -1
ctrl_pressed = true
keycode = 87
unicode = 119
[sub_resource type="Shortcut" id="Shortcut_46v8y"]
events = [SubResource("InputEventKey_exx3o")]
[node name="DeckHolderRenderer" type="Control"] [node name="DeckHolderRenderer" type="Control"]
layout_mode = 3 layout_mode = 3
anchors_preset = 15 anchors_preset = 15
@ -75,11 +29,6 @@ script = ExtResource("1_67g2g")
DECK_SCENE = ExtResource("3_uf16c") DECK_SCENE = ExtResource("3_uf16c")
DEBUG_DECKS_LIST = ExtResource("4_ux0jt") DEBUG_DECKS_LIST = ExtResource("4_ux0jt")
DEBUG_NODES_LIST = ExtResource("5_pnfg8") DEBUG_NODES_LIST = ExtResource("5_pnfg8")
new_deck_shortcut = SubResource("Shortcut_30rq6")
open_deck_shortcut = SubResource("Shortcut_m48tj")
save_deck_shortcut = SubResource("Shortcut_xr6s4")
save_deck_as_shortcut = SubResource("Shortcut_myxuq")
close_deck_shortcut = SubResource("Shortcut_46v8y")
[node name="MarginContainer" type="MarginContainer" parent="."] [node name="MarginContainer" type="MarginContainer" parent="."]
layout_mode = 1 layout_mode = 1
@ -128,9 +77,15 @@ item_7/separator = true
[node name="Edit" type="PopupMenu" parent="MarginContainer/VSplitContainer/VBoxContainer/MenuBar"] [node name="Edit" type="PopupMenu" parent="MarginContainer/VSplitContainer/VBoxContainer/MenuBar"]
unique_name_in_owner = true unique_name_in_owner = true
item_count = 1 item_count = 4
item_0/text = "Settings..." item_0/text = "Copy Node(s)"
item_0/id = 0 item_0/id = 0
item_1/text = "Paste Node(s)"
item_1/id = 1
item_2/text = "Duplicate Node(s)"
item_2/id = 2
item_3/text = "Settings..."
item_3/id = 3
[node name="Connections" type="PopupMenu" parent="MarginContainer/VSplitContainer/VBoxContainer/MenuBar"] [node name="Connections" type="PopupMenu" parent="MarginContainer/VSplitContainer/VBoxContainer/MenuBar"]
unique_name_in_owner = true unique_name_in_owner = true
@ -205,6 +160,7 @@ unique_name_in_owner = true
unique_name_in_owner = true unique_name_in_owner = true
[connection signal="id_pressed" from="MarginContainer/VSplitContainer/VBoxContainer/MenuBar/File" to="." method="_on_file_id_pressed"] [connection signal="id_pressed" from="MarginContainer/VSplitContainer/VBoxContainer/MenuBar/File" to="." method="_on_file_id_pressed"]
[connection signal="about_to_popup" from="MarginContainer/VSplitContainer/VBoxContainer/MenuBar/Edit" to="." method="_on_edit_about_to_popup"]
[connection signal="id_pressed" from="MarginContainer/VSplitContainer/VBoxContainer/MenuBar/Edit" to="." method="_on_edit_id_pressed"] [connection signal="id_pressed" from="MarginContainer/VSplitContainer/VBoxContainer/MenuBar/Edit" to="." method="_on_edit_id_pressed"]
[connection signal="id_pressed" from="MarginContainer/VSplitContainer/VBoxContainer/MenuBar/Connections" to="." method="_on_connections_id_pressed"] [connection signal="id_pressed" from="MarginContainer/VSplitContainer/VBoxContainer/MenuBar/Connections" to="." method="_on_connections_id_pressed"]
[connection signal="id_pressed" from="MarginContainer/VSplitContainer/VBoxContainer/MenuBar/Debug" to="." method="_on_debug_id_pressed"] [connection signal="id_pressed" from="MarginContainer/VSplitContainer/VBoxContainer/MenuBar/Debug" to="." method="_on_debug_id_pressed"]

View file

@ -3,9 +3,9 @@
[ext_resource type="Script" path="res://graph_node_renderer/deck_node_renderer_graph_node.gd" id="1_pos0w"] [ext_resource type="Script" path="res://graph_node_renderer/deck_node_renderer_graph_node.gd" id="1_pos0w"]
[node name="DeckNodeRendererGraphNode" type="GraphNode"] [node name="DeckNodeRendererGraphNode" type="GraphNode"]
script = ExtResource("1_pos0w")
custom_minimum_size = Vector2(200, 0) custom_minimum_size = Vector2(200, 0)
offset_right = 200.0 offset_right = 200.0
offset_bottom = 55.0 offset_bottom = 55.0
resizable = true resizable = true
title = "Deck Node" title = "Deck Node"
script = ExtResource("1_pos0w")

View file

@ -210,7 +210,7 @@ func get_selected_nodes() -> Array:
## Executes functionality based off hotkey inputs. Specifically handles creating groups ## Executes functionality based off hotkey inputs. Specifically handles creating groups
## based off the action "group_nodes". ## based off the action "group_nodes".
func _gui_input(event: InputEvent) -> void: func _gui_input(event: InputEvent) -> void:
if event.is_action_pressed("group_nodes") and get_selected_nodes().size() > 0: if RendererShortcuts.check_shortcut("group_nodes", event) and get_selected_nodes().size() > 0:
clear_connections() clear_connections()
var nodes = get_selected_nodes().map( var nodes = get_selected_nodes().map(
func(x: DeckNodeRendererGraphNode): func(x: DeckNodeRendererGraphNode):
@ -218,7 +218,16 @@ func _gui_input(event: InputEvent) -> void:
) )
deck.group_nodes(nodes) deck.group_nodes(nodes)
refresh_connections() refresh_connections()
get_viewport().set_input_as_handled() accept_event()
if RendererShortcuts.check_shortcut("add_node", event):
var p := get_viewport_rect().position + get_global_mouse_position()
p += Vector2(10, 10)
var r := Rect2i(p, search_popup_panel.size)
search_popup_panel.popup_on_parent(r)
add_node_menu.focus_search_bar()
popup_position = r.position
accept_event()
if event.is_action_pressed("debug_make_unique") and get_selected_nodes().size() == 1: if event.is_action_pressed("debug_make_unique") and get_selected_nodes().size() == 1:
var node: DeckNode = get_selected_nodes().map( var node: DeckNode = get_selected_nodes().map(
@ -230,12 +239,26 @@ func _gui_input(event: InputEvent) -> void:
node.make_unique() node.make_unique()
if event.is_action_pressed("rename_node") and get_selected_nodes().size() == 1: if RendererShortcuts.check_shortcut("rename_node", event) and get_selected_nodes().size() == 1:
var node: DeckNodeRendererGraphNode = get_selected_nodes()[0] var node: DeckNodeRendererGraphNode = get_selected_nodes()[0]
var pos := get_viewport_rect().position + get_global_mouse_position() var pos := get_viewport_rect().position + get_global_mouse_position()
rename_popup.popup_on_parent(Rect2i(pos, rename_popup_size)) rename_popup.popup_on_parent(Rect2i(pos, rename_popup_size))
rename_popup.le.size.x = rename_popup_size.x rename_popup.le.size.x = rename_popup_size.x
rename_popup.set_text(node.title) rename_popup.set_text(node.title)
accept_event()
# copy/paste/duplicate/etc
if RendererShortcuts.check_shortcut("copy_nodes", event) and get_selected_nodes().size() > 0:
_on_copy_nodes_request()
accept_event()
if RendererShortcuts.check_shortcut("duplicate_nodes", event) and get_selected_nodes().size() > 0:
_on_duplicate_nodes_request()
accept_event()
if RendererShortcuts.check_shortcut("paste_nodes", event):
_on_paste_nodes_request()
accept_event()
## Handles entering groups with action "enter_group". Done here to bypass neighbor ## Handles entering groups with action "enter_group". Done here to bypass neighbor
@ -244,11 +267,11 @@ func _input(event: InputEvent) -> void:
if not has_focus(): if not has_focus():
return return
if event.is_action_pressed("enter_group") and get_selected_nodes().size() == 1: if RendererShortcuts.check_shortcut("enter_group", event) and get_selected_nodes().size() == 1:
if (get_selected_nodes()[0] as DeckNodeRendererGraphNode).node.node_type != "group_node": if (get_selected_nodes()[0] as DeckNodeRendererGraphNode).node.node_type != "group_node":
return return
group_enter_requested.emit((get_selected_nodes()[0] as DeckNodeRendererGraphNode).node.group_id) group_enter_requested.emit((get_selected_nodes()[0] as DeckNodeRendererGraphNode).node.group_id)
get_viewport().set_input_as_handled() accept_event()
func _on_rename_popup_rename_confirmed(new_name: String) -> void: func _on_rename_popup_rename_confirmed(new_name: String) -> void:
@ -271,6 +294,7 @@ func _on_popup_request(p_popup_position: Vector2) -> void:
add_node_menu.focus_search_bar() add_node_menu.focus_search_bar()
popup_position = p_popup_position popup_position = p_popup_position
## Connected to [signal AddNodeMenu.node_selected] and creates a [DeckNode] using ## Connected to [signal AddNodeMenu.node_selected] and creates a [DeckNode] using
## [method NodeDB.instance_node]. Then placing it at the [member scroll_offset] + ## [method NodeDB.instance_node]. Then placing it at the [member scroll_offset] +
## [member popup_position] / [member zoom] ## [member popup_position] / [member zoom]
@ -297,7 +321,6 @@ func _on_deck_nodes_disconnected(from_node_id: String, to_node_id: String, from_
return x.node._id == to_node_id return x.node._id == to_node_id
)[0] )[0]
print(is_node_connected(from_node.name, from_output_port, to_node.name, to_input_port))
disconnect_node(from_node.name, from_output_port, to_node.name, to_input_port) disconnect_node(from_node.name, from_output_port, to_node.name, to_input_port)

View file

@ -20,9 +20,6 @@ script = ExtResource("1_pojfs")
NODE_SCENE = ExtResource("2_67ymi") NODE_SCENE = ExtResource("2_67ymi")
ADD_NODE_MENU_SCENE = ExtResource("3_thbt5") ADD_NODE_MENU_SCENE = ExtResource("3_thbt5")
[connection signal="copy_nodes_request" from="." to="." method="_on_copy_nodes_request"]
[connection signal="delete_nodes_request" from="." to="." method="_on_delete_nodes_request"] [connection signal="delete_nodes_request" from="." to="." method="_on_delete_nodes_request"]
[connection signal="duplicate_nodes_request" from="." to="." method="_on_duplicate_nodes_request"]
[connection signal="paste_nodes_request" from="." to="." method="_on_paste_nodes_request"]
[connection signal="popup_request" from="." to="." method="_on_popup_request"] [connection signal="popup_request" from="." to="." method="_on_popup_request"]
[connection signal="scroll_offset_changed" from="." to="." method="_on_scroll_offset_changed"] [connection signal="scroll_offset_changed" from="." to="." method="_on_scroll_offset_changed"]

View file

@ -6,7 +6,7 @@ class_name SettingsDialog
@onready var category_tree: Tree = %CategoryTree @onready var category_tree: Tree = %CategoryTree
@onready var category_content: Control = %CategoryContent @onready var category_content: Control = %CategoryContent
@onready var shortcuts_tree: Tree = %Shortcuts @onready var shortcuts_editor: ShortcutsEditor = %ShortcutsEditor
func _ready() -> void: func _ready() -> void:
@ -22,4 +22,16 @@ func _ready() -> void:
i.visible = i.get_index() == item.get_index() i.visible = i.get_index() == item.get_index()
) )
canceled.connect(shortcuts_editor.ensure_no_focus)
confirmed.connect(shortcuts_editor.ensure_no_focus)
category_tree.set_selected(cr.get_child(0), 0) category_tree.set_selected(cr.get_child(0), 0)
func _unhandled_key_input(event: InputEvent) -> void:
if not visible:
return
if event.keycode == KEY_ESCAPE and event.is_pressed():
canceled.emit()
hide()

View file

@ -1,6 +1,7 @@
[gd_scene load_steps=2 format=3 uid="uid://dodqetbke5wji"] [gd_scene load_steps=3 format=3 uid="uid://dodqetbke5wji"]
[ext_resource type="Script" path="res://graph_node_renderer/settings_dialog.gd" id="1_lh25g"] [ext_resource type="Script" path="res://graph_node_renderer/settings_dialog.gd" id="1_lh25g"]
[ext_resource type="PackedScene" uid="uid://bvjxc2vyx35b1" path="res://graph_node_renderer/shortcuts/shortcuts_editor.tscn" id="2_5tyfb"]
[node name="SettingsDialog" type="AcceptDialog"] [node name="SettingsDialog" type="AcceptDialog"]
title = "Settings" title = "Settings"
@ -8,6 +9,7 @@ initial_position = 1
size = Vector2i(705, 370) size = Vector2i(705, 370)
min_size = Vector2i(500, 250) min_size = Vector2i(500, 250)
ok_button_text = "Close" ok_button_text = "Close"
dialog_close_on_escape = false
script = ExtResource("1_lh25g") script = ExtResource("1_lh25g")
[node name="HSplitContainer" type="HSplitContainer" parent="."] [node name="HSplitContainer" type="HSplitContainer" parent="."]
@ -44,8 +46,7 @@ text = "Library Group Search Paths"
layout_mode = 2 layout_mode = 2
size_flags_vertical = 3 size_flags_vertical = 3
[node name="Shortcuts" type="Tree" parent="HSplitContainer/CategoryContent"] [node name="Shortcuts" type="VBoxContainer" parent="HSplitContainer/CategoryContent"]
unique_name_in_owner = true
visible = false visible = false
layout_mode = 1 layout_mode = 1
anchors_preset = 15 anchors_preset = 15
@ -53,4 +54,33 @@ anchor_right = 1.0
anchor_bottom = 1.0 anchor_bottom = 1.0
grow_horizontal = 2 grow_horizontal = 2
grow_vertical = 2 grow_vertical = 2
hide_root = true
[node name="Label" type="Label" parent="HSplitContainer/CategoryContent/Shortcuts"]
layout_mode = 2
text = "Shortcuts"
[node name="PanelContainer" type="PanelContainer" parent="HSplitContainer/CategoryContent/Shortcuts"]
layout_mode = 2
size_flags_vertical = 3
[node name="ScrollContainer" type="ScrollContainer" parent="HSplitContainer/CategoryContent/Shortcuts/PanelContainer"]
layout_mode = 2
horizontal_scroll_mode = 0
[node name="MarginContainer" type="MarginContainer" parent="HSplitContainer/CategoryContent/Shortcuts/PanelContainer/ScrollContainer"]
layout_mode = 2
size_flags_horizontal = 3
size_flags_vertical = 3
theme_override_constants/margin_left = 4
theme_override_constants/margin_top = 4
theme_override_constants/margin_right = 4
theme_override_constants/margin_bottom = 4
[node name="ShortcutsEditor" parent="HSplitContainer/CategoryContent/Shortcuts/PanelContainer/ScrollContainer/MarginContainer" instance=ExtResource("2_5tyfb")]
unique_name_in_owner = true
layout_mode = 2
[node name="Label2" type="Label" parent="HSplitContainer/CategoryContent/Shortcuts"]
layout_mode = 2
text = "Click on a shortcut to change it.
Press Escape to cancel the change or Enter to confirm it."

View file

@ -0,0 +1,173 @@
# (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"}),
"_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"}),
}
## 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
## 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)

View file

@ -0,0 +1,136 @@
# (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)
extends VBoxContainer
class_name ShortcutsEditor
## An editor for [RendererShortcuts].
const REVERT_ICON = preload("res://graph_node_renderer/textures/revert-icon.svg")
func _notification(what: int) -> void:
if what == RendererShortcuts.NOTIFICATION_SHORTCUTS_LOADED:
var g := ButtonGroup.new()
var map := RendererShortcuts.get_full_map()
for action: String in map:
if action.begins_with("_sep"):
# draw a separator.
var l := Label.new()
l.text = map[action].capitalize()
var hb := HBoxContainer.new()
var sep := HSeparator.new()
sep.size_flags_horizontal = Control.SIZE_EXPAND_FILL
hb.add_child(sep)
hb.add_child(l)
hb.add_child(sep.duplicate(0))
add_child.call_deferred(hb)
continue
var c := ShortcutDisplay.new(action, action.capitalize(), g)
add_child.call_deferred(c)
## Unfocuses all [ShorcutsEditor.ShortcutDisplay] children.
func ensure_no_focus() -> void:
for c in get_children():
if c is ShortcutDisplay:
c.unfocus_button()
## A container for displaying an editable shorcut.
class ShortcutDisplay extends HBoxContainer:
## The action this container represents and edits.
var action: String
var _label: Label
var _button: ShortcutButton
var _reset_button: Button
var _cc: CenterContainer
func _init(p_action: String, p_label: String, p_group: ButtonGroup) -> void:
action = p_action
_label = Label.new()
_label.size_flags_horizontal = Control.SIZE_EXPAND_FILL
_label.text = p_label
_button = ShortcutButton.new(RendererShortcuts.get_shortcut(p_action).events[0])
_button.button_group = p_group
_button.text = RendererShortcuts.get_shortcut(p_action).get_as_text()
_button.confirmed.connect(
func(raw_event: InputEvent):
var override := RendererShortcuts.add_override(action, raw_event)
_button.set_event(override.events[0])
_reset_button.disabled = false
)
_reset_button = Button.new()
_reset_button.icon = REVERT_ICON
_reset_button.tooltip_text = "Reset (default: %s)" % RendererShortcuts.get_default(action).get_as_text()
_reset_button.pressed.connect(
func():
_button.set_event(RendererShortcuts.get_default(action).events[0])
_reset_button.disabled = true
RendererShortcuts.remove_override(action)
)
_reset_button.disabled = not RendererShortcuts.has_override(action)
_cc = CenterContainer.new()
_cc.size_flags_horizontal = Control.SIZE_EXPAND_FILL
_button.size_flags_horizontal = Control.SIZE_SHRINK_CENTER
add_child(_label)
_cc.add_child(_button)
add_child(_cc)
add_child(_reset_button)
## Removes focus of the container's button, in cases where focus is
## about to be lost to prevent remapping an action on accident.
func unfocus_button() -> void:
_button.set_event(_button._event)
## A button that displays a shortcut and allows editing (remapping) it.
class ShortcutButton extends Button:
var _event: InputEvent
var _prev_event: InputEvent
## Emitted when Enter is pressed on the button.
signal confirmed(raw_event: InputEvent)
func _init(p_event: InputEvent) -> void:
toggle_mode = true
set_event(p_event)
## Sets the event this button is storing and updates its text.
func set_event(p_event: InputEvent) -> void:
button_pressed = false
_event = p_event
text = _event.as_text()
func _unhandled_key_input(event: InputEvent) -> void:
if not button_pressed:
return
accept_event()
if event.keycode == KEY_ESCAPE:
set_event(_event)
return
if event.keycode == KEY_ENTER:
button_pressed = false
confirmed.emit(_prev_event)
return
if event.is_pressed():
text = "%s..." % event.as_text()
_prev_event = event

View file

@ -0,0 +1,6 @@
[gd_scene load_steps=2 format=3 uid="uid://bvjxc2vyx35b1"]
[ext_resource type="Script" path="res://graph_node_renderer/shortcuts/shortcuts_editor.gd" id="1_hoas8"]
[node name="ShortcutsEditor" type="VBoxContainer"]
script = ExtResource("1_hoas8")

View file

@ -0,0 +1 @@
<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="M5 8a4 4 0 1 1 4 4v2a6 6 0 1 0-6-6H1l3 4 3-4z" fill="#e0e0e0"/></svg>

After

Width:  |  Height:  |  Size: 162 B

View file

@ -0,0 +1,37 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://bgsdcu3sdl8qt"
path="res://.godot/imported/revert-icon.svg-5ba4d0c812ba7bad2eceb424fb6b2d29.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://graph_node_renderer/textures/revert-icon.svg"
dest_files=["res://.godot/imported/revert-icon.svg-5ba4d0c812ba7bad2eceb424fb6b2d29.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1
svg/scale=1.0
editor/scale_with_editor_scale=false
editor/convert_colors_with_editor_theme=false

View file

@ -25,26 +25,6 @@ enabled=PackedStringArray("res://addons/no-obs-ws/plugin.cfg", "res://addons/no_
[input] [input]
group_nodes={
"deadzone": 0.5,
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":true,"meta_pressed":false,"pressed":false,"keycode":71,"physical_keycode":0,"key_label":0,"unicode":103,"echo":false,"script":null)
]
}
enter_group={
"deadzone": 0.5,
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194306,"key_label":0,"unicode":0,"echo":false,"script":null)
]
}
rename_node={
"deadzone": 0.5,
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":4194333,"physical_keycode":0,"key_label":0,"unicode":0,"echo":false,"script":null)
]
}
toggle_bottom_dock={
"deadzone": 0.5,
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":78,"key_label":0,"unicode":110,"echo":false,"script":null)
]
}
debug_make_unique={ debug_make_unique={
"deadzone": 0.5, "deadzone": 0.5,
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194335,"key_label":0,"unicode":0,"echo":false,"script":null) "events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194335,"key_label":0,"unicode":0,"echo":false,"script":null)