mirror of
https://codeberg.org/StreamGraph/StreamGraph.git
synced 2024-11-13 19:49:55 +01:00
add a sidebar (#146)
Reviewed-on: https://codeberg.org/StreamGraph/StreamGraph/pulls/146 Co-authored-by: Lera Elvoé <yagich@poto.cafe> Co-committed-by: Lera Elvoé <yagich@poto.cafe>
This commit is contained in:
parent
ed5a0f427b
commit
1ce3cd0367
18 changed files with 733 additions and 28 deletions
|
@ -2,9 +2,59 @@
|
||||||
# (c) 2023-present Yagich
|
# (c) 2023-present Yagich
|
||||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||||
class_name Util
|
class_name Util
|
||||||
|
## A class with commonly used methods.
|
||||||
|
|
||||||
|
static var _batches: Dictionary # Dictionary[StringName, BatchConnection]
|
||||||
|
|
||||||
|
|
||||||
## Connects the [param p_func] to [param p_signal] if that connection doesn't already exist.
|
## Connects the [param p_func] to [param p_signal] if that connection doesn't already exist.
|
||||||
static func safe_connect(p_signal: Signal, p_func: Callable) -> void:
|
static func safe_connect(p_signal: Signal, p_func: Callable) -> void:
|
||||||
if not p_signal.is_connected(p_func):
|
if not p_signal.is_connected(p_func) and not p_signal.is_null() and p_func.is_valid():
|
||||||
p_signal.connect(p_func)
|
p_signal.connect(p_func)
|
||||||
|
|
||||||
|
|
||||||
|
## Disconnects the [param p_func] from [param p_signal] if that connection exists.
|
||||||
|
static func safe_disconnect(p_signal: Signal, p_func: Callable) -> void:
|
||||||
|
if p_signal.is_connected(p_func) and not p_signal.is_null() and p_func.is_valid():
|
||||||
|
p_signal.disconnect(p_func)
|
||||||
|
|
||||||
|
|
||||||
|
## Returns a new [Util.BatchConnection] object.
|
||||||
|
static func batch_begin() -> BatchConnection:
|
||||||
|
return BatchConnection.new()
|
||||||
|
|
||||||
|
|
||||||
|
## Adds the [param batch] object to storage, to be retrieved later with [param key].
|
||||||
|
static func push_batch(batch: BatchConnection, key: StringName) -> void:
|
||||||
|
_batches[key] = batch
|
||||||
|
|
||||||
|
|
||||||
|
## Disconnects the signals in a batch connection stored at [param key]. See [method push_batch].
|
||||||
|
static func pop_batch(key: StringName) -> void:
|
||||||
|
var b: BatchConnection = _batches.get(key, null)
|
||||||
|
if not b:
|
||||||
|
return
|
||||||
|
|
||||||
|
b._pop()
|
||||||
|
_batches.erase(key)
|
||||||
|
|
||||||
|
|
||||||
|
## An object representing multiple connections.
|
||||||
|
##
|
||||||
|
## Useful when there's a need to connect multiple signals in one operation, to be disconnected later.
|
||||||
|
class BatchConnection:
|
||||||
|
var _connections := {} # Dictionary[Signal, Array[Callable]]
|
||||||
|
|
||||||
|
|
||||||
|
## Add a new connection from [param p_signal] to [param p_func]. Uses [method Util.safe_connect].
|
||||||
|
func add(p_signal: Signal, p_func: Callable) -> void:
|
||||||
|
var arr: Array = _connections.get(p_signal, [])
|
||||||
|
arr.append(p_func)
|
||||||
|
_connections[p_signal] = arr
|
||||||
|
Util.safe_connect(p_signal, p_func)
|
||||||
|
|
||||||
|
|
||||||
|
func _pop() -> void:
|
||||||
|
for sig: Signal in _connections:
|
||||||
|
for f in _connections[sig]:
|
||||||
|
Util.safe_disconnect(sig, f)
|
||||||
|
|
|
@ -74,6 +74,8 @@ var _deck_to_save: WeakRef
|
||||||
@onready var rpc_setup_dialog := $RPCSetupDialog as RPCSetupDialog
|
@onready var rpc_setup_dialog := $RPCSetupDialog as RPCSetupDialog
|
||||||
|
|
||||||
@onready var bottom_dock: BottomDock = %BottomDock
|
@onready var bottom_dock: BottomDock = %BottomDock
|
||||||
|
@onready var sidebar_split: HSplitContainer = %SidebarSplit
|
||||||
|
@onready var sidebar: Sidebar = %Sidebar as Sidebar
|
||||||
|
|
||||||
signal quit_completed()
|
signal quit_completed()
|
||||||
signal rpc_start_requested(port: int)
|
signal rpc_start_requested(port: int)
|
||||||
|
@ -167,6 +169,16 @@ func _ready() -> void:
|
||||||
get_active_deck_renderer().dirty = true
|
get_active_deck_renderer().dirty = true
|
||||||
get_active_deck().remove_variable(field_name)
|
get_active_deck().remove_variable(field_name)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
sidebar_split.dragger_visibility = SplitContainer.DRAGGER_HIDDEN_COLLAPSED
|
||||||
|
sidebar.go_to_node_requested.connect(
|
||||||
|
func(node_id: String) -> void:
|
||||||
|
# we can reasonably assume its the current deck
|
||||||
|
var deck := get_active_deck()
|
||||||
|
var node := deck.get_node(node_id)
|
||||||
|
var deck_renderer := get_active_deck_renderer()
|
||||||
|
deck_renderer.focus_node(deck_renderer.get_node_renderer(node))
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
func reset_popup_menu_shortcuts() -> void:
|
func reset_popup_menu_shortcuts() -> void:
|
||||||
|
@ -195,8 +207,13 @@ func _notification(what: int) -> void:
|
||||||
|
|
||||||
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):
|
Util.safe_disconnect(deck.variables_updated, bottom_dock.rebuild_variable_tree)
|
||||||
deck.variables_updated.disconnect(bottom_dock.rebuild_variable_tree)
|
|
||||||
|
var deck_renderer := get_deck_renderer_at_tab(previous_tab)
|
||||||
|
Util.safe_disconnect(deck_renderer.node_selected, set_sidebar_node)
|
||||||
|
Util.safe_disconnect(deck_renderer.node_deselected, set_sidebar_node)
|
||||||
|
|
||||||
|
Util.pop_batch(&"sidebar_signals")
|
||||||
|
|
||||||
|
|
||||||
func _on_tab_container_tab_changed(tab: int) -> void:
|
func _on_tab_container_tab_changed(tab: int) -> void:
|
||||||
|
@ -206,6 +223,30 @@ func _on_tab_container_tab_changed(tab: int) -> void:
|
||||||
bottom_dock.rebuild_variable_tree(get_active_deck().variable_stack)
|
bottom_dock.rebuild_variable_tree(get_active_deck().variable_stack)
|
||||||
var deck := get_active_deck()
|
var deck := get_active_deck()
|
||||||
deck.variables_updated.connect(bottom_dock.rebuild_variable_tree.bind(deck.variable_stack))
|
deck.variables_updated.connect(bottom_dock.rebuild_variable_tree.bind(deck.variable_stack))
|
||||||
|
|
||||||
|
sidebar.set_edited_deck(deck.id)
|
||||||
|
get_active_deck_renderer().node_selected.connect(set_sidebar_node)
|
||||||
|
get_active_deck_renderer().node_deselected.connect(set_sidebar_node)
|
||||||
|
sidebar.refresh_node_list()
|
||||||
|
|
||||||
|
var batch := Util.batch_begin()
|
||||||
|
batch.add(deck.node_added, refresh_sidebar_node_list)
|
||||||
|
batch.add(deck.node_removed, refresh_sidebar_node_list)
|
||||||
|
Util.push_batch(batch, &"sidebar_signals")
|
||||||
|
|
||||||
|
|
||||||
|
func set_sidebar_node(_node: Node) -> void:
|
||||||
|
var count = get_active_deck_renderer().get_selected_nodes().size()
|
||||||
|
if count != 1:
|
||||||
|
sidebar.set_edited_node()
|
||||||
|
return
|
||||||
|
|
||||||
|
var dnode := (get_active_deck_renderer().get_selected_nodes()[0] as DeckNodeRendererGraphNode).node
|
||||||
|
sidebar.set_edited_node(dnode._id)
|
||||||
|
|
||||||
|
|
||||||
|
func refresh_sidebar_node_list(_unused1 = null, _unused2 = null, _unused3 = null) -> void:
|
||||||
|
sidebar.refresh_node_list()
|
||||||
|
|
||||||
|
|
||||||
## Called when the File button in the [MenuBar] is pressed with the [param id]
|
## Called when the File button in the [MenuBar] is pressed with the [param id]
|
||||||
|
@ -595,6 +636,13 @@ func _unhandled_key_input(event: InputEvent) -> void:
|
||||||
if RendererShortcuts.check_shortcut("toggle_bottom_dock", event):
|
if RendererShortcuts.check_shortcut("toggle_bottom_dock", event):
|
||||||
bottom_dock.visible = !bottom_dock.visible
|
bottom_dock.visible = !bottom_dock.visible
|
||||||
accept_event()
|
accept_event()
|
||||||
|
|
||||||
|
if RendererShortcuts.check_shortcut("toggle_sidebar", event):
|
||||||
|
sidebar.visible = !sidebar.visible
|
||||||
|
if sidebar.visible:
|
||||||
|
sidebar_split.dragger_visibility = SplitContainer.DRAGGER_VISIBLE
|
||||||
|
else:
|
||||||
|
sidebar_split.dragger_visibility = SplitContainer.DRAGGER_HIDDEN_COLLAPSED
|
||||||
|
|
||||||
|
|
||||||
func _on_help_id_pressed(id: int) -> void:
|
func _on_help_id_pressed(id: int) -> void:
|
||||||
|
|
|
@ -2,7 +2,6 @@
|
||||||
|
|
||||||
[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"]
|
||||||
[ext_resource type="Theme" uid="uid://dqqdqscid2iem" path="res://graph_node_renderer/default_theme.tres" id="1_tgul2"]
|
|
||||||
[ext_resource type="PackedScene" uid="uid://b18qpb48df14l" path="res://graph_node_renderer/deck_renderer_graph_edit.tscn" id="3_uf16c"]
|
[ext_resource type="PackedScene" uid="uid://b18qpb48df14l" path="res://graph_node_renderer/deck_renderer_graph_edit.tscn" id="3_uf16c"]
|
||||||
[ext_resource type="PackedScene" uid="uid://dayri1ejk20bc" path="res://graph_node_renderer/bottom_dock.tscn" id="4_gwnhy"]
|
[ext_resource type="PackedScene" uid="uid://dayri1ejk20bc" path="res://graph_node_renderer/bottom_dock.tscn" id="4_gwnhy"]
|
||||||
[ext_resource type="Script" path="res://addons/no-obs-ws/NoOBSWS.gd" id="4_nu72u"]
|
[ext_resource type="Script" path="res://addons/no-obs-ws/NoOBSWS.gd" id="4_nu72u"]
|
||||||
|
@ -10,6 +9,7 @@
|
||||||
[ext_resource type="Script" path="res://addons/no_twitch/twitch_connection.gd" id="5_3n36q"]
|
[ext_resource type="Script" path="res://addons/no_twitch/twitch_connection.gd" id="5_3n36q"]
|
||||||
[ext_resource type="PackedScene" uid="uid://cddfyvpf5nqq7" path="res://graph_node_renderer/debug_nodes_list.tscn" id="5_pnfg8"]
|
[ext_resource type="PackedScene" uid="uid://cddfyvpf5nqq7" path="res://graph_node_renderer/debug_nodes_list.tscn" id="5_pnfg8"]
|
||||||
[ext_resource type="PackedScene" uid="uid://eioso6jb42jy" path="res://graph_node_renderer/obs_websocket_setup_dialog.tscn" id="5_uo2gj"]
|
[ext_resource type="PackedScene" uid="uid://eioso6jb42jy" path="res://graph_node_renderer/obs_websocket_setup_dialog.tscn" id="5_uo2gj"]
|
||||||
|
[ext_resource type="PackedScene" uid="uid://b56hjad0ih0gu" path="res://graph_node_renderer/sidebar/sidebar.tscn" id="7_0lv5b"]
|
||||||
[ext_resource type="PackedScene" uid="uid://bq2lxmbnic4lc" path="res://graph_node_renderer/twitch_setup_dialog.tscn" id="7_7rhap"]
|
[ext_resource type="PackedScene" uid="uid://bq2lxmbnic4lc" path="res://graph_node_renderer/twitch_setup_dialog.tscn" id="7_7rhap"]
|
||||||
[ext_resource type="PackedScene" uid="uid://cuwou2aa7qfc2" path="res://graph_node_renderer/unsaved_changes_dialog_single_deck.tscn" id="8_qf6ve"]
|
[ext_resource type="PackedScene" uid="uid://cuwou2aa7qfc2" path="res://graph_node_renderer/unsaved_changes_dialog_single_deck.tscn" id="8_qf6ve"]
|
||||||
[ext_resource type="PackedScene" uid="uid://cvvkj138fg8jg" path="res://graph_node_renderer/unsaved_changes_dialog.tscn" id="9_4n0q6"]
|
[ext_resource type="PackedScene" uid="uid://cvvkj138fg8jg" path="res://graph_node_renderer/unsaved_changes_dialog.tscn" id="9_4n0q6"]
|
||||||
|
@ -24,7 +24,6 @@ anchor_right = 1.0
|
||||||
anchor_bottom = 1.0
|
anchor_bottom = 1.0
|
||||||
grow_horizontal = 2
|
grow_horizontal = 2
|
||||||
grow_vertical = 2
|
grow_vertical = 2
|
||||||
theme = ExtResource("1_tgul2")
|
|
||||||
script = ExtResource("1_67g2g")
|
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")
|
||||||
|
@ -42,17 +41,24 @@ theme_override_constants/margin_top = 2
|
||||||
theme_override_constants/margin_right = 2
|
theme_override_constants/margin_right = 2
|
||||||
theme_override_constants/margin_bottom = 2
|
theme_override_constants/margin_bottom = 2
|
||||||
|
|
||||||
[node name="VSplitContainer" type="VSplitContainer" parent="MarginContainer"]
|
[node name="SidebarSplit" type="HSplitContainer" parent="MarginContainer"]
|
||||||
|
unique_name_in_owner = true
|
||||||
layout_mode = 2
|
layout_mode = 2
|
||||||
|
theme_override_constants/separation = 1
|
||||||
|
split_offset = -270
|
||||||
|
|
||||||
|
[node name="BottomSplit" type="VSplitContainer" parent="MarginContainer/SidebarSplit"]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
split_offset = 460
|
split_offset = 460
|
||||||
|
|
||||||
[node name="VBoxContainer" type="VBoxContainer" parent="MarginContainer/VSplitContainer"]
|
[node name="VBoxContainer" type="VBoxContainer" parent="MarginContainer/SidebarSplit/BottomSplit"]
|
||||||
layout_mode = 2
|
layout_mode = 2
|
||||||
|
|
||||||
[node name="MenuBar" type="MenuBar" parent="MarginContainer/VSplitContainer/VBoxContainer"]
|
[node name="MenuBar" type="MenuBar" parent="MarginContainer/SidebarSplit/BottomSplit/VBoxContainer"]
|
||||||
layout_mode = 2
|
layout_mode = 2
|
||||||
|
|
||||||
[node name="File" type="PopupMenu" parent="MarginContainer/VSplitContainer/VBoxContainer/MenuBar"]
|
[node name="File" type="PopupMenu" parent="MarginContainer/SidebarSplit/BottomSplit/VBoxContainer/MenuBar"]
|
||||||
unique_name_in_owner = true
|
unique_name_in_owner = true
|
||||||
item_count = 8
|
item_count = 8
|
||||||
item_0/text = "New Deck"
|
item_0/text = "New Deck"
|
||||||
|
@ -75,7 +81,7 @@ item_7/text = "Recent Decks"
|
||||||
item_7/id = 7
|
item_7/id = 7
|
||||||
item_7/separator = true
|
item_7/separator = true
|
||||||
|
|
||||||
[node name="Edit" type="PopupMenu" parent="MarginContainer/VSplitContainer/VBoxContainer/MenuBar"]
|
[node name="Edit" type="PopupMenu" parent="MarginContainer/SidebarSplit/BottomSplit/VBoxContainer/MenuBar"]
|
||||||
unique_name_in_owner = true
|
unique_name_in_owner = true
|
||||||
item_count = 4
|
item_count = 4
|
||||||
item_0/text = "Copy Node(s)"
|
item_0/text = "Copy Node(s)"
|
||||||
|
@ -87,7 +93,7 @@ item_2/id = 2
|
||||||
item_3/text = "Settings..."
|
item_3/text = "Settings..."
|
||||||
item_3/id = 3
|
item_3/id = 3
|
||||||
|
|
||||||
[node name="Connections" type="PopupMenu" parent="MarginContainer/VSplitContainer/VBoxContainer/MenuBar"]
|
[node name="Connections" type="PopupMenu" parent="MarginContainer/SidebarSplit/BottomSplit/VBoxContainer/MenuBar"]
|
||||||
unique_name_in_owner = true
|
unique_name_in_owner = true
|
||||||
item_count = 3
|
item_count = 3
|
||||||
item_0/text = "OBS..."
|
item_0/text = "OBS..."
|
||||||
|
@ -97,7 +103,7 @@ item_1/id = 1
|
||||||
item_2/text = "RPC Server..."
|
item_2/text = "RPC Server..."
|
||||||
item_2/id = 2
|
item_2/id = 2
|
||||||
|
|
||||||
[node name="Debug" type="PopupMenu" parent="MarginContainer/VSplitContainer/VBoxContainer/MenuBar"]
|
[node name="Debug" type="PopupMenu" parent="MarginContainer/SidebarSplit/BottomSplit/VBoxContainer/MenuBar"]
|
||||||
unique_name_in_owner = true
|
unique_name_in_owner = true
|
||||||
item_count = 3
|
item_count = 3
|
||||||
item_0/text = "Decks..."
|
item_0/text = "Decks..."
|
||||||
|
@ -109,7 +115,7 @@ item_2/checkable = 1
|
||||||
item_2/checked = true
|
item_2/checked = true
|
||||||
item_2/id = 2
|
item_2/id = 2
|
||||||
|
|
||||||
[node name="Help" type="PopupMenu" parent="MarginContainer/VSplitContainer/VBoxContainer/MenuBar"]
|
[node name="Help" type="PopupMenu" parent="MarginContainer/SidebarSplit/BottomSplit/VBoxContainer/MenuBar"]
|
||||||
unique_name_in_owner = true
|
unique_name_in_owner = true
|
||||||
item_count = 2
|
item_count = 2
|
||||||
item_0/text = "Online Documentation"
|
item_0/text = "Online Documentation"
|
||||||
|
@ -117,11 +123,16 @@ item_0/id = 0
|
||||||
item_1/text = "About..."
|
item_1/text = "About..."
|
||||||
item_1/id = 1
|
item_1/id = 1
|
||||||
|
|
||||||
[node name="TabContainerCustom" parent="MarginContainer/VSplitContainer/VBoxContainer" instance=ExtResource("1_s3ug2")]
|
[node name="TabContainerCustom" parent="MarginContainer/SidebarSplit/BottomSplit/VBoxContainer" instance=ExtResource("1_s3ug2")]
|
||||||
unique_name_in_owner = true
|
unique_name_in_owner = true
|
||||||
layout_mode = 2
|
layout_mode = 2
|
||||||
|
|
||||||
[node name="BottomDock" parent="MarginContainer/VSplitContainer" instance=ExtResource("4_gwnhy")]
|
[node name="BottomDock" parent="MarginContainer/SidebarSplit/BottomSplit" instance=ExtResource("4_gwnhy")]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
visible = false
|
||||||
|
layout_mode = 2
|
||||||
|
|
||||||
|
[node name="Sidebar" parent="MarginContainer/SidebarSplit" instance=ExtResource("7_0lv5b")]
|
||||||
unique_name_in_owner = true
|
unique_name_in_owner = true
|
||||||
visible = false
|
visible = false
|
||||||
layout_mode = 2
|
layout_mode = 2
|
||||||
|
@ -159,12 +170,12 @@ unique_name_in_owner = true
|
||||||
[node name="SettingsDialog" parent="." instance=ExtResource("16_rktri")]
|
[node name="SettingsDialog" parent="." instance=ExtResource("16_rktri")]
|
||||||
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/SidebarSplit/BottomSplit/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="about_to_popup" from="MarginContainer/SidebarSplit/BottomSplit/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/SidebarSplit/BottomSplit/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/SidebarSplit/BottomSplit/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/SidebarSplit/BottomSplit/VBoxContainer/MenuBar/Debug" to="." method="_on_debug_id_pressed"]
|
||||||
[connection signal="id_pressed" from="MarginContainer/VSplitContainer/VBoxContainer/MenuBar/Help" to="." method="_on_help_id_pressed"]
|
[connection signal="id_pressed" from="MarginContainer/SidebarSplit/BottomSplit/VBoxContainer/MenuBar/Help" to="." method="_on_help_id_pressed"]
|
||||||
[connection signal="connect_button_pressed" from="OBSWebsocketSetupDialog" to="." method="_on_obs_websocket_setup_dialog_connect_button_pressed"]
|
[connection signal="connect_button_pressed" from="OBSWebsocketSetupDialog" to="." method="_on_obs_websocket_setup_dialog_connect_button_pressed"]
|
||||||
[connection signal="confirmed" from="UnsavedChangesDialogSingleDeck" to="." method="_on_unsaved_changes_dialog_single_deck_confirmed"]
|
[connection signal="confirmed" from="UnsavedChangesDialogSingleDeck" to="." method="_on_unsaved_changes_dialog_single_deck_confirmed"]
|
||||||
[connection signal="custom_action" from="UnsavedChangesDialogSingleDeck" to="." method="_on_unsaved_changes_dialog_single_deck_custom_action"]
|
[connection signal="custom_action" from="UnsavedChangesDialogSingleDeck" to="." method="_on_unsaved_changes_dialog_single_deck_custom_action"]
|
||||||
|
|
|
@ -69,6 +69,7 @@ func _ready() -> void:
|
||||||
connection_request.connect(attempt_connection)
|
connection_request.connect(attempt_connection)
|
||||||
disconnection_request.connect(attempt_disconnect)
|
disconnection_request.connect(attempt_disconnect)
|
||||||
|
|
||||||
|
|
||||||
## Receives [signal GraphEdit.connection_request] and attempts to create a
|
## Receives [signal GraphEdit.connection_request] and attempts to create a
|
||||||
## connection between the two [DeckNode]s involved, utilizes [NodeDB] for accessing them.
|
## connection between the two [DeckNode]s involved, utilizes [NodeDB] for accessing them.
|
||||||
func attempt_connection(from_node_name: StringName, from_port: int, to_node_name: StringName, to_port: int) -> void:
|
func attempt_connection(from_node_name: StringName, from_port: int, to_node_name: StringName, to_port: int) -> void:
|
||||||
|
@ -94,6 +95,7 @@ func _is_node_hover_valid(from_node: StringName, from_port: int, to_node: String
|
||||||
|
|
||||||
return deck.is_valid_connection(from_node_renderer.node._id, to_node_renderer.node._id, from_port, to_port)
|
return deck.is_valid_connection(from_node_renderer.node._id, to_node_renderer.node._id, from_port, to_port)
|
||||||
|
|
||||||
|
|
||||||
## Receives [signal GraphEdit.disconnection_request] and attempts to disconnect the two [DeckNode]s
|
## Receives [signal GraphEdit.disconnection_request] and attempts to disconnect the two [DeckNode]s
|
||||||
## involved, utilizes [NodeDB] for accessing them.
|
## involved, utilizes [NodeDB] for accessing them.
|
||||||
func attempt_disconnect(from_node_name: StringName, from_port: int, to_node_name: StringName, to_port: int) -> void:
|
func attempt_disconnect(from_node_name: StringName, from_port: int, to_node_name: StringName, to_port: int) -> void:
|
||||||
|
@ -113,6 +115,7 @@ func attempt_disconnect(from_node_name: StringName, from_port: int, to_node_name
|
||||||
)
|
)
|
||||||
dirty = true
|
dirty = true
|
||||||
|
|
||||||
|
|
||||||
## Returns the associated [DeckNodeRendererGraphNode] for the supplied [DeckNode].
|
## Returns the associated [DeckNodeRendererGraphNode] for the supplied [DeckNode].
|
||||||
## Or [code]null[/code] if none is found.
|
## Or [code]null[/code] if none is found.
|
||||||
func get_node_renderer(node: DeckNode) -> DeckNodeRendererGraphNode:
|
func get_node_renderer(node: DeckNode) -> DeckNodeRendererGraphNode:
|
||||||
|
@ -122,6 +125,29 @@ func get_node_renderer(node: DeckNode) -> DeckNodeRendererGraphNode:
|
||||||
|
|
||||||
return null
|
return null
|
||||||
|
|
||||||
|
|
||||||
|
func focus_node(node: DeckNodeRendererGraphNode) -> void:
|
||||||
|
set_selected(node)
|
||||||
|
var t := create_tween()
|
||||||
|
t.tween_property(self, ^"scroll_offset", (node.position_offset + node.size / 2.0) * zoom - size / 2.0, 0.3).set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_IN_OUT)
|
||||||
|
|
||||||
|
|
||||||
|
func focus_selection() -> void:
|
||||||
|
var nodes := get_selected_nodes()
|
||||||
|
if nodes.size() == 0:
|
||||||
|
return
|
||||||
|
|
||||||
|
var midpoint := Vector2()
|
||||||
|
for i in nodes:
|
||||||
|
midpoint += i.position_offset + i.size / 2.0
|
||||||
|
|
||||||
|
midpoint *= zoom
|
||||||
|
midpoint /= nodes.size()
|
||||||
|
midpoint -= size / 2.0
|
||||||
|
var t := create_tween()
|
||||||
|
t.tween_property(self, ^"scroll_offset", midpoint, 0.3).set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_IN_OUT)
|
||||||
|
|
||||||
|
|
||||||
## Updates [member Deck]s meta property "offset" whenever [member GraphEdit.scroll_offset]
|
## Updates [member Deck]s meta property "offset" whenever [member GraphEdit.scroll_offset]
|
||||||
func _on_scroll_offset_changed(offset: Vector2) -> void:
|
func _on_scroll_offset_changed(offset: Vector2) -> void:
|
||||||
deck.set_meta("offset", offset)
|
deck.set_meta("offset", offset)
|
||||||
|
@ -176,6 +202,7 @@ func refresh_connections() -> void:
|
||||||
to_node_port
|
to_node_port
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
## Connected to [signal Deck.node_added], used to instance the required
|
## Connected to [signal Deck.node_added], used to instance the required
|
||||||
## [DeckNodeRendererGraphNode] and set it's [member DeckNodeRenderGraphNode.position_offset]
|
## [DeckNodeRendererGraphNode] and set it's [member DeckNodeRenderGraphNode.position_offset]
|
||||||
func _on_deck_node_added(node: DeckNode) -> void:
|
func _on_deck_node_added(node: DeckNode) -> void:
|
||||||
|
@ -199,6 +226,7 @@ func _on_deck_node_removed(node: DeckNode) -> void:
|
||||||
break
|
break
|
||||||
dirty = true
|
dirty = true
|
||||||
|
|
||||||
|
|
||||||
## Utility function that gets all [DeckNodeRenderGraphNode]s that are selected
|
## Utility function that gets all [DeckNodeRenderGraphNode]s that are selected
|
||||||
## See [member GraphNode.selected]
|
## See [member GraphNode.selected]
|
||||||
func get_selected_nodes() -> Array:
|
func get_selected_nodes() -> Array:
|
||||||
|
@ -207,6 +235,7 @@ func get_selected_nodes() -> Array:
|
||||||
return x.selected
|
return x.selected
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
## 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:
|
||||||
|
@ -259,6 +288,9 @@ func _gui_input(event: InputEvent) -> void:
|
||||||
if RendererShortcuts.check_shortcut("paste_nodes", event):
|
if RendererShortcuts.check_shortcut("paste_nodes", event):
|
||||||
_on_paste_nodes_request()
|
_on_paste_nodes_request()
|
||||||
accept_event()
|
accept_event()
|
||||||
|
|
||||||
|
if RendererShortcuts.check_shortcut("focus_nodes", event):
|
||||||
|
focus_selection()
|
||||||
|
|
||||||
|
|
||||||
## Handles entering groups with action "enter_group". Done here to bypass neighbor
|
## Handles entering groups with action "enter_group". Done here to bypass neighbor
|
||||||
|
@ -276,7 +308,7 @@ func _input(event: InputEvent) -> void:
|
||||||
|
|
||||||
func _on_rename_popup_rename_confirmed(new_name: String) -> void:
|
func _on_rename_popup_rename_confirmed(new_name: String) -> void:
|
||||||
var node: DeckNodeRendererGraphNode = get_selected_nodes()[0]
|
var node: DeckNodeRendererGraphNode = get_selected_nodes()[0]
|
||||||
node.title = new_name
|
#node.title = new_name
|
||||||
node.node.rename(new_name)
|
node.node.rename(new_name)
|
||||||
dirty = true
|
dirty = true
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,45 @@
|
||||||
[gd_resource type="Theme" format=3 uid="uid://dqqdqscid2iem"]
|
[gd_resource type="Theme" load_steps=5 format=3 uid="uid://dqqdqscid2iem"]
|
||||||
|
|
||||||
|
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_dt61u"]
|
||||||
|
content_margin_left = 4.0
|
||||||
|
content_margin_top = 4.0
|
||||||
|
content_margin_right = 4.0
|
||||||
|
content_margin_bottom = 4.0
|
||||||
|
bg_color = Color(0.384314, 0.384314, 0.384314, 0.835294)
|
||||||
|
corner_radius_top_left = 4
|
||||||
|
corner_radius_top_right = 4
|
||||||
|
corner_radius_bottom_right = 4
|
||||||
|
corner_radius_bottom_left = 4
|
||||||
|
|
||||||
|
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_fmm5o"]
|
||||||
|
content_margin_left = 4.0
|
||||||
|
content_margin_top = 4.0
|
||||||
|
content_margin_right = 4.0
|
||||||
|
content_margin_bottom = 4.0
|
||||||
|
bg_color = Color(0.247059, 0.247059, 0.247059, 0.835294)
|
||||||
|
corner_radius_top_left = 4
|
||||||
|
corner_radius_top_right = 4
|
||||||
|
corner_radius_bottom_right = 4
|
||||||
|
corner_radius_bottom_left = 4
|
||||||
|
|
||||||
|
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_5q5t0"]
|
||||||
|
content_margin_left = 4.0
|
||||||
|
content_margin_top = 4.0
|
||||||
|
content_margin_right = 4.0
|
||||||
|
content_margin_bottom = 4.0
|
||||||
|
bg_color = Color(0.247059, 0.247059, 0.247059, 0.835294)
|
||||||
|
corner_radius_top_left = 4
|
||||||
|
corner_radius_top_right = 4
|
||||||
|
corner_radius_bottom_right = 4
|
||||||
|
corner_radius_bottom_left = 4
|
||||||
|
|
||||||
|
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_q56mu"]
|
||||||
|
bg_color = Color(0.623529, 0.564706, 0.909804, 0.0588235)
|
||||||
|
|
||||||
[resource]
|
[resource]
|
||||||
default_font_size = 14
|
default_font_size = 14
|
||||||
|
AccordionButton/base_type = &"Button"
|
||||||
|
AccordionButton/styles/hover = SubResource("StyleBoxFlat_dt61u")
|
||||||
|
AccordionButton/styles/normal = SubResource("StyleBoxFlat_fmm5o")
|
||||||
|
AccordionButton/styles/pressed = SubResource("StyleBoxFlat_5q5t0")
|
||||||
|
AccordionMenu/styles/background = SubResource("StyleBoxFlat_q56mu")
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
# meta-description: An empty template with StreamGraph license header.
|
|
||||||
# (c) 2023-present Eroax
|
# (c) 2023-present Eroax
|
||||||
# (c) 2023-present Yagich
|
# (c) 2023-present Yagich
|
||||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||||
|
|
|
@ -14,3 +14,10 @@ func _setup(port: Port, _node: DeckNode) -> void:
|
||||||
check_box.text = port.label
|
check_box.text = port.label
|
||||||
port.value_callback = check_box.is_pressed
|
port.value_callback = check_box.is_pressed
|
||||||
check_box.toggled.connect(port.set_value)
|
check_box.toggled.connect(port.set_value)
|
||||||
|
|
||||||
|
|
||||||
|
func set_value(new_value: Variant) -> void:
|
||||||
|
if check_box.has_focus():
|
||||||
|
return
|
||||||
|
else:
|
||||||
|
check_box.button_pressed = bool(new_value)
|
||||||
|
|
|
@ -14,3 +14,10 @@ func _setup(port: Port, _node: DeckNode) -> void:
|
||||||
code_edit.text_changed.connect(port.set_value.bind(code_edit.get_text))
|
code_edit.text_changed.connect(port.set_value.bind(code_edit.get_text))
|
||||||
code_edit.custom_minimum_size = Vector2(200, 100)
|
code_edit.custom_minimum_size = Vector2(200, 100)
|
||||||
code_edit.size_flags_vertical = SIZE_EXPAND_FILL
|
code_edit.size_flags_vertical = SIZE_EXPAND_FILL
|
||||||
|
|
||||||
|
|
||||||
|
func set_value(new_value: Variant) -> void:
|
||||||
|
if code_edit.has_focus():
|
||||||
|
return
|
||||||
|
|
||||||
|
code_edit.text = new_value
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
# meta-description: An empty template with StreamGraph license header.
|
|
||||||
# (c) 2023-present Eroax
|
# (c) 2023-present Eroax
|
||||||
# (c) 2023-present Yagich
|
# (c) 2023-present Yagich
|
||||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||||
|
@ -7,6 +6,8 @@ class_name DescriptorContainer
|
||||||
|
|
||||||
@onready var type_icon: TextureRect = %TypeIcon
|
@onready var type_icon: TextureRect = %TypeIcon
|
||||||
var descriptor: Array
|
var descriptor: Array
|
||||||
|
var ignore_signal: bool = false
|
||||||
|
|
||||||
|
|
||||||
const TYPE_ICONS := {
|
const TYPE_ICONS := {
|
||||||
DeckType.Types.BOOL: preload("res://graph_node_renderer/textures/type_bool.svg"),
|
DeckType.Types.BOOL: preload("res://graph_node_renderer/textures/type_bool.svg"),
|
||||||
|
@ -18,20 +19,31 @@ const TYPE_ICONS := {
|
||||||
|
|
||||||
|
|
||||||
@warning_ignore("unused_parameter")
|
@warning_ignore("unused_parameter")
|
||||||
func set_up_from_port(port: Port, node: DeckNode) -> void:
|
func set_up_from_port(port: Port, node: DeckNode, in_node: bool = true) -> void:
|
||||||
descriptor = port.descriptor.split(":")
|
descriptor = port.descriptor.split(":")
|
||||||
|
Util.safe_connect(port.value_updated,
|
||||||
|
func(new_value: Variant):
|
||||||
|
if ignore_signal:
|
||||||
|
return
|
||||||
|
set_value(new_value)
|
||||||
|
)
|
||||||
_setup(port, node)
|
_setup(port, node)
|
||||||
if port.port_type == DeckNode.PortType.VIRTUAL or port.type == DeckType.Types.ANY:
|
if (port.port_type == DeckNode.PortType.VIRTUAL or port.type == DeckType.Types.ANY) or not in_node:
|
||||||
type_icon.visible = false
|
type_icon.visible = false
|
||||||
return
|
return
|
||||||
|
|
||||||
type_icon.modulate = DeckNodeRendererGraphNode.TYPE_COLORS[port.type]
|
type_icon.modulate = DeckNodeRendererGraphNode.TYPE_COLORS[port.type]
|
||||||
type_icon.tooltip_text = "Type: %s" % DeckType.type_str(port.type).to_lower()
|
type_icon.tooltip_text = "Type: %s" % DeckType.type_str(port.type).to_lower()
|
||||||
type_icon.texture = TYPE_ICONS[port.type]
|
type_icon.texture = TYPE_ICONS[port.type]
|
||||||
if port.port_type == DeckNode.PortType.OUTPUT:
|
if port.port_type == DeckNode.PortType.OUTPUT and in_node:
|
||||||
move_child(type_icon, -1)
|
move_child(type_icon, -1)
|
||||||
|
|
||||||
|
|
||||||
@warning_ignore("unused_parameter")
|
@warning_ignore("unused_parameter")
|
||||||
func _setup(port: Port, node: DeckNode) -> void:
|
func _setup(port: Port, node: DeckNode) -> void:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
@warning_ignore("unused_parameter")
|
||||||
|
func set_value(new_value: Variant) -> void:
|
||||||
|
pass
|
||||||
|
|
|
@ -12,3 +12,10 @@ func _setup(port: Port, _node: DeckNode) -> void:
|
||||||
line_edit.placeholder_text = port.label
|
line_edit.placeholder_text = port.label
|
||||||
port.value_callback = line_edit.get_text
|
port.value_callback = line_edit.get_text
|
||||||
line_edit.text_changed.connect(port.set_value)
|
line_edit.text_changed.connect(port.set_value)
|
||||||
|
|
||||||
|
|
||||||
|
func set_value(new_value: Variant) -> void:
|
||||||
|
if line_edit.has_focus():
|
||||||
|
return
|
||||||
|
|
||||||
|
line_edit.text = new_value
|
||||||
|
|
|
@ -27,3 +27,13 @@ func _setup(port: Port, _node: DeckNode) -> void:
|
||||||
if box.get_item_text(i) == port.value:
|
if box.get_item_text(i) == port.value:
|
||||||
box.select(i)
|
box.select(i)
|
||||||
break
|
break
|
||||||
|
|
||||||
|
|
||||||
|
func set_value(new_value: Variant) -> void:
|
||||||
|
if box.has_focus():
|
||||||
|
return
|
||||||
|
|
||||||
|
for i in box.item_count:
|
||||||
|
if box.get_item_text(i) == new_value:
|
||||||
|
box.select(i)
|
||||||
|
break
|
||||||
|
|
|
@ -26,3 +26,10 @@ func _setup(port: Port, _node: DeckNode) -> void:
|
||||||
spin_box.step = float(descriptor[4])
|
spin_box.step = float(descriptor[4])
|
||||||
port.value_callback = spin_box.get_value
|
port.value_callback = spin_box.get_value
|
||||||
spin_box.value_changed.connect(port.set_value)
|
spin_box.value_changed.connect(port.set_value)
|
||||||
|
|
||||||
|
|
||||||
|
func set_value(new_value: Variant) -> void:
|
||||||
|
if spin_box.has_focus():
|
||||||
|
return
|
||||||
|
|
||||||
|
spin_box.set_value_no_signal(new_value)
|
||||||
|
|
|
@ -52,6 +52,7 @@ static var defaults = {
|
||||||
"group_nodes": create_shortcut_from_dict({"ctrl": true, "key": "g"}),
|
"group_nodes": create_shortcut_from_dict({"ctrl": true, "key": "g"}),
|
||||||
"enter_group": create_shortcut_from_dict({"key": "tab"}),
|
"enter_group": create_shortcut_from_dict({"key": "tab"}),
|
||||||
"rename_node": create_shortcut_from_dict({"key": "f2"}),
|
"rename_node": create_shortcut_from_dict({"key": "f2"}),
|
||||||
|
"focus_nodes": create_shortcut_from_dict({"key": "f"}),
|
||||||
|
|
||||||
"_sep_nodes": "nodes",
|
"_sep_nodes": "nodes",
|
||||||
"copy_nodes": create_shortcut_from_dict({"ctrl": true, "key": "c"}),
|
"copy_nodes": create_shortcut_from_dict({"ctrl": true, "key": "c"}),
|
||||||
|
@ -62,6 +63,7 @@ static var defaults = {
|
||||||
"_sep_misc": "misc",
|
"_sep_misc": "misc",
|
||||||
"settings": create_shortcut_from_dict({"ctrl": true, "key": "comma"}),
|
"settings": create_shortcut_from_dict({"ctrl": true, "key": "comma"}),
|
||||||
"toggle_bottom_dock": create_shortcut_from_dict({"key": "n"}),
|
"toggle_bottom_dock": create_shortcut_from_dict({"key": "n"}),
|
||||||
|
"toggle_sidebar": create_shortcut_from_dict({"key": "t"}),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
152
graph_node_renderer/sidebar/accordion_menu.gd
Normal file
152
graph_node_renderer/sidebar/accordion_menu.gd
Normal file
|
@ -0,0 +1,152 @@
|
||||||
|
# (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)
|
||||||
|
@tool
|
||||||
|
extends VBoxContainer
|
||||||
|
class_name AccordionMenu
|
||||||
|
## A collapsible menu container.
|
||||||
|
##
|
||||||
|
## Provides a container that arranges its children vertically with a button to collapse
|
||||||
|
## and uncollapse its children. The name of the node controls the collapse button's text.
|
||||||
|
|
||||||
|
## Controls whether children are visible.
|
||||||
|
@export var collapsed: bool = false:
|
||||||
|
set = set_collapsed
|
||||||
|
|
||||||
|
## If [code]true[/code], the menu will draw a background under its children,
|
||||||
|
## which can be helpful for telling nested elements apart.[br]
|
||||||
|
## The stylebox for drawing this background can be defined in the [Theme],
|
||||||
|
## which is called [code]background[/code].
|
||||||
|
@export var draw_background: bool = false:
|
||||||
|
set(v):
|
||||||
|
draw_background = v
|
||||||
|
queue_redraw()
|
||||||
|
|
||||||
|
## If [code]true[/code], the menu will draw lines pointing to child [Control]s,
|
||||||
|
## similar to [Tree].
|
||||||
|
@export var draw_tree: bool = true:
|
||||||
|
set(v):
|
||||||
|
draw_tree = v
|
||||||
|
queue_redraw()
|
||||||
|
|
||||||
|
var _collapse_button: CollapseButton
|
||||||
|
const COLLAPSE_ICON = preload("res://graph_node_renderer/textures/collapse-icon.svg")
|
||||||
|
const COLLAPSE_ICON_COLLAPSED = preload("res://graph_node_renderer/textures/collapse-icon-collapsed.svg")
|
||||||
|
const BASE_MARGIN := 12
|
||||||
|
var _indent_level := 1
|
||||||
|
|
||||||
|
|
||||||
|
func _enter_tree() -> void:
|
||||||
|
if get_child_count(true) > 0 and get_child(0, true) is CollapseButton:
|
||||||
|
get_child(0, true).queue_free()
|
||||||
|
|
||||||
|
_collapse_button = CollapseButton.new(collapsed, name)
|
||||||
|
_collapse_button.toggled.connect(set_collapsed)
|
||||||
|
|
||||||
|
renamed.connect(
|
||||||
|
func():
|
||||||
|
_collapse_button.text = name
|
||||||
|
)
|
||||||
|
|
||||||
|
add_child(_collapse_button, false, Node.INTERNAL_MODE_FRONT)
|
||||||
|
|
||||||
|
|
||||||
|
func _exit_tree() -> void:
|
||||||
|
_collapse_button.queue_free()
|
||||||
|
|
||||||
|
|
||||||
|
func set_collapsed(v: bool) -> void:
|
||||||
|
collapsed = v
|
||||||
|
if _collapse_button:
|
||||||
|
_collapse_button.button_pressed = collapsed
|
||||||
|
if collapsed:
|
||||||
|
await collapse()
|
||||||
|
else:
|
||||||
|
await uncollapse()
|
||||||
|
queue_redraw()
|
||||||
|
|
||||||
|
|
||||||
|
## Collapse the menu, making the children invisible.
|
||||||
|
func collapse() -> void:
|
||||||
|
if _collapse_button:
|
||||||
|
_collapse_button.icon = COLLAPSE_ICON_COLLAPSED
|
||||||
|
for child in get_children(false):
|
||||||
|
child.visible = false
|
||||||
|
if is_inside_tree():
|
||||||
|
await get_tree().process_frame
|
||||||
|
update_minimum_size()
|
||||||
|
|
||||||
|
|
||||||
|
## Uncollapse the menu, making the children visible.
|
||||||
|
func uncollapse() -> void:
|
||||||
|
if _collapse_button:
|
||||||
|
_collapse_button.icon = COLLAPSE_ICON
|
||||||
|
for child in get_children(false):
|
||||||
|
child.visible = true
|
||||||
|
if is_inside_tree():
|
||||||
|
await get_tree().process_frame
|
||||||
|
update_minimum_size()
|
||||||
|
|
||||||
|
|
||||||
|
func _notification(what: int) -> void:
|
||||||
|
if what == NOTIFICATION_PRE_SORT_CHILDREN:
|
||||||
|
for i in get_children(false):
|
||||||
|
i.visible = not collapsed
|
||||||
|
|
||||||
|
if what == NOTIFICATION_SORT_CHILDREN:
|
||||||
|
for child: Control in get_children(false):
|
||||||
|
var base_rect := child.get_rect()
|
||||||
|
base_rect.size.x -= BASE_MARGIN * _indent_level
|
||||||
|
base_rect.position.x += BASE_MARGIN * _indent_level
|
||||||
|
fit_child_in_rect(child, base_rect)
|
||||||
|
|
||||||
|
|
||||||
|
func _draw() -> void:
|
||||||
|
if draw_tree and not collapsed and get_child_count() > 0:
|
||||||
|
# draw a tree-like line.
|
||||||
|
var line_color := Color(1.0, 1.0, 1.0, 0.35)
|
||||||
|
# first, draw a single vertical line
|
||||||
|
var line_start_x := _collapse_button.position.x + BASE_MARGIN / 2.0
|
||||||
|
var tree_line_start := Vector2(line_start_x, _collapse_button.position.y + _collapse_button.size.y)
|
||||||
|
var tree_line_end := tree_line_start
|
||||||
|
#tree_line_end.y = get_rect().size.y
|
||||||
|
tree_line_end.y = get_combined_minimum_size().y
|
||||||
|
tree_line_end.y -= get_child(-1, false).size.y / 2.0
|
||||||
|
|
||||||
|
if get_child(-1, false) is AccordionMenu:
|
||||||
|
var other: AccordionMenu = get_child(-1, false) as AccordionMenu
|
||||||
|
tree_line_end.y -= other.size.y / 2
|
||||||
|
tree_line_end.y += other._collapse_button.size.y / 2.0
|
||||||
|
|
||||||
|
draw_line(tree_line_start, tree_line_end, line_color)
|
||||||
|
|
||||||
|
# draw lines going out to each child
|
||||||
|
for child in get_children(false):
|
||||||
|
# special case for if the child is also an accordion:
|
||||||
|
# draw the line pointing to the header
|
||||||
|
if child is AccordionMenu:
|
||||||
|
var start := Vector2(line_start_x, child.position.y + child._collapse_button.size.y / 2)
|
||||||
|
var end := start + Vector2(BASE_MARGIN / 2.0, 0.0)
|
||||||
|
draw_line(start, end, line_color)
|
||||||
|
else:
|
||||||
|
var start := Vector2(line_start_x, child.position.y + child.size.y / 2)
|
||||||
|
var end := start + Vector2(BASE_MARGIN / 2.0, 0.0)
|
||||||
|
draw_line(start, end, line_color)
|
||||||
|
|
||||||
|
if draw_background:
|
||||||
|
var sb := get_theme_stylebox(&"background", &"AccordionMenu")
|
||||||
|
var button_height := _collapse_button.get_rect().size.y
|
||||||
|
var rect := Rect2(_collapse_button.position, size)
|
||||||
|
rect.size.y -= button_height
|
||||||
|
rect.position.y += button_height
|
||||||
|
draw_style_box(sb, rect)
|
||||||
|
|
||||||
|
|
||||||
|
class CollapseButton extends Button:
|
||||||
|
func _init(p_collapsed: bool = false, p_text: String = "") -> void:
|
||||||
|
text = p_text
|
||||||
|
icon = COLLAPSE_ICON_COLLAPSED if p_collapsed else COLLAPSE_ICON
|
||||||
|
toggle_mode = true
|
||||||
|
button_pressed = p_collapsed
|
||||||
|
alignment = HORIZONTAL_ALIGNMENT_LEFT
|
||||||
|
theme_type_variation = &"AccordionButton"
|
268
graph_node_renderer/sidebar/sidebar.gd
Normal file
268
graph_node_renderer/sidebar/sidebar.gd
Normal file
|
@ -0,0 +1,268 @@
|
||||||
|
# (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 PanelContainer
|
||||||
|
class_name Sidebar
|
||||||
|
|
||||||
|
@onready var deck_menu: AccordionMenu = %Deck
|
||||||
|
@onready var node_menu: AccordionMenu = %Node
|
||||||
|
@onready var node_list_menu: AccordionMenu = %"Node List"
|
||||||
|
|
||||||
|
const SYSTEM_CODE_FONT = preload("res://graph_node_renderer/system_code_font.tres")
|
||||||
|
|
||||||
|
var edited_deck_id: String
|
||||||
|
var edited_node_id: String
|
||||||
|
|
||||||
|
var deck_inspector: DeckInspector
|
||||||
|
var node_inspector: NodeInspector
|
||||||
|
|
||||||
|
signal go_to_node_requested(node_id: String)
|
||||||
|
|
||||||
|
|
||||||
|
func _ready() -> void:
|
||||||
|
set_edited_deck()
|
||||||
|
|
||||||
|
|
||||||
|
func set_edited_deck(id: String = "") -> void:
|
||||||
|
if edited_deck_id == id and not id.is_empty():
|
||||||
|
return
|
||||||
|
|
||||||
|
edited_deck_id = id
|
||||||
|
set_edited_node("")
|
||||||
|
|
||||||
|
for i in deck_menu.get_children():
|
||||||
|
i.queue_free()
|
||||||
|
|
||||||
|
if id.is_empty():
|
||||||
|
var label := Label.new()
|
||||||
|
label.autowrap_mode = TextServer.AUTOWRAP_WORD
|
||||||
|
label.text = "There is no open Deck. Open or create one to edit its properties."
|
||||||
|
deck_menu.add_child(label)
|
||||||
|
deck_inspector = null
|
||||||
|
return
|
||||||
|
|
||||||
|
deck_inspector = DeckInspector.new(id)
|
||||||
|
for i in deck_inspector.nodes:
|
||||||
|
deck_menu.add_child(i)
|
||||||
|
|
||||||
|
|
||||||
|
func refresh_node_list(_unused = null) -> void:
|
||||||
|
for i in node_list_menu.get_children():
|
||||||
|
i.queue_free()
|
||||||
|
|
||||||
|
for node_id: String in DeckHolder.get_deck(edited_deck_id).nodes:
|
||||||
|
var node := DeckHolder.get_deck(edited_deck_id).get_node(node_id)
|
||||||
|
var b := Button.new()
|
||||||
|
b.flat = true
|
||||||
|
b.text = "\"%s\"" % node.name
|
||||||
|
b.pressed.connect(
|
||||||
|
func():
|
||||||
|
go_to_node_requested.emit(node._id)
|
||||||
|
)
|
||||||
|
b.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||||
|
b.tooltip_text = "Click to focus this node in the graph"
|
||||||
|
b.alignment = HORIZONTAL_ALIGNMENT_LEFT
|
||||||
|
b.clip_text = true
|
||||||
|
|
||||||
|
var cb := Button.new()
|
||||||
|
cb.text = "Copy ID"
|
||||||
|
cb.pressed.connect(
|
||||||
|
func():
|
||||||
|
DisplayServer.clipboard_set(node._id)
|
||||||
|
)
|
||||||
|
#Util.safe_connect(node.renamed,
|
||||||
|
#func(new_name: String):
|
||||||
|
# TODO: bad. if the node (deck node or button, whichever)
|
||||||
|
# gets removed the captures get invalidated.
|
||||||
|
# maybe dont use lambda here
|
||||||
|
#b.text = "\"%s\"" % node.name
|
||||||
|
#)
|
||||||
|
#Util.safe_connect(node.renamed, _set_button_text.bind(b))
|
||||||
|
# this is probably bad too, but its the only one that worked so far.
|
||||||
|
Util.safe_connect(node.renamed, refresh_node_list)
|
||||||
|
var hb := HBoxContainer.new()
|
||||||
|
hb.add_child(b)
|
||||||
|
hb.add_child(cb)
|
||||||
|
node_list_menu.add_child(hb)
|
||||||
|
|
||||||
|
|
||||||
|
#func _set_button_text(new_text: String, button: Button) -> void:
|
||||||
|
# this was also bad. "Cannot convert argument 2 from Object to Object" 🙃
|
||||||
|
#button.text = "\"%s\"" % new_text
|
||||||
|
|
||||||
|
|
||||||
|
func set_edited_node(id: String = "") -> void:
|
||||||
|
if edited_node_id == id and not id.is_empty():
|
||||||
|
return
|
||||||
|
|
||||||
|
edited_node_id = id
|
||||||
|
|
||||||
|
for i in node_menu.get_children():
|
||||||
|
i.queue_free()
|
||||||
|
|
||||||
|
if id.is_empty():
|
||||||
|
var label := Label.new()
|
||||||
|
label.autowrap_mode = TextServer.AUTOWRAP_WORD
|
||||||
|
label.text = "There is no Node selected (or multiple are selected). Select a single Node to edit its properties."
|
||||||
|
node_menu.add_child(label)
|
||||||
|
node_inspector = null
|
||||||
|
return
|
||||||
|
|
||||||
|
await get_tree().process_frame
|
||||||
|
|
||||||
|
node_inspector = NodeInspector.new(edited_deck_id, id)
|
||||||
|
node_inspector.go_to_node_requested.connect(
|
||||||
|
func():
|
||||||
|
go_to_node_requested.emit(edited_node_id)
|
||||||
|
)
|
||||||
|
for i in node_inspector.nodes:
|
||||||
|
node_menu.add_child(i)
|
||||||
|
|
||||||
|
DeckHolder.get_deck(edited_deck_id).node_removed.connect(
|
||||||
|
func(node: DeckNode):
|
||||||
|
if node._id == edited_node_id:
|
||||||
|
set_edited_node()
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class Inspector extends HBoxContainer:
|
||||||
|
func _init(text: String, editor: Control = null) -> void:
|
||||||
|
var _label = Label.new()
|
||||||
|
_label.text = text
|
||||||
|
_label.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||||
|
|
||||||
|
add_child(_label)
|
||||||
|
if editor:
|
||||||
|
editor.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||||
|
add_child(editor)
|
||||||
|
|
||||||
|
|
||||||
|
class DeckInspector:
|
||||||
|
var ref: WeakRef
|
||||||
|
var nodes: Array[Control]
|
||||||
|
|
||||||
|
|
||||||
|
func add_inspector(text: String, control: Control = null) -> void:
|
||||||
|
nodes.append(Inspector.new(text, control))
|
||||||
|
|
||||||
|
|
||||||
|
func _init(id: String) -> void:
|
||||||
|
ref = weakref(DeckHolder.get_deck(id))
|
||||||
|
var deck: Deck = ref.get_ref() as Deck
|
||||||
|
if deck.is_group:
|
||||||
|
add_inspector("This deck is a group.")
|
||||||
|
|
||||||
|
|
||||||
|
class NodeInspector:
|
||||||
|
var ref: WeakRef
|
||||||
|
var nodes: Array[Control]
|
||||||
|
|
||||||
|
signal go_to_node_requested()
|
||||||
|
|
||||||
|
var DESCRIPTOR_SCENES := {
|
||||||
|
"button": load("res://graph_node_renderer/descriptors/button_descriptor.tscn"),
|
||||||
|
"field": load("res://graph_node_renderer/descriptors/field_descriptor.tscn"),
|
||||||
|
"singlechoice": load("res://graph_node_renderer/descriptors/single_choice_descriptor.tscn"),
|
||||||
|
"codeblock": load("res://graph_node_renderer/descriptors/codeblock_descriptor.tscn"),
|
||||||
|
"checkbox": load("res://graph_node_renderer/descriptors/check_box_descriptor.tscn"),
|
||||||
|
"spinbox": load("res://graph_node_renderer/descriptors/spin_box_descriptor.tscn"),
|
||||||
|
"label": load("res://graph_node_renderer/descriptors/label_descriptor.tscn"),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func add_inspector(text: String, control: Control = null) -> void:
|
||||||
|
nodes.append(Inspector.new(text, control))
|
||||||
|
|
||||||
|
|
||||||
|
func create_label(text: String) -> Label:
|
||||||
|
var l := Label.new()
|
||||||
|
l.text = text
|
||||||
|
l.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||||
|
return l
|
||||||
|
|
||||||
|
|
||||||
|
func _init(deck_id: String, id: String) -> void:
|
||||||
|
ref = weakref(DeckHolder.get_deck(deck_id).get_node(id))
|
||||||
|
var node: DeckNode = ref.get_ref() as DeckNode
|
||||||
|
|
||||||
|
var type_label := create_label(node.node_type)
|
||||||
|
var type_label_settings := LabelSettings.new()
|
||||||
|
type_label_settings.font = SYSTEM_CODE_FONT
|
||||||
|
type_label_settings.font_size = 14
|
||||||
|
type_label.label_settings = type_label_settings
|
||||||
|
var copy_button := Button.new()
|
||||||
|
copy_button.text = "Copy"
|
||||||
|
copy_button.pressed.connect(
|
||||||
|
func():
|
||||||
|
DisplayServer.clipboard_set(node.node_type)
|
||||||
|
)
|
||||||
|
var hb := HBoxContainer.new()
|
||||||
|
hb.add_child(type_label)
|
||||||
|
hb.add_child(copy_button)
|
||||||
|
add_inspector("Node Type:", hb)
|
||||||
|
|
||||||
|
var name_field := LineEdit.new()
|
||||||
|
name_field.placeholder_text = "Node name"
|
||||||
|
name_field.text = node.name
|
||||||
|
name_field.text_changed.connect(node.rename)
|
||||||
|
node.renamed.connect(
|
||||||
|
func(new_name: String) -> void:
|
||||||
|
if name_field.has_focus():
|
||||||
|
return
|
||||||
|
|
||||||
|
name_field.text = new_name
|
||||||
|
)
|
||||||
|
add_inspector("Node name:", name_field)
|
||||||
|
|
||||||
|
var focus_button := Button.new()
|
||||||
|
focus_button.text = "Focus node"
|
||||||
|
focus_button.pressed.connect(
|
||||||
|
func():
|
||||||
|
go_to_node_requested.emit()
|
||||||
|
)
|
||||||
|
focus_button.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||||
|
nodes.append(focus_button)
|
||||||
|
|
||||||
|
add_port_menu(node.get_input_ports(), node)
|
||||||
|
add_port_menu(node.get_output_ports(), node)
|
||||||
|
|
||||||
|
|
||||||
|
func add_port_menu(ports: Array[Port], node: DeckNode) -> void:
|
||||||
|
if ports.is_empty():
|
||||||
|
return
|
||||||
|
var ports_menu := AccordionMenu.new()
|
||||||
|
ports_menu.draw_background = true
|
||||||
|
var is_output := ports[0].port_type == DeckNode.PortType.OUTPUT
|
||||||
|
ports_menu.set_name.call_deferred("Output Ports" if is_output else "Input Ports")
|
||||||
|
for port in ports:
|
||||||
|
var acc := AccordionMenu.new()
|
||||||
|
acc.draw_background = true
|
||||||
|
acc.name = "Port %s" % port.index_of_type
|
||||||
|
|
||||||
|
var label_label := create_label("Name: %s" % port.label)
|
||||||
|
acc.add_child(label_label)
|
||||||
|
var type_label := create_label("Type: %s" % DeckType.type_str(port.type).to_lower())
|
||||||
|
acc.add_child(type_label)
|
||||||
|
var usage_is_both := port.usage_type == Port.UsageType.BOTH
|
||||||
|
var usage_text = "Both (Value Request or Trigger)" if usage_is_both else Port.UsageType.keys()[port.usage_type].capitalize()
|
||||||
|
var usage_label := create_label("Usage: %s" % usage_text)
|
||||||
|
acc.add_child(usage_label)
|
||||||
|
|
||||||
|
var descriptor_split := port.descriptor.split(":")
|
||||||
|
if DESCRIPTOR_SCENES.has(descriptor_split[0]):
|
||||||
|
var port_hb := HBoxContainer.new()
|
||||||
|
var value_label := create_label("Value:")
|
||||||
|
port_hb.add_child(value_label)
|
||||||
|
var desc: DescriptorContainer = DESCRIPTOR_SCENES[descriptor_split[0]].instantiate()
|
||||||
|
|
||||||
|
desc.ready.connect(
|
||||||
|
func():
|
||||||
|
desc.set_up_from_port(port, node, false)
|
||||||
|
)
|
||||||
|
port_hb.add_child(desc)
|
||||||
|
|
||||||
|
acc.add_child(port_hb)
|
||||||
|
|
||||||
|
ports_menu.add_child(acc)
|
||||||
|
ports_menu.collapsed = true
|
||||||
|
nodes.append(ports_menu)
|
44
graph_node_renderer/sidebar/sidebar.tscn
Normal file
44
graph_node_renderer/sidebar/sidebar.tscn
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
[gd_scene load_steps=3 format=3 uid="uid://b56hjad0ih0gu"]
|
||||||
|
|
||||||
|
[ext_resource type="Script" path="res://graph_node_renderer/sidebar/sidebar.gd" id="1_bcym7"]
|
||||||
|
[ext_resource type="Script" path="res://graph_node_renderer/sidebar/accordion_menu.gd" id="1_q1gqb"]
|
||||||
|
|
||||||
|
[node name="Sidebar" type="PanelContainer"]
|
||||||
|
offset_right = 439.0
|
||||||
|
offset_bottom = 266.0
|
||||||
|
script = ExtResource("1_bcym7")
|
||||||
|
|
||||||
|
[node name="MarginContainer" type="MarginContainer" parent="."]
|
||||||
|
layout_mode = 2
|
||||||
|
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="ScrollContainer" type="ScrollContainer" parent="MarginContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
horizontal_scroll_mode = 0
|
||||||
|
|
||||||
|
[node name="VBoxContainer" type="VBoxContainer" parent="MarginContainer/ScrollContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
|
||||||
|
[node name="Label" type="Label" parent="MarginContainer/ScrollContainer/VBoxContainer"]
|
||||||
|
layout_mode = 2
|
||||||
|
text = "Inspector"
|
||||||
|
|
||||||
|
[node name="Deck" type="VBoxContainer" parent="MarginContainer/ScrollContainer/VBoxContainer"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
layout_mode = 2
|
||||||
|
script = ExtResource("1_q1gqb")
|
||||||
|
|
||||||
|
[node name="Node" type="VBoxContainer" parent="MarginContainer/ScrollContainer/VBoxContainer"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
layout_mode = 2
|
||||||
|
script = ExtResource("1_q1gqb")
|
||||||
|
|
||||||
|
[node name="Node List" type="VBoxContainer" parent="MarginContainer/ScrollContainer/VBoxContainer"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_vertical = 3
|
||||||
|
script = ExtResource("1_q1gqb")
|
4
graph_node_renderer/system_code_font.tres
Normal file
4
graph_node_renderer/system_code_font.tres
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
[gd_resource type="SystemFont" format=3 uid="uid://borqsrevyoygv"]
|
||||||
|
|
||||||
|
[resource]
|
||||||
|
font_names = PackedStringArray("Monospace")
|
|
@ -23,6 +23,10 @@ config/icon="res://dist/logo-flattened.svg"
|
||||||
|
|
||||||
enabled=PackedStringArray("res://addons/no-obs-ws/plugin.cfg", "res://addons/no_twitch/plugin.cfg")
|
enabled=PackedStringArray("res://addons/no-obs-ws/plugin.cfg", "res://addons/no_twitch/plugin.cfg")
|
||||||
|
|
||||||
|
[gui]
|
||||||
|
|
||||||
|
theme/custom="res://graph_node_renderer/default_theme.tres"
|
||||||
|
|
||||||
[input]
|
[input]
|
||||||
|
|
||||||
debug_make_unique={
|
debug_make_unique={
|
||||||
|
|
Loading…
Reference in a new issue