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
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
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.
|
||||
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)
|
||||
|
||||
|
||||
## 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 bottom_dock: BottomDock = %BottomDock
|
||||
@onready var sidebar_split: HSplitContainer = %SidebarSplit
|
||||
@onready var sidebar: Sidebar = %Sidebar as Sidebar
|
||||
|
||||
signal quit_completed()
|
||||
signal rpc_start_requested(port: int)
|
||||
|
@ -167,6 +169,16 @@ func _ready() -> void:
|
|||
get_active_deck_renderer().dirty = true
|
||||
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:
|
||||
|
@ -195,8 +207,13 @@ func _notification(what: int) -> void:
|
|||
|
||||
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):
|
||||
deck.variables_updated.disconnect(bottom_dock.rebuild_variable_tree)
|
||||
Util.safe_disconnect(deck.variables_updated, 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:
|
||||
|
@ -206,6 +223,30 @@ func _on_tab_container_tab_changed(tab: int) -> void:
|
|||
bottom_dock.rebuild_variable_tree(get_active_deck().variable_stack)
|
||||
var deck := get_active_deck()
|
||||
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]
|
||||
|
@ -595,6 +636,13 @@ func _unhandled_key_input(event: InputEvent) -> void:
|
|||
if RendererShortcuts.check_shortcut("toggle_bottom_dock", event):
|
||||
bottom_dock.visible = !bottom_dock.visible
|
||||
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:
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
|
||||
[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="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://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"]
|
||||
|
@ -10,6 +9,7 @@
|
|||
[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://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://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"]
|
||||
|
@ -24,7 +24,6 @@ anchor_right = 1.0
|
|||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
theme = ExtResource("1_tgul2")
|
||||
script = ExtResource("1_67g2g")
|
||||
DECK_SCENE = ExtResource("3_uf16c")
|
||||
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_bottom = 2
|
||||
|
||||
[node name="VSplitContainer" type="VSplitContainer" parent="MarginContainer"]
|
||||
[node name="SidebarSplit" type="HSplitContainer" parent="MarginContainer"]
|
||||
unique_name_in_owner = true
|
||||
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
|
||||
|
||||
[node name="VBoxContainer" type="VBoxContainer" parent="MarginContainer/VSplitContainer"]
|
||||
[node name="VBoxContainer" type="VBoxContainer" parent="MarginContainer/SidebarSplit/BottomSplit"]
|
||||
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
|
||||
|
||||
[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
|
||||
item_count = 8
|
||||
item_0/text = "New Deck"
|
||||
|
@ -75,7 +81,7 @@ item_7/text = "Recent Decks"
|
|||
item_7/id = 7
|
||||
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
|
||||
item_count = 4
|
||||
item_0/text = "Copy Node(s)"
|
||||
|
@ -87,7 +93,7 @@ item_2/id = 2
|
|||
item_3/text = "Settings..."
|
||||
item_3/id = 3
|
||||
|
||||
[node name="Connections" type="PopupMenu" parent="MarginContainer/VSplitContainer/VBoxContainer/MenuBar"]
|
||||
[node name="Connections" type="PopupMenu" parent="MarginContainer/SidebarSplit/BottomSplit/VBoxContainer/MenuBar"]
|
||||
unique_name_in_owner = true
|
||||
item_count = 3
|
||||
item_0/text = "OBS..."
|
||||
|
@ -97,7 +103,7 @@ item_1/id = 1
|
|||
item_2/text = "RPC Server..."
|
||||
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
|
||||
item_count = 3
|
||||
item_0/text = "Decks..."
|
||||
|
@ -109,7 +115,7 @@ item_2/checkable = 1
|
|||
item_2/checked = true
|
||||
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
|
||||
item_count = 2
|
||||
item_0/text = "Online Documentation"
|
||||
|
@ -117,11 +123,16 @@ item_0/id = 0
|
|||
item_1/text = "About..."
|
||||
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
|
||||
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
|
||||
visible = false
|
||||
layout_mode = 2
|
||||
|
@ -159,12 +170,12 @@ unique_name_in_owner = true
|
|||
[node name="SettingsDialog" parent="." instance=ExtResource("16_rktri")]
|
||||
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"]
|
||||
[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/File" to="." method="_on_file_id_pressed"]
|
||||
[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/SidebarSplit/BottomSplit/VBoxContainer/MenuBar/Edit" to="." method="_on_edit_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/SidebarSplit/BottomSplit/VBoxContainer/MenuBar/Debug" to="." method="_on_debug_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="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"]
|
||||
|
|
|
@ -69,6 +69,7 @@ func _ready() -> void:
|
|||
connection_request.connect(attempt_connection)
|
||||
disconnection_request.connect(attempt_disconnect)
|
||||
|
||||
|
||||
## Receives [signal GraphEdit.connection_request] and attempts to create a
|
||||
## 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:
|
||||
|
@ -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)
|
||||
|
||||
|
||||
## Receives [signal GraphEdit.disconnection_request] and attempts to disconnect the two [DeckNode]s
|
||||
## involved, utilizes [NodeDB] for accessing them.
|
||||
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
|
||||
|
||||
|
||||
## Returns the associated [DeckNodeRendererGraphNode] for the supplied [DeckNode].
|
||||
## Or [code]null[/code] if none is found.
|
||||
func get_node_renderer(node: DeckNode) -> DeckNodeRendererGraphNode:
|
||||
|
@ -122,6 +125,29 @@ func get_node_renderer(node: DeckNode) -> DeckNodeRendererGraphNode:
|
|||
|
||||
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]
|
||||
func _on_scroll_offset_changed(offset: Vector2) -> void:
|
||||
deck.set_meta("offset", offset)
|
||||
|
@ -176,6 +202,7 @@ func refresh_connections() -> void:
|
|||
to_node_port
|
||||
)
|
||||
|
||||
|
||||
## Connected to [signal Deck.node_added], used to instance the required
|
||||
## [DeckNodeRendererGraphNode] and set it's [member DeckNodeRenderGraphNode.position_offset]
|
||||
func _on_deck_node_added(node: DeckNode) -> void:
|
||||
|
@ -199,6 +226,7 @@ func _on_deck_node_removed(node: DeckNode) -> void:
|
|||
break
|
||||
dirty = true
|
||||
|
||||
|
||||
## Utility function that gets all [DeckNodeRenderGraphNode]s that are selected
|
||||
## See [member GraphNode.selected]
|
||||
func get_selected_nodes() -> Array:
|
||||
|
@ -207,6 +235,7 @@ func get_selected_nodes() -> Array:
|
|||
return x.selected
|
||||
)
|
||||
|
||||
|
||||
## Executes functionality based off hotkey inputs. Specifically handles creating groups
|
||||
## based off the action "group_nodes".
|
||||
func _gui_input(event: InputEvent) -> void:
|
||||
|
@ -259,6 +288,9 @@ func _gui_input(event: InputEvent) -> void:
|
|||
if RendererShortcuts.check_shortcut("paste_nodes", event):
|
||||
_on_paste_nodes_request()
|
||||
accept_event()
|
||||
|
||||
if RendererShortcuts.check_shortcut("focus_nodes", event):
|
||||
focus_selection()
|
||||
|
||||
|
||||
## 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:
|
||||
var node: DeckNodeRendererGraphNode = get_selected_nodes()[0]
|
||||
node.title = new_name
|
||||
#node.title = new_name
|
||||
node.node.rename(new_name)
|
||||
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]
|
||||
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 Yagich
|
||||
# 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
|
||||
port.value_callback = check_box.is_pressed
|
||||
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.custom_minimum_size = Vector2(200, 100)
|
||||
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 Yagich
|
||||
# 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
|
||||
var descriptor: Array
|
||||
var ignore_signal: bool = false
|
||||
|
||||
|
||||
const TYPE_ICONS := {
|
||||
DeckType.Types.BOOL: preload("res://graph_node_renderer/textures/type_bool.svg"),
|
||||
|
@ -18,20 +19,31 @@ const TYPE_ICONS := {
|
|||
|
||||
|
||||
@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(":")
|
||||
Util.safe_connect(port.value_updated,
|
||||
func(new_value: Variant):
|
||||
if ignore_signal:
|
||||
return
|
||||
set_value(new_value)
|
||||
)
|
||||
_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
|
||||
return
|
||||
|
||||
type_icon.modulate = DeckNodeRendererGraphNode.TYPE_COLORS[port.type]
|
||||
type_icon.tooltip_text = "Type: %s" % DeckType.type_str(port.type).to_lower()
|
||||
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)
|
||||
|
||||
|
||||
@warning_ignore("unused_parameter")
|
||||
func _setup(port: Port, node: DeckNode) -> void:
|
||||
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
|
||||
port.value_callback = line_edit.get_text
|
||||
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:
|
||||
box.select(i)
|
||||
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])
|
||||
port.value_callback = spin_box.get_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"}),
|
||||
"enter_group": create_shortcut_from_dict({"key": "tab"}),
|
||||
"rename_node": create_shortcut_from_dict({"key": "f2"}),
|
||||
"focus_nodes": create_shortcut_from_dict({"key": "f"}),
|
||||
|
||||
"_sep_nodes": "nodes",
|
||||
"copy_nodes": create_shortcut_from_dict({"ctrl": true, "key": "c"}),
|
||||
|
@ -62,6 +63,7 @@ static var defaults = {
|
|||
"_sep_misc": "misc",
|
||||
"settings": create_shortcut_from_dict({"ctrl": true, "key": "comma"}),
|
||||
"toggle_bottom_dock": create_shortcut_from_dict({"key": "n"}),
|
||||
"toggle_sidebar": create_shortcut_from_dict({"key": "t"}),
|
||||
}
|
||||
|
||||
|
||||
|
|
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")
|
||||
|
||||
[gui]
|
||||
|
||||
theme/custom="res://graph_node_renderer/default_theme.tres"
|
||||
|
||||
[input]
|
||||
|
||||
debug_make_unique={
|
||||
|
|
Loading…
Reference in a new issue