mirror of
https://codeberg.org/StreamGraph/StreamGraph.git
synced 2024-11-13 19:49:55 +01:00
f720efcc72
saving decks that use lib groups will no longer save the whole file path to that library (at the expense of the structure needing to be the same) also some ui/ux improvements: - more menus in sidebar remember their collapsed state between deck/node switches - adding a lib group will name the group node appropriately - save dialog properly remembers the most recent path when invoked via ctrl+s and the deck hasn't been saved before Reviewed-on: https://codeberg.org/StreamGraph/StreamGraph/pulls/162 Co-authored-by: Lera Elvoé <yagich@poto.cafe> Co-committed-by: Lera Elvoé <yagich@poto.cafe>
695 lines
25 KiB
GDScript
695 lines
25 KiB
GDScript
# (c) 2023-present Eroax
|
|
# (c) 2023-present Yagich
|
|
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
|
extends Control
|
|
class_name DeckHolderRenderer
|
|
|
|
## Renderer class for [DeckHolder]
|
|
##
|
|
## Entry point for the [GraphEdit] based Renderer
|
|
|
|
## Reference to the base scene for [DeckRendererGraphEdit]
|
|
@export var DECK_SCENE: PackedScene
|
|
@export var DEBUG_DECKS_LIST: PackedScene
|
|
@export var DEBUG_NODES_LIST: PackedScene
|
|
|
|
const PERSISTENCE_NAMESPACE := "default"
|
|
|
|
## Reference to the main windows [TabContainerCustom]
|
|
@onready var tab_container := %TabContainerCustom as TabContainerCustom
|
|
## Reference to the [FileDialog] used for File operations through the program.
|
|
@onready var file_dialog: FileDialog = $FileDialog
|
|
|
|
## Enum for storing the Options in the "File" PopupMenu.
|
|
enum FileMenuId {
|
|
NEW,
|
|
OPEN,
|
|
SAVE = 3,
|
|
SAVE_AS,
|
|
CLOSE = 6,
|
|
RECENTS,
|
|
}
|
|
@onready var file_popup_menu: PopupMenu = %File as PopupMenu
|
|
var max_recents := 4
|
|
var recent_files := []
|
|
var recent_path: String
|
|
@onready var unsaved_changes_dialog_single_deck := $UnsavedChangesDialogSingleDeck as UnsavedChangesDialogSingleDeck
|
|
@onready var unsaved_changes_dialog: ConfirmationDialog = $UnsavedChangesDialog
|
|
|
|
enum ConnectionsMenuId {
|
|
OBS,
|
|
TWITCH,
|
|
RPC,
|
|
}
|
|
@onready var connections_popup_menu: PopupMenu = %Connections
|
|
|
|
enum DebugMenuId {
|
|
DECKS,
|
|
NODES,
|
|
EMBED_SUBWINDOWS,
|
|
}
|
|
@onready var debug_popup_menu: PopupMenu = %Debug
|
|
|
|
enum HelpMenuId {
|
|
DOCS,
|
|
ABOUT,
|
|
}
|
|
@onready var about_dialog: AcceptDialog = %AboutDialog
|
|
|
|
enum EditMenuId {
|
|
COPY,
|
|
PASTE,
|
|
DUPLICATE,
|
|
SETTINGS,
|
|
}
|
|
@onready var edit_popup_menu: PopupMenu = %Edit
|
|
@onready var settings_dialog: SettingsDialog = %SettingsDialog
|
|
|
|
## Weak Reference to the Deck that is currently going to be saved.
|
|
var _deck_to_save: WeakRef
|
|
|
|
@onready var no_obsws := %NoOBSWS as NoOBSWS
|
|
|
|
@onready var obs_setup_dialog := $OBSWebsocketSetupDialog as OBSWebsocketSetupDialog
|
|
@onready var twitch_setup_dialog := $Twitch_Setup_Dialog as TwitchSetupDialog
|
|
|
|
@onready var bottom_dock: BottomDock = %BottomDock
|
|
@onready var sidebar_split: HSplitContainer = %SidebarSplit
|
|
@onready var sidebar: Sidebar = %Sidebar as Sidebar
|
|
|
|
@onready var compat_dialog: ConfirmationDialog = %CompatDialog
|
|
|
|
signal quit_completed()
|
|
|
|
|
|
func _ready() -> void:
|
|
tab_container.add_button_pressed.connect(add_empty_deck)
|
|
tab_container.tab_changed.connect(_on_tab_container_tab_changed)
|
|
tab_container.tab_about_to_change.connect(_on_tab_container_tab_about_to_change)
|
|
RendererPersistence.init_namespace(PERSISTENCE_NAMESPACE)
|
|
|
|
var embed_subwindows: bool = RendererPersistence.get_or_create(PERSISTENCE_NAMESPACE, "config", "embed_subwindows", true)
|
|
debug_popup_menu.set_item_checked(
|
|
DebugMenuId.EMBED_SUBWINDOWS,
|
|
embed_subwindows
|
|
)
|
|
|
|
get_tree().get_root().gui_embed_subwindows = embed_subwindows
|
|
file_dialog.use_native_dialog = not embed_subwindows
|
|
|
|
recent_files = RendererPersistence.get_or_create(
|
|
PERSISTENCE_NAMESPACE, "config",
|
|
"recent_files", []
|
|
)
|
|
recent_path = RendererPersistence.get_or_create(
|
|
PERSISTENCE_NAMESPACE, "config",
|
|
"recent_path", OS.get_system_dir(OS.SYSTEM_DIR_DOCUMENTS)
|
|
)
|
|
|
|
RendererShortcuts.load_overrides()
|
|
|
|
reset_popup_menu_shortcuts()
|
|
|
|
add_recents_to_menu()
|
|
|
|
tab_container.tab_close_requested.connect(request_tab_close)
|
|
|
|
file_dialog.canceled.connect(disconnect_file_dialog_signals)
|
|
Connections.obs_websocket = no_obsws
|
|
Connections.twitch = %Twitch_Connection
|
|
|
|
no_obsws.event_received.connect(
|
|
func(m: NoOBSWS.Message):
|
|
Connections._obs_event_received(m.get_data())
|
|
)
|
|
|
|
Connections.twitch.chat_received_rich.connect(Connections._twitch_chat_received)
|
|
|
|
bottom_dock.variable_viewer.top_field_edited.connect(
|
|
func(old_name: String, new_name: String, new_value: Variant) -> void:
|
|
get_active_deck_renderer().dirty = true
|
|
get_active_deck().update_variable(old_name, new_name, new_value)
|
|
)
|
|
|
|
bottom_dock.variable_viewer.top_field_removed.connect(
|
|
func(field_name: String) -> 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))
|
|
)
|
|
|
|
connections_popup_menu.set_item_tooltip(ConnectionsMenuId.RPC, "RPC support has been removed and will return in a future version.")
|
|
|
|
|
|
func reset_popup_menu_shortcuts() -> void:
|
|
file_popup_menu.set_item_shortcut(FileMenuId.NEW, RendererShortcuts.get_shortcut("new_deck"))
|
|
file_popup_menu.set_item_shortcut(FileMenuId.OPEN, RendererShortcuts.get_shortcut("open_deck"))
|
|
file_popup_menu.set_item_shortcut(FileMenuId.SAVE, RendererShortcuts.get_shortcut("save_deck"))
|
|
file_popup_menu.set_item_shortcut(FileMenuId.SAVE_AS, RendererShortcuts.get_shortcut("save_deck_as"))
|
|
file_popup_menu.set_item_shortcut(FileMenuId.CLOSE, RendererShortcuts.get_shortcut("close_deck"))
|
|
|
|
edit_popup_menu.set_item_shortcut(EditMenuId.COPY, RendererShortcuts.get_shortcut("copy_nodes"))
|
|
edit_popup_menu.set_item_shortcut(EditMenuId.PASTE, RendererShortcuts.get_shortcut("paste_nodes"))
|
|
edit_popup_menu.set_item_shortcut(EditMenuId.DUPLICATE, RendererShortcuts.get_shortcut("duplicate_nodes"))
|
|
edit_popup_menu.set_item_shortcut(EditMenuId.SETTINGS, RendererShortcuts.get_shortcut("settings"))
|
|
|
|
|
|
func reset_recents_shortcuts() -> void:
|
|
for i in recent_files.size():
|
|
var s := RendererShortcuts.get_shortcut("open_recent_deck_%s" % [i + 1])
|
|
file_popup_menu.set_item_shortcut(file_popup_menu.get_item_count() - recent_files.size() + i, s)
|
|
|
|
|
|
func _notification(what: int) -> void:
|
|
if what == RendererShortcuts.NOTIFICATION_SHORTCUTS_UPDATED:
|
|
reset_popup_menu_shortcuts()
|
|
|
|
|
|
func _on_tab_container_tab_about_to_change(previous_tab: int) -> void:
|
|
var deck := get_deck_at_tab(previous_tab)
|
|
if deck == null:
|
|
sidebar.set_edited_deck()
|
|
return
|
|
|
|
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:
|
|
var deck := get_active_deck()
|
|
if deck == null:
|
|
sidebar.set_edited_deck()
|
|
return
|
|
var is_group = tab_container.get_tab_metadata(tab, "group", false)
|
|
file_popup_menu.set_item_disabled(FileMenuId.SAVE, is_group)
|
|
file_popup_menu.set_item_disabled(FileMenuId.SAVE_AS, is_group)
|
|
bottom_dock.rebuild_variable_tree(get_active_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)
|
|
#get_active_deck_renderer().nodes_about_to_delete.connect(sidebar.set_edited_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)
|
|
#batch.add(get_active_deck_renderer().nodes_about_to_delete, sidebar.set_edited_node)
|
|
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]
|
|
## of the button within it that was pressed.
|
|
func _on_file_id_pressed(id: int) -> void:
|
|
match id:
|
|
FileMenuId.NEW:
|
|
add_empty_deck()
|
|
FileMenuId.OPEN:
|
|
open_open_dialog(recent_path)
|
|
FileMenuId.SAVE when tab_container.get_tab_count() > 0:
|
|
save_active_deck()
|
|
FileMenuId.SAVE_AS when tab_container.get_tab_count() > 0:
|
|
open_save_dialog(tab_container.get_tab_metadata(tab_container.get_current_tab(), "path"))
|
|
FileMenuId.CLOSE:
|
|
request_tab_close(tab_container.get_current_tab())
|
|
_ when id in range(FileMenuId.RECENTS, FileMenuId.RECENTS + max_recents + 1):
|
|
open_deck_at_path(recent_files[id - FileMenuId.RECENTS - 1])
|
|
|
|
|
|
func _on_edit_id_pressed(id: int) -> void:
|
|
match id:
|
|
EditMenuId.SETTINGS:
|
|
settings_dialog.popup_centered()
|
|
EditMenuId.COPY:
|
|
var r := get_active_deck_renderer()
|
|
r._on_copy_nodes_request()
|
|
EditMenuId.PASTE:
|
|
var r := get_active_deck_renderer()
|
|
r._on_paste_nodes_request()
|
|
EditMenuId.DUPLICATE:
|
|
var r := get_active_deck_renderer()
|
|
r._on_duplicate_nodes_request()
|
|
|
|
|
|
func _on_edit_about_to_popup() -> void:
|
|
# enable/disable the copy/paste buttons depending on if there's a deck renderer and if it has nodes selected
|
|
if tab_container.get_tab_count() == 0:
|
|
edit_popup_menu.set_item_disabled(EditMenuId.COPY, true)
|
|
edit_popup_menu.set_item_disabled(EditMenuId.PASTE, true)
|
|
edit_popup_menu.set_item_disabled(EditMenuId.DUPLICATE, true)
|
|
return
|
|
|
|
edit_popup_menu.set_item_disabled(EditMenuId.PASTE, false)
|
|
var r := get_active_deck_renderer()
|
|
edit_popup_menu.set_item_disabled(EditMenuId.COPY, r.get_selected_nodes().size() == 0)
|
|
edit_popup_menu.set_item_disabled(EditMenuId.DUPLICATE, r.get_selected_nodes().size() == 0)
|
|
|
|
|
|
## Adds an empty [DeckRendererGraphEdit] with a corresponding [Deck] for it's data.
|
|
func add_empty_deck() -> void:
|
|
var deck := DeckHolder.add_empty_deck()
|
|
var inst: DeckRendererGraphEdit = DECK_SCENE.instantiate()
|
|
inst.deck = deck
|
|
var tab := tab_container.add_content(inst, "<unsaved deck>")
|
|
tab_container.set_tab_metadata(tab, "id", deck.id)
|
|
tab_container.set_tab_metadata(tab, "group", false)
|
|
tab_container.set_tab_metadata(tab, "path", recent_path.path_join(""))
|
|
inst.group_enter_requested.connect(_on_deck_renderer_group_enter_requested)
|
|
inst.dirty_state_changed.connect(_on_deck_renderer_dirty_state_changed.bind(inst))
|
|
tab_container.set_current_tab(tab)
|
|
bottom_dock.variable_viewer.enable_new_button()
|
|
|
|
|
|
## Closes the current tab in [member tab_container]
|
|
func close_current_tab() -> void:
|
|
tab_container.close_tab(tab_container.get_current_tab())
|
|
|
|
|
|
func request_tab_close(tab: int):
|
|
if tab_container.get_tab_metadata(tab, "dirty") and not tab_container.get_tab_metadata(tab, "group"):
|
|
unsaved_changes_dialog_single_deck.set_meta("tab", tab)
|
|
unsaved_changes_dialog_single_deck.show()
|
|
return
|
|
await close_tab(tab)
|
|
|
|
|
|
func close_tab(tab: int) -> void:
|
|
if not tab_container.get_tab_metadata(tab, "group"):
|
|
var groups := DeckHolder.close_deck(tab_container.get_tab_metadata(tab, "id"))
|
|
# close tabs associated with this deck's groups
|
|
for group in groups:
|
|
for c_tab in range(tab_container.get_tab_count() - 1, -1, -1):
|
|
if tab_container.get_tab_metadata(c_tab, "id") == group:
|
|
tab_container.close_tab(c_tab)
|
|
await get_tree().process_frame
|
|
|
|
tab_container.close_tab(tab)
|
|
var tc = tab_container.get_tab_count()
|
|
if tab_container.get_tab_count() == 0:
|
|
bottom_dock.variable_viewer.disable_new_button()
|
|
sidebar.set_edited_deck()
|
|
|
|
|
|
## Opens [member file_dialog] with the mode [member FileDialog.FILE_MODE_SAVE_FILE]
|
|
## as well as getting a weakref to the active [Deck]
|
|
func open_save_dialog(path: String) -> void:
|
|
file_dialog.file_mode = FileDialog.FILE_MODE_SAVE_FILE
|
|
file_dialog.title = "Save a Deck"
|
|
file_dialog.current_path = path if not path.is_empty() else recent_path
|
|
_deck_to_save = weakref(get_active_deck())
|
|
file_dialog.popup_centered()
|
|
file_dialog.file_selected.connect(_on_file_dialog_save_file, CONNECT_ONE_SHOT)
|
|
|
|
|
|
## Opens [member file_dialog] with the mode [FileDialog.FILE_MODE_OPEN_FILES]
|
|
## with the supplied [param path]
|
|
func open_open_dialog(path: String) -> void:
|
|
#file_dialog.file_mode = FileDialog.FILE_MODE_OPEN_FILES
|
|
# TODO: disabled opening multiple for now until better compat dialog method is found
|
|
file_dialog.file_mode = FileDialog.FILE_MODE_OPEN_FILE
|
|
#file_dialog.title = "Open Deck(s)"
|
|
file_dialog.title = "Open Deck"
|
|
file_dialog.current_path = path + "/"
|
|
file_dialog.popup_centered()
|
|
#file_dialog.files_selected.connect(_on_file_dialog_open_files, CONNECT_ONE_SHOT)
|
|
file_dialog.file_selected.connect(open_deck_at_path, CONNECT_ONE_SHOT)
|
|
|
|
|
|
## Connected to [signal FileDialog.save_file] on [member file_dialog].
|
|
## Saves the selected [Deck] if it still exists.
|
|
func _on_file_dialog_save_file(path: String) -> void:
|
|
var deck: Deck = _deck_to_save.get_ref() as Deck
|
|
if not deck:
|
|
return
|
|
|
|
deck.save_path = path
|
|
tab_container.set_tab_title(tab_container.get_current_tab(), path.get_file())
|
|
var renderer: DeckRendererGraphEdit = tab_container.get_content(tab_container.get_current_tab()) as DeckRendererGraphEdit
|
|
renderer.dirty = false
|
|
# TODO: put this into DeckHolder instead
|
|
var json := JSON.stringify(deck.to_dict(), "\t")
|
|
var f := FileAccess.open(path, FileAccess.WRITE)
|
|
f.store_string(json)
|
|
add_recent_file(get_active_deck().save_path)
|
|
recent_path = path.get_base_dir()
|
|
RendererPersistence.set_value(PERSISTENCE_NAMESPACE, "config", "recent_path", recent_path)
|
|
|
|
|
|
## Connected to [signal FileDialog.open_files] on [member file_dialog]. Opens
|
|
## the selected paths, instantiating [DeckRenderGraphEdit]s and [Deck]s for each.
|
|
func _on_file_dialog_open_files(paths: PackedStringArray) -> void:
|
|
for path in paths:
|
|
open_deck_at_path(path)
|
|
|
|
|
|
func open_deck_at_path(path: String) -> void:
|
|
for tab in tab_container.get_tab_count():
|
|
if tab_container.get_tab_metadata(tab, "path") == path:
|
|
tab_container.set_current_tab(tab)
|
|
return
|
|
|
|
var f := FileAccess.open(path, FileAccess.READ)
|
|
if f.get_error() != OK:
|
|
return
|
|
|
|
var deck_data: Dictionary = JSON.parse_string(f.get_as_text())
|
|
if DeckHolder.get_deck_compat(deck_data) != 0:
|
|
compat_dialog.set_meta(&"path", path)
|
|
file_dialog.hide()
|
|
compat_dialog.popup_centered()
|
|
return
|
|
|
|
open_deck_from_dict(deck_data, path)
|
|
|
|
|
|
func open_deck_from_dict(data: Dictionary, path: String) -> void:
|
|
var deck := DeckHolder.open_deck_from_dict(data, path)
|
|
if deck == null:
|
|
DeckHolder.logger.toast_error("Error loading deck at path: %s" % path)
|
|
return
|
|
var inst: DeckRendererGraphEdit = DECK_SCENE.instantiate()
|
|
inst.deck = deck
|
|
var tab := tab_container.add_content(inst, path.get_file())
|
|
tab_container.set_tab_metadata(tab, "id", deck.id)
|
|
tab_container.set_tab_metadata(tab, "group", false)
|
|
tab_container.set_tab_metadata(tab, "path", path)
|
|
inst.initialize_from_deck()
|
|
inst.group_enter_requested.connect(_on_deck_renderer_group_enter_requested)
|
|
inst.dirty_state_changed.connect(_on_deck_renderer_dirty_state_changed.bind(inst))
|
|
add_recent_file(path)
|
|
recent_path = path.get_base_dir()
|
|
RendererPersistence.set_value(PERSISTENCE_NAMESPACE, "config", "recent_path", recent_path)
|
|
tab_container.set_current_tab(tab)
|
|
bottom_dock.variable_viewer.enable_new_button()
|
|
|
|
|
|
## Returns the current deck in the [member tab_container].
|
|
func get_active_deck() -> Deck:
|
|
return get_deck_at_tab(tab_container.get_current_tab())
|
|
|
|
|
|
## Returns the deck at [param tab] in the [member tab_container].
|
|
func get_deck_at_tab(tab: int) -> Deck:
|
|
var r := get_deck_renderer_at_tab(tab)
|
|
if is_instance_valid(r.deck):
|
|
return get_deck_renderer_at_tab(tab).deck
|
|
else:
|
|
return null
|
|
|
|
|
|
## Returns the current deck renderer in the [member tab_container].
|
|
func get_active_deck_renderer() -> DeckRendererGraphEdit:
|
|
return get_deck_renderer_at_tab(tab_container.get_current_tab())
|
|
|
|
|
|
## Returns the deck renderer at [param tab] in the [member tab_container].
|
|
func get_deck_renderer_at_tab(tab: int) -> DeckRendererGraphEdit:
|
|
if tab_container.is_empty():
|
|
return null
|
|
|
|
return tab_container.get_content(tab) as DeckRendererGraphEdit
|
|
|
|
|
|
## Saves the active [Deck] in [member tab_container]
|
|
func save_active_deck() -> void:
|
|
save_tab(tab_container.get_current_tab())
|
|
|
|
|
|
func save_tab(tab: int) -> void:
|
|
if tab_container.get_tab_metadata(tab, "group"):
|
|
return
|
|
|
|
var renderer := tab_container.get_content(tab) as DeckRendererGraphEdit
|
|
var deck := renderer.deck
|
|
if deck.save_path.is_empty():
|
|
#open_save_dialog("res://") # what the fuck?
|
|
open_save_dialog(tab_container.get_tab_metadata(tab, "path", recent_path))
|
|
else:
|
|
var json := JSON.stringify(deck.to_dict(), "\t")
|
|
var f := FileAccess.open(deck.save_path, FileAccess.WRITE)
|
|
f.store_string(json)
|
|
add_recent_file(deck.save_path)
|
|
renderer.dirty = false
|
|
|
|
|
|
## Disconnects the [FileDialog] signals if they are already connected.
|
|
func disconnect_file_dialog_signals() -> void:
|
|
if file_dialog.file_selected.is_connected(_on_file_dialog_save_file):
|
|
file_dialog.file_selected.disconnect(_on_file_dialog_save_file)
|
|
|
|
if file_dialog.files_selected.is_connected(_on_file_dialog_open_files):
|
|
file_dialog.files_selected.disconnect(_on_file_dialog_open_files)
|
|
|
|
if file_dialog.file_selected.is_connected(open_deck_at_path):
|
|
file_dialog.file_selected.disconnect(open_deck_at_path)
|
|
|
|
|
|
## Connected to [signal DeckRenderGraphEdit.group_entered_request] to allow entering
|
|
## groups based off the given [param group_id] and [param deck]. As well as adding
|
|
## a corresponding tab to [member tab_container]
|
|
func _on_deck_renderer_group_enter_requested(group_id: String) -> void:
|
|
#var group_deck := deck.get_group(group_id)
|
|
for tab in tab_container.get_tab_count():
|
|
if tab_container.get_tab_metadata(tab, "id") == group_id:
|
|
tab_container.set_current_tab(tab)
|
|
return
|
|
var group_deck := DeckHolder.get_deck(group_id)
|
|
var deck_renderer: DeckRendererGraphEdit = DECK_SCENE.instantiate()
|
|
deck_renderer.deck = group_deck
|
|
deck_renderer.initialize_from_deck()
|
|
var title := ""
|
|
if group_deck.is_library:
|
|
title = "(L) %s" % group_deck.id
|
|
else:
|
|
title = "(G) %s" % group_id.left(8)
|
|
var tab := tab_container.add_content(deck_renderer, title)
|
|
tab_container.set_tab_metadata(tab, "id", group_id)
|
|
tab_container.set_tab_metadata(tab, "group", true)
|
|
deck_renderer.group_enter_requested.connect(_on_deck_renderer_group_enter_requested)
|
|
tab_container.set_current_tab(tab)
|
|
|
|
|
|
func _on_connections_id_pressed(id: int) -> void:
|
|
match id:
|
|
ConnectionsMenuId.OBS:
|
|
obs_setup_dialog.popup_centered()
|
|
ConnectionsMenuId.TWITCH:
|
|
twitch_setup_dialog.popup_centered()
|
|
|
|
|
|
func _on_obs_websocket_setup_dialog_connect_button_pressed(state: OBSWebsocketSetupDialog.ConnectionState) -> void:
|
|
match state:
|
|
OBSWebsocketSetupDialog.ConnectionState.DISCONNECTED:
|
|
obs_setup_dialog.set_button_state(OBSWebsocketSetupDialog.ConnectionState.CONNECTING)
|
|
no_obsws.subscriptions = obs_setup_dialog.get_subscriptions()
|
|
no_obsws.connect_to_obsws(obs_setup_dialog.get_port(), obs_setup_dialog.get_password())
|
|
await no_obsws.connection_ready
|
|
obs_setup_dialog.set_button_state(OBSWebsocketSetupDialog.ConnectionState.CONNECTED)
|
|
OBSWebsocketSetupDialog.ConnectionState.CONNECTED:
|
|
no_obsws.disconnect_from_obsws()
|
|
obs_setup_dialog.set_button_state(OBSWebsocketSetupDialog.ConnectionState.DISCONNECTED)
|
|
|
|
|
|
func _process(delta: float) -> void:
|
|
DeckHolder.send_event(&"process", {"delta": delta})
|
|
|
|
|
|
func _on_debug_id_pressed(id: int) -> void:
|
|
match id:
|
|
DebugMenuId.DECKS:
|
|
var d := AcceptDialog.new()
|
|
var debug_decks: DebugDecksList = DEBUG_DECKS_LIST.instantiate()
|
|
d.add_child(debug_decks)
|
|
d.canceled.connect(d.queue_free)
|
|
d.confirmed.connect(d.queue_free)
|
|
debug_decks.item_pressed.connect(_on_debug_decks_viewer_item_pressed)
|
|
add_child(d)
|
|
d.popup_centered()
|
|
DebugMenuId.NODES:
|
|
var d := AcceptDialog.new()
|
|
var debug_nodes: DebugNodesList = DEBUG_NODES_LIST.instantiate()
|
|
d.add_child(debug_nodes)
|
|
d.canceled.connect(d.queue_free)
|
|
d.confirmed.connect(d.queue_free)
|
|
debug_nodes.build(get_active_deck())
|
|
add_child(d)
|
|
d.popup_centered()
|
|
DebugMenuId.EMBED_SUBWINDOWS:
|
|
var c := debug_popup_menu.is_item_checked(id)
|
|
debug_popup_menu.set_item_checked(id, not c)
|
|
get_tree().get_root().gui_embed_subwindows = not c
|
|
RendererPersistence.set_value(PERSISTENCE_NAMESPACE, "config", "embed_subwindows", not c)
|
|
file_dialog.use_native_dialog = c
|
|
|
|
|
|
func _on_debug_decks_viewer_item_pressed(deck_id: String, instance_id: String) -> void:
|
|
if instance_id == "":
|
|
var deck := DeckHolder.get_deck(deck_id)
|
|
var inst: DeckRendererGraphEdit = DECK_SCENE.instantiate()
|
|
inst.deck = deck
|
|
var tab := tab_container.add_content(inst, "<Deck %s>" % [deck_id.left(8)])
|
|
tab_container.set_tab_metadata(tab, "id", deck.id)
|
|
inst.initialize_from_deck()
|
|
inst.group_enter_requested.connect(_on_deck_renderer_group_enter_requested)
|
|
tab_container.set_current_tab(tab)
|
|
else:
|
|
var deck := DeckHolder.get_group_instance(deck_id, instance_id)
|
|
var inst: DeckRendererGraphEdit = DECK_SCENE.instantiate()
|
|
inst.deck = deck
|
|
var tab := tab_container.add_content(inst, "<Group %s::%s>" % [deck_id.left(8), instance_id.left(8)])
|
|
tab_container.set_tab_metadata(tab, "id", deck.id)
|
|
inst.initialize_from_deck()
|
|
inst.group_enter_requested.connect(_on_deck_renderer_group_enter_requested)
|
|
tab_container.set_current_tab(tab)
|
|
|
|
|
|
func _on_deck_renderer_dirty_state_changed(renderer: DeckRendererGraphEdit) -> void:
|
|
var idx: int = range(tab_container.get_tab_count()).filter(
|
|
func(x: int):
|
|
return tab_container.get_content(x) == renderer
|
|
)[0]
|
|
var title := tab_container.get_tab_title(idx).trim_suffix("(*)")
|
|
if renderer.dirty:
|
|
tab_container.set_tab_title(idx, "%s(*)" % title)
|
|
else:
|
|
tab_container.set_tab_title(idx, title.trim_suffix("(*)"))
|
|
tab_container.set_tab_metadata(idx, "dirty", renderer.dirty)
|
|
|
|
|
|
func add_recent_file(path: String) -> void:
|
|
var item := recent_files.find(path)
|
|
if item == -1:
|
|
recent_files.push_front(path)
|
|
else:
|
|
recent_files.push_front(recent_files.pop_at(item))
|
|
recent_files = recent_files.slice(0, max_recents)
|
|
|
|
add_recents_to_menu()
|
|
|
|
|
|
func add_recents_to_menu() -> void:
|
|
if recent_files.is_empty():
|
|
return
|
|
|
|
if file_popup_menu.get_item_count() > FileMenuId.RECENTS + 1:
|
|
var end := file_popup_menu.get_item_count() - FileMenuId.RECENTS - 1
|
|
for i in end:
|
|
file_popup_menu.remove_item(file_popup_menu.get_item_count() - 1)
|
|
|
|
var reduce_length := recent_files.any(func(x: String): return x.length() > 35)
|
|
|
|
for i in recent_files.size():
|
|
var file = recent_files[i] as String
|
|
if reduce_length:
|
|
# shorten the basepath to be the first letter of all folders
|
|
var base: String = Array(
|
|
file.get_base_dir().split("/", false)
|
|
).reduce(
|
|
func(a: String, s: String):
|
|
return a + s[0] + "/",
|
|
"/")
|
|
var filename := file.get_file()
|
|
file = base.path_join(filename)
|
|
|
|
file_popup_menu.add_item(file)
|
|
|
|
reset_recents_shortcuts()
|
|
RendererPersistence.set_value(PERSISTENCE_NAMESPACE, "config", "recent_files", recent_files)
|
|
|
|
|
|
func request_quit() -> void:
|
|
RendererPersistence.commit(PERSISTENCE_NAMESPACE)
|
|
|
|
if range(tab_container.get_tab_count()).any(func(x: int): return tab_container.get_content(x).dirty):
|
|
unsaved_changes_dialog.show()
|
|
else:
|
|
for i in range(tab_container.get_tab_count() - 1, -1, -1):
|
|
await close_tab(i)
|
|
|
|
#get_tree().quit()
|
|
quit_completed.emit()
|
|
|
|
|
|
func _on_unsaved_changes_dialog_single_deck_confirmed() -> void:
|
|
save_tab(unsaved_changes_dialog_single_deck.get_meta("tab"))
|
|
await close_tab(unsaved_changes_dialog_single_deck.get_meta("tab"))
|
|
|
|
|
|
func _on_unsaved_changes_dialog_single_deck_custom_action(action: StringName) -> void:
|
|
if action == &"force_close":
|
|
await close_tab(unsaved_changes_dialog_single_deck.get_meta("tab"))
|
|
unsaved_changes_dialog_single_deck.hide()
|
|
|
|
|
|
func _on_unsaved_changes_dialog_confirmed() -> void:
|
|
for i in range(tab_container.get_tab_count() - 1, -1, -1):
|
|
await close_tab(i)
|
|
#get_tree().quit()
|
|
quit_completed.emit()
|
|
|
|
|
|
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:
|
|
match id:
|
|
HelpMenuId.ABOUT:
|
|
about_dialog.show()
|
|
HelpMenuId.DOCS:
|
|
OS.shell_open("https://codeberg.org/Eroax/StreamGraph/wiki")
|
|
|
|
|
|
func _on_compat_dialog_confirmed() -> void:
|
|
var path: String = compat_dialog.get_meta(&"path", "")
|
|
var f := FileAccess.open(path, FileAccess.READ)
|
|
if f.get_error() != OK:
|
|
return
|
|
|
|
# save a backup
|
|
if path.split(".")[-2] != "old":
|
|
var backup_path := "%s.old.deck" % path.trim_suffix(".deck")
|
|
DirAccess.copy_absolute(path, backup_path)
|
|
|
|
var deck_data: Dictionary = JSON.parse_string(f.get_as_text())
|
|
open_deck_from_dict(deck_data, path)
|
|
# set it as dirty to make sure the user resaves
|
|
get_active_deck_renderer().dirty = true
|