mirror of
https://codeberg.org/StreamGraph/StreamGraph.git
synced 2024-11-13 19:49:55 +01:00
d98d6f52bd
Reviewed-on: https://codeberg.org/StreamGraph/StreamGraph/pulls/89 Co-authored-by: Lera Elvoé <yagich@poto.cafe> Co-committed-by: Lera Elvoé <yagich@poto.cafe>
511 lines
18 KiB
GDScript
511 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
|
|
|
|
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)
|
|
)
|
|
add_recents_to_menu()
|
|
|
|
tab_container.tab_close_requested.connect(
|
|
func(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)
|
|
)
|
|
|
|
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)
|
|
|
|
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 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)
|
|
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 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 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.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.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)
|
|
var s := Shortcut.new()
|
|
var k := InputEventKey.new()
|
|
@warning_ignore("int_as_enum_without_cast")
|
|
k.keycode = KEY_1 + i
|
|
k.ctrl_pressed = true
|
|
s.events.append(k)
|
|
file_popup_menu.set_item_shortcut(file_popup_menu.get_item_count() - 1, s)
|
|
|
|
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_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")
|