mirror of
https://codeberg.org/StreamGraph/StreamGraph.git
synced 2024-11-13 19:49:55 +01:00
759f6eff73
every now and then, opening the project in Godot would throw an error saying that `res://graph_node_renderer/graph_node_renderer.tscn` scene was corrupted and could not be opened. the scene would refuse to open in the editor, throwing the same error. however, the app ran, instantiating the scene like nothing was wrong. the error would sometimes go away after a few restarts. after many hours of searching up about it, i think i identified the problem. that specific scene file has nothing actually corrupted about it, and deleting and re-adding it solved the problem until some other random next time it popped up. see the relevant issues on godot's github: https://github.com/godotengine/godot/issues/85907 https://github.com/godotengine/godot/issues/79545 https://github.com/godotengine/godot/issues/70985 for the time being, i've replaced most relevant scene preloads with exports. hopefully it doesn't happen now. Reviewed-on: https://codeberg.org/StreamGraph/StreamGraph/pulls/70 Co-authored-by: Lera Elvoé <yagich@poto.cafe> Co-committed-by: Lera Elvoé <yagich@poto.cafe>
501 lines
18 KiB
GDScript
501 lines
18 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
|
|
|
|
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
|
|
|
|
@export var new_deck_shortcut: Shortcut
|
|
@export var open_deck_shortcut: Shortcut
|
|
@export var save_deck_shortcut: Shortcut
|
|
@export var save_deck_as_shortcut: Shortcut
|
|
@export var close_deck_shortcut: Shortcut
|
|
|
|
## Enum for storing the Options in the "File" PopupMenu.
|
|
enum FileMenuId {
|
|
NEW,
|
|
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,
|
|
}
|
|
|
|
enum DebugMenuId {
|
|
DECKS,
|
|
EMBED_SUBWINDOWS,
|
|
}
|
|
@onready var debug_popup_menu: PopupMenu = %Debug
|
|
|
|
enum HelpMenuId {
|
|
DOCS,
|
|
ABOUT,
|
|
}
|
|
@onready var about_dialog: AcceptDialog = %AboutDialog
|
|
|
|
## 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
|
|
|
|
|
|
func _ready() -> void:
|
|
get_tree().auto_accept_quit = false
|
|
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 = !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)
|
|
)
|
|
add_recents_to_menu()
|
|
|
|
tab_container.tab_close_requested.connect(
|
|
func(tab: int):
|
|
if tab_container.get_tab_metadata(tab, "dirty") && !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)
|
|
)
|
|
|
|
file_dialog.canceled.connect(disconnect_file_dialog_signals)
|
|
Connections.obs_websocket = no_obsws
|
|
Connections.twitch = %Twitch_Connection
|
|
|
|
Connections.twitch.chat_received_rich.connect(Connections._twitch_chat_received)
|
|
|
|
file_popup_menu.set_item_shortcut(FileMenuId.NEW, new_deck_shortcut)
|
|
file_popup_menu.set_item_shortcut(FileMenuId.OPEN, open_deck_shortcut)
|
|
file_popup_menu.set_item_shortcut(FileMenuId.SAVE, save_deck_shortcut)
|
|
file_popup_menu.set_item_shortcut(FileMenuId.SAVE_AS, save_deck_as_shortcut)
|
|
file_popup_menu.set_item_shortcut(FileMenuId.CLOSE, close_deck_shortcut)
|
|
|
|
bottom_dock.variable_viewer.top_field_edited.connect(
|
|
func(old_name: String, new_name: String, new_value: Variant) -> void:
|
|
get_active_deck_renderer().dirty = true
|
|
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)
|
|
)
|
|
|
|
|
|
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)
|
|
|
|
|
|
func _on_tab_container_tab_changed(tab: int) -> void:
|
|
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)
|
|
var deck := get_active_deck()
|
|
deck.variables_updated.connect(bottom_dock.rebuild_variable_tree.bind(deck.variable_stack))
|
|
|
|
|
|
## 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:
|
|
close_current_tab()
|
|
_ when id in range(FileMenuId.RECENTS, FileMenuId.RECENTS + max_recents + 1):
|
|
open_deck_at_path(recent_files[id - FileMenuId.RECENTS - 1])
|
|
|
|
|
|
## 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 close_tab(tab: int) -> void:
|
|
if !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)
|
|
if tab_container.get_tab_count() == 0:
|
|
bottom_dock.variable_viewer.disable_new_button()
|
|
|
|
## 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
|
|
_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
|
|
file_dialog.title = "Open Deck(s)"
|
|
file_dialog.current_path = path + "/"
|
|
file_dialog.popup_centered()
|
|
file_dialog.files_selected.connect(_on_file_dialog_open_files, 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 !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 deck := DeckHolder.open_deck_from_file(path)
|
|
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:
|
|
return get_deck_renderer_at_tab(tab).deck
|
|
|
|
|
|
## 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://")
|
|
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)
|
|
|
|
## 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 tab := tab_container.add_content(deck_renderer, "(g) %s" % group_id.left(8))
|
|
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.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)
|
|
|
|
|
|
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.EMBED_SUBWINDOWS:
|
|
var c := debug_popup_menu.is_item_checked(id)
|
|
debug_popup_menu.set_item_checked(id, !c)
|
|
get_tree().get_root().gui_embed_subwindows = !c
|
|
RendererPersistence.set_value(PERSISTENCE_NAMESPACE, "config", "embed_subwindows", !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)
|
|
var s := Shortcut.new()
|
|
var k := InputEventKey.new()
|
|
k.keycode = KEY_1 + i
|
|
k.ctrl_pressed = true
|
|
s.events.append(k)
|
|
file_popup_menu.set_item_shortcut(file_popup_menu.get_item_count() - 1, s)
|
|
|
|
RendererPersistence.set_value(PERSISTENCE_NAMESPACE, "config", "recent_files", recent_files)
|
|
|
|
|
|
func _notification(what: int) -> void:
|
|
if what == NOTIFICATION_WM_CLOSE_REQUEST:
|
|
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 tab_container.get_tab_count():
|
|
#close_tab(i)
|
|
for i in range(tab_container.get_tab_count() - 1, -1, -1):
|
|
await close_tab(i)
|
|
|
|
get_tree().quit()
|
|
|
|
|
|
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()
|
|
|
|
|
|
func _unhandled_input(event: InputEvent) -> void:
|
|
if event.is_action_pressed("toggle_bottom_dock"):
|
|
bottom_dock.visible = !bottom_dock.visible
|
|
accept_event()
|
|
|
|
|
|
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")
|