mirror of
https://codeberg.org/StreamGraph/StreamGraph.git
synced 2024-11-13 19:49:55 +01:00
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:
parent
a56280f00a
commit
ed5a0f427b
13 changed files with 501 additions and 110 deletions
|
@ -20,12 +20,6 @@ const PERSISTENCE_NAMESPACE := "default"
|
|||
## Reference to the [FileDialog] used for File operations through the program.
|
||||
@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 FileMenuId {
|
||||
NEW,
|
||||
|
@ -62,8 +56,12 @@ enum HelpMenuId {
|
|||
@onready var about_dialog: AcceptDialog = %AboutDialog
|
||||
|
||||
enum EditMenuId {
|
||||
COPY,
|
||||
PASTE,
|
||||
DUPLICATE,
|
||||
SETTINGS,
|
||||
}
|
||||
@onready var edit_popup_menu: PopupMenu = %Edit
|
||||
@onready var settings_dialog: SettingsDialog = %SettingsDialog
|
||||
|
||||
## Weak Reference to the Deck that is currently going to be saved.
|
||||
|
@ -105,6 +103,11 @@ func _ready() -> void:
|
|||
PERSISTENCE_NAMESPACE, "config",
|
||||
"recent_path", OS.get_system_dir(OS.SYSTEM_DIR_DOCUMENTS)
|
||||
)
|
||||
|
||||
RendererShortcuts.load_overrides()
|
||||
|
||||
reset_popup_menu_shortcuts()
|
||||
|
||||
add_recents_to_menu()
|
||||
|
||||
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)
|
||||
|
||||
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(
|
||||
func(old_name: String, new_name: String, new_value: Variant) -> void:
|
||||
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:
|
||||
var deck := get_deck_at_tab(previous_tab)
|
||||
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:
|
||||
EditMenuId.SETTINGS:
|
||||
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.
|
||||
|
@ -225,6 +269,7 @@ func add_empty_deck() -> void:
|
|||
tab_container.set_current_tab(tab)
|
||||
bottom_dock.variable_viewer.enable_new_button()
|
||||
|
||||
|
||||
## Closes the current tab in [member tab_container]
|
||||
func close_current_tab() -> void:
|
||||
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_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)
|
||||
|
||||
|
||||
|
@ -552,8 +591,8 @@ func _on_unsaved_changes_dialog_confirmed() -> void:
|
|||
quit_completed.emit()
|
||||
|
||||
|
||||
func _unhandled_input(event: InputEvent) -> void:
|
||||
if event.is_action_pressed("toggle_bottom_dock"):
|
||||
func _unhandled_key_input(event: InputEvent) -> void:
|
||||
if RendererShortcuts.check_shortcut("toggle_bottom_dock", event):
|
||||
bottom_dock.visible = !bottom_dock.visible
|
||||
accept_event()
|
||||
|
||||
|
@ -564,3 +603,4 @@ func _on_help_id_pressed(id: int) -> void:
|
|||
about_dialog.show()
|
||||
HelpMenuId.DOCS:
|
||||
OS.shell_open("https://codeberg.org/Eroax/StreamGraph/wiki")
|
||||
|
||||
|
|
|
@ -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="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://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"]
|
||||
layout_mode = 3
|
||||
anchors_preset = 15
|
||||
|
@ -75,11 +29,6 @@ script = ExtResource("1_67g2g")
|
|||
DECK_SCENE = ExtResource("3_uf16c")
|
||||
DEBUG_DECKS_LIST = ExtResource("4_ux0jt")
|
||||
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="."]
|
||||
layout_mode = 1
|
||||
|
@ -128,9 +77,15 @@ item_7/separator = true
|
|||
|
||||
[node name="Edit" type="PopupMenu" parent="MarginContainer/VSplitContainer/VBoxContainer/MenuBar"]
|
||||
unique_name_in_owner = true
|
||||
item_count = 1
|
||||
item_0/text = "Settings..."
|
||||
item_count = 4
|
||||
item_0/text = "Copy Node(s)"
|
||||
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"]
|
||||
unique_name_in_owner = true
|
||||
|
@ -205,6 +160,7 @@ 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="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/Connections" to="." method="_on_connections_id_pressed"]
|
||||
[connection signal="id_pressed" from="MarginContainer/VSplitContainer/VBoxContainer/MenuBar/Debug" to="." method="_on_debug_id_pressed"]
|
||||
|
|
|
@ -3,9 +3,9 @@
|
|||
[ext_resource type="Script" path="res://graph_node_renderer/deck_node_renderer_graph_node.gd" id="1_pos0w"]
|
||||
|
||||
[node name="DeckNodeRendererGraphNode" type="GraphNode"]
|
||||
script = ExtResource("1_pos0w")
|
||||
custom_minimum_size = Vector2(200, 0)
|
||||
offset_right = 200.0
|
||||
offset_bottom = 55.0
|
||||
resizable = true
|
||||
title = "Deck Node"
|
||||
script = ExtResource("1_pos0w")
|
||||
|
|
|
@ -210,7 +210,7 @@ func get_selected_nodes() -> Array:
|
|||
## Executes functionality based off hotkey inputs. Specifically handles creating groups
|
||||
## based off the action "group_nodes".
|
||||
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()
|
||||
var nodes = get_selected_nodes().map(
|
||||
func(x: DeckNodeRendererGraphNode):
|
||||
|
@ -218,7 +218,16 @@ func _gui_input(event: InputEvent) -> void:
|
|||
)
|
||||
deck.group_nodes(nodes)
|
||||
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:
|
||||
var node: DeckNode = get_selected_nodes().map(
|
||||
|
@ -230,12 +239,26 @@ func _gui_input(event: InputEvent) -> void:
|
|||
|
||||
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 pos := get_viewport_rect().position + get_global_mouse_position()
|
||||
rename_popup.popup_on_parent(Rect2i(pos, rename_popup_size))
|
||||
rename_popup.le.size.x = rename_popup_size.x
|
||||
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
|
||||
|
@ -244,11 +267,11 @@ func _input(event: InputEvent) -> void:
|
|||
if not has_focus():
|
||||
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":
|
||||
return
|
||||
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:
|
||||
|
@ -271,6 +294,7 @@ func _on_popup_request(p_popup_position: Vector2) -> void:
|
|||
add_node_menu.focus_search_bar()
|
||||
popup_position = p_popup_position
|
||||
|
||||
|
||||
## Connected to [signal AddNodeMenu.node_selected] and creates a [DeckNode] using
|
||||
## [method NodeDB.instance_node]. Then placing it at the [member scroll_offset] +
|
||||
## [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
|
||||
)[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)
|
||||
|
||||
|
||||
|
|
|
@ -20,9 +20,6 @@ script = ExtResource("1_pojfs")
|
|||
NODE_SCENE = ExtResource("2_67ymi")
|
||||
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="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="scroll_offset_changed" from="." to="." method="_on_scroll_offset_changed"]
|
||||
|
|
|
@ -6,7 +6,7 @@ class_name SettingsDialog
|
|||
|
||||
@onready var category_tree: Tree = %CategoryTree
|
||||
@onready var category_content: Control = %CategoryContent
|
||||
@onready var shortcuts_tree: Tree = %Shortcuts
|
||||
@onready var shortcuts_editor: ShortcutsEditor = %ShortcutsEditor
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
|
@ -21,5 +21,17 @@ func _ready() -> void:
|
|||
for i: Control in category_content.get_children():
|
||||
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)
|
||||
|
||||
|
||||
func _unhandled_key_input(event: InputEvent) -> void:
|
||||
if not visible:
|
||||
return
|
||||
|
||||
if event.keycode == KEY_ESCAPE and event.is_pressed():
|
||||
canceled.emit()
|
||||
hide()
|
||||
|
|
|
@ -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="PackedScene" uid="uid://bvjxc2vyx35b1" path="res://graph_node_renderer/shortcuts/shortcuts_editor.tscn" id="2_5tyfb"]
|
||||
|
||||
[node name="SettingsDialog" type="AcceptDialog"]
|
||||
title = "Settings"
|
||||
|
@ -8,6 +9,7 @@ initial_position = 1
|
|||
size = Vector2i(705, 370)
|
||||
min_size = Vector2i(500, 250)
|
||||
ok_button_text = "Close"
|
||||
dialog_close_on_escape = false
|
||||
script = ExtResource("1_lh25g")
|
||||
|
||||
[node name="HSplitContainer" type="HSplitContainer" parent="."]
|
||||
|
@ -44,8 +46,7 @@ text = "Library Group Search Paths"
|
|||
layout_mode = 2
|
||||
size_flags_vertical = 3
|
||||
|
||||
[node name="Shortcuts" type="Tree" parent="HSplitContainer/CategoryContent"]
|
||||
unique_name_in_owner = true
|
||||
[node name="Shortcuts" type="VBoxContainer" parent="HSplitContainer/CategoryContent"]
|
||||
visible = false
|
||||
layout_mode = 1
|
||||
anchors_preset = 15
|
||||
|
@ -53,4 +54,33 @@ anchor_right = 1.0
|
|||
anchor_bottom = 1.0
|
||||
grow_horizontal = 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."
|
||||
|
|
173
graph_node_renderer/shortcuts/renderer_shortcuts.gd
Normal file
173
graph_node_renderer/shortcuts/renderer_shortcuts.gd
Normal 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)
|
136
graph_node_renderer/shortcuts/shortcuts_editor.gd
Normal file
136
graph_node_renderer/shortcuts/shortcuts_editor.gd
Normal 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
|
||||
|
6
graph_node_renderer/shortcuts/shortcuts_editor.tscn
Normal file
6
graph_node_renderer/shortcuts/shortcuts_editor.tscn
Normal 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")
|
1
graph_node_renderer/textures/revert-icon.svg
Normal file
1
graph_node_renderer/textures/revert-icon.svg
Normal 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 |
37
graph_node_renderer/textures/revert-icon.svg.import
Normal file
37
graph_node_renderer/textures/revert-icon.svg.import
Normal 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
|
|
@ -25,26 +25,6 @@ enabled=PackedStringArray("res://addons/no-obs-ws/plugin.cfg", "res://addons/no_
|
|||
|
||||
[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={
|
||||
"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)
|
||||
|
|
Loading…
Reference in a new issue