control.gd 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737
  1. extends Control
  2. var mainmenu_visible : bool = false #used to test if mainmenu is open
  3. var effect_position = Vector2(40,40) #tracks mouse position for node placement offset
  4. @onready var graph_edit = $GraphEdit
  5. var cdpprogs_location #stores the cdp programs location from user prefs for easy access
  6. var delete_intermediate_outputs # tracks state of delete intermediate outputs toggle
  7. @onready var console_output: RichTextLabel = $Console/ConsoleOutput
  8. var undo_redo := UndoRedo.new()
  9. var output_audio_player #tracks the node that is the current output player for linking
  10. var input_audio_player #tracks node that is the current input player for linking
  11. var currentfile = "none" #tracks dir of currently loaded file for saving
  12. var changesmade = false #tracks if user has made changes to the currently loaded save file
  13. var savestate # tracks what the user is trying to do when savechangespopup is called
  14. var helpfile #tracks which help file the user was trying to load when savechangespopup is called
  15. var outfilename #links to the user name for outputfile field
  16. var foldertoggle #links to the reuse folder button
  17. var lastoutputfolder = "none" #tracks last output folder, this can in future be used to replace global.outfile but i cba right now
  18. var uiscale = 1.0 #tracks scaling for retina screens
  19. var use_anyway #used to store the folder selected for cdprogs when it appears the wrong folder is selected but the user wants to use it anyway
  20. var main_theme = preload("res://theme/main_theme.tres") #load the theme
  21. #scripts
  22. var open_help
  23. var run_thread
  24. var save_load
  25. # Called when the node enters the scene tree for the first time.
  26. func _ready() -> void:
  27. Nodes.hide()
  28. $mainmenu.hide()
  29. $NoLocationPopup.hide()
  30. $Console.hide()
  31. $NoInputPopup.hide()
  32. $MultipleConnectionsPopup.hide()
  33. $AudioSettings.hide()
  34. $AudioDevicePopup.hide()
  35. $SearchMenu.hide()
  36. $Settings.hide()
  37. $ProgressWindow.hide()
  38. $WrongFolderPopup.hide()
  39. $SaveChangesPopup.hide()
  40. $SaveDialog.access = FileDialog.ACCESS_FILESYSTEM
  41. $SaveDialog.file_mode = FileDialog.FILE_MODE_SAVE_FILE
  42. $SaveDialog.filters = ["*.thd"]
  43. $LoadDialog.access = FileDialog.ACCESS_FILESYSTEM
  44. $LoadDialog.file_mode = FileDialog.FILE_MODE_OPEN_FILE
  45. $LoadDialog.filters = ["*.thd"]
  46. get_tree().set_auto_accept_quit(false) #disable closing the app with the x and instead handle it internally
  47. load_scripts()
  48. make_signal_connections()
  49. check_user_preferences()
  50. hidpi_adjustment()
  51. new_patch()
  52. load_from_filesystem()
  53. check_cdp_location_set()
  54. func load_scripts():
  55. #load and initialise scripts
  56. open_help = preload("res://scenes/main/scripts/open_help.gd").new()
  57. open_help.init(self)
  58. add_child(open_help)
  59. run_thread = preload("res://scenes/main/scripts/run_thread.gd").new()
  60. run_thread.init(self, $ProgressWindow, $ProgressWindow/ProgressLabel, $ProgressWindow/ProgressBar, $GraphEdit, $Console, $Console/ConsoleOutput)
  61. add_child(run_thread)
  62. graph_edit.init(self, $GraphEdit, Callable(open_help, "show_help_for_node"), $MultipleConnectionsPopup)
  63. save_load = preload("res://scenes/main/scripts/save_load.gd").new()
  64. save_load.init(self, $GraphEdit, Callable(open_help, "show_help_for_node"), Callable(graph_edit, "_register_node_movement"), Callable(graph_edit, "_register_inputs_in_node"), Callable(self, "link_output"))
  65. add_child(save_load)
  66. func make_signal_connections():
  67. get_node("SearchMenu").make_node.connect(graph_edit._make_node)
  68. get_node("SearchMenu").swap_node.connect(graph_edit._swap_node)
  69. get_node("mainmenu").make_node.connect(graph_edit._make_node)
  70. get_node("mainmenu").open_help.connect(open_help.show_help_for_node)
  71. get_node("Settings").open_cdp_location.connect(show_cdp_location)
  72. get_node("Settings").console_on_top.connect(change_console_settings)
  73. get_node("Settings").invert_ui.connect(invert_theme_toggled)
  74. get_node("Settings").swap_zoom_and_move.connect(swap_zoom_and_move)
  75. func hidpi_adjustment():
  76. #checks if display is hidpi and scales ui accordingly hidpi - 144
  77. if DisplayServer.screen_get_dpi(0) >= 144:
  78. uiscale = 2.0
  79. get_window().content_scale_factor = uiscale
  80. #goes through popup_windows group and scales all popups and resizes them
  81. for window in get_tree().get_nodes_in_group("popup_windows"):
  82. window.size = window.size * uiscale
  83. window.content_scale_factor = uiscale
  84. func load_from_filesystem():
  85. #checks if user has opened a file from the system file menu and loads it
  86. var args = OS.get_cmdline_args()
  87. for arg in args:
  88. var path = arg.strip_edges()
  89. if FileAccess.file_exists(path) and path.get_extension().to_lower() == "thd":
  90. save_load.load_graph_edit(path)
  91. break
  92. func new_patch():
  93. #clear old patch
  94. graph_edit.clear_connections()
  95. for node in graph_edit.get_children():
  96. if node is GraphNode:
  97. node.queue_free()
  98. await get_tree().process_frame # Wait for nodes to actually be removed
  99. graph_edit.scroll_offset = Vector2(0, 0)
  100. #Generate input and output nodes
  101. var effect: GraphNode = Nodes.get_node(NodePath("inputfile")).duplicate()
  102. effect.name = "inputfile"
  103. get_node("GraphEdit").add_child(effect, true)
  104. effect.connect("open_help", Callable(open_help, "show_help_for_node"))
  105. effect.position_offset = Vector2(20,80)
  106. effect = Nodes.get_node(NodePath("outputfile")).duplicate()
  107. effect.name = "outputfile"
  108. get_node("GraphEdit").add_child(effect, true)
  109. effect.init() #initialise ui from user prefs
  110. effect.connect("open_help", Callable(open_help, "show_help_for_node"))
  111. effect.position_offset = Vector2((DisplayServer.screen_get_size().x - 480) / uiscale, 80)
  112. graph_edit._register_node_movement() #link nodes for tracking position changes for changes tracking
  113. changesmade = false #so it stops trying to save unchanged empty files
  114. get_window().title = "SoundThread"
  115. link_output()
  116. func link_output():
  117. #links various buttons and function in the input nodes - this is called after they are created so that it still works on new and loading files
  118. for control in get_tree().get_nodes_in_group("outputnode"): #check all items in outputnode group
  119. if control.get_meta("outputfunction") == "deleteintermediate": #link delete intermediate files toggle to script
  120. control.toggled.connect(_toggle_delete)
  121. _toggle_delete(control.button_pressed)
  122. #control.button_pressed = interface_settings.get("delete_intermediate", true)
  123. elif control.get_meta("outputfunction") == "runprocess": #link runprocess button
  124. control.button_down.connect(_run_process)
  125. elif control.get_meta("outputfunction") == "audioplayer": #link output audio player
  126. output_audio_player = control
  127. elif control.get_meta("outputfunction") == "filename":
  128. control.text = "outfile"
  129. outfilename = control
  130. elif control.get_meta("outputfunction") == "reusefolder":
  131. foldertoggle = control
  132. #foldertoggle.button_pressed = interface_settings.get("reuse_output_folder", true)
  133. elif control.get_meta("outputfunction") == "openfolder":
  134. control.button_down.connect(_open_output_folder)
  135. #for control in get_tree().get_nodes_in_group("inputnode"):
  136. #if control.get_meta("inputfunction") == "audioplayer": #link input for recycle function
  137. #print("input player found")
  138. #input_audio_player = control
  139. func check_user_preferences():
  140. var interface_settings = ConfigHandler.load_interface_settings()
  141. var audio_settings = ConfigHandler.load_audio_settings()
  142. var audio_devices = AudioServer.get_output_device_list()
  143. $Console.always_on_top = interface_settings.console_on_top
  144. if audio_devices.has(audio_settings.device):
  145. AudioServer.set_output_device(audio_settings.device)
  146. else:
  147. $AudioDevicePopup.popup_centered()
  148. match interface_settings.theme:
  149. 0:
  150. RenderingServer.set_default_clear_color(Color("#2f4f4e"))
  151. 1:
  152. RenderingServer.set_default_clear_color(Color("#000807"))
  153. 2:
  154. RenderingServer.set_default_clear_color(Color("#98d4d2"))
  155. 3:
  156. RenderingServer.set_default_clear_color(Color(interface_settings.theme_custom_colour))
  157. #set the theme to either the main theme or inverted theme depending on user preferences
  158. invert_theme_toggled(interface_settings.invert_theme)
  159. swap_zoom_and_move(interface_settings.swap_zoom_and_move)
  160. func show_cdp_location():
  161. $CdpLocationDialog.show()
  162. func check_cdp_location_set():
  163. #checks if the location has been set and prompts user to set it
  164. var cdpprogs_settings = ConfigHandler.load_cdpprogs_settings()
  165. if cdpprogs_settings.location == "no_location":
  166. $NoLocationPopup.popup_centered()
  167. else:
  168. #if location is set, stores it in a variable
  169. cdpprogs_location = str(cdpprogs_settings.location)
  170. print(cdpprogs_location)
  171. func _on_ok_button_button_down() -> void:
  172. #after user has read dialog on where to find cdp progs this loads the file browser
  173. $NoLocationPopup.hide()
  174. if OS.get_name() == "Windows":
  175. $CdpLocationDialog.current_dir = "C:/"
  176. else:
  177. $CdpLocationDialog.current_dir = OS.get_environment("HOME")
  178. $CdpLocationDialog.show()
  179. func _on_cdp_location_dialog_dir_selected(dir: String) -> void:
  180. var is_windows = OS.get_name() == "Windows"
  181. var cdprogs_correct
  182. #check if the selected folder contains the hilite program as it has a reasonably unique name and will indicate that the CDP processes do exist in that folder
  183. if is_windows:
  184. cdprogs_correct = FileAccess.file_exists(dir + "/distort.exe")
  185. else:
  186. cdprogs_correct = FileAccess.file_exists(dir + "/distort")
  187. if cdprogs_correct:
  188. #if this location does seem to contain cdp programs
  189. #saves default location for cdp programs in config file
  190. ConfigHandler.save_cdpprogs_settings(dir)
  191. cdpprogs_location = dir
  192. else:
  193. #if it doesn't seem to contain the programs then try and extrapolate the correct folder from the one selected
  194. var selected_folder = dir.get_slice("/", (dir.get_slice_count("/") - 1))
  195. print(selected_folder)
  196. if selected_folder.to_lower() == "cdpr8":
  197. dir = dir + "/_cdp/_cdprogs"
  198. #run this function recursively to check if the programs do exist
  199. _on_cdp_location_dialog_dir_selected(dir)
  200. elif selected_folder.to_lower() == "_cdp":
  201. dir = dir + "/_cdprogs"
  202. #run this function recursively to check if the programs do exist
  203. _on_cdp_location_dialog_dir_selected(dir)
  204. else:
  205. #can't find them
  206. use_anyway = dir
  207. $WrongFolderPopup.popup_centered()
  208. func _on_cdp_location_dialog_canceled() -> void:
  209. #cycles around the set location prompt if user cancels the file dialog
  210. check_cdp_location_set()
  211. func _on_select_folder_button_button_down() -> void:
  212. $WrongFolderPopup.hide()
  213. _on_ok_button_button_down()
  214. func _on_use_anyway_button_button_down() -> void:
  215. $WrongFolderPopup.hide()
  216. ConfigHandler.save_cdpprogs_settings(use_anyway)
  217. cdpprogs_location = use_anyway
  218. func _input(event):
  219. if event.is_action_pressed("undo"):
  220. simulate_mouse_click()
  221. await get_tree().process_frame
  222. undo_redo.undo()
  223. elif event.is_action_pressed("redo"):
  224. undo_redo.redo()
  225. elif event.is_action_pressed("save"):
  226. if currentfile == "none":
  227. savestate = "saveas"
  228. $SaveDialog.popup_centered()
  229. else:
  230. save_load.save_graph_edit(currentfile)
  231. elif event.is_action_pressed("open_explore"):
  232. open_explore()
  233. elif event.is_action_pressed("search"):
  234. var pos = graph_edit.get_local_mouse_position()
  235. _on_graph_edit_popup_request(pos)
  236. elif event.is_action_pressed("run_thread"):
  237. _run_process()
  238. elif event.is_action_pressed("new"):
  239. if changesmade == true:
  240. savestate = "newfile"
  241. $SaveChangesPopup.popup_centered()
  242. else:
  243. new_patch()
  244. currentfile = "none" #reset current file to none for save tracking
  245. elif event.is_action_pressed("save_as"):
  246. savestate = "saveas"
  247. $SaveDialog.popup_centered()
  248. func simulate_mouse_click():
  249. #simulates clicking the middle mouse button in order to hide any visible tooltips
  250. var click_pos = get_viewport().get_mouse_position()
  251. var down_event := InputEventMouseButton.new()
  252. down_event.button_index = MOUSE_BUTTON_MIDDLE
  253. down_event.pressed = true
  254. down_event.position = click_pos
  255. Input.parse_input_event(down_event)
  256. var up_event := InputEventMouseButton.new()
  257. up_event.button_index = MOUSE_BUTTON_MIDDLE
  258. up_event.pressed = false
  259. up_event.position = click_pos
  260. Input.parse_input_event(up_event)
  261. func _run_process() -> void:
  262. #check if any of the inputfile nodes don't have files loaded
  263. for node in graph_edit.get_children():
  264. if node.get_meta("command") == "inputfile" and node.get_node("AudioPlayer").has_meta("inputfile") == false:
  265. $NoInputPopup.popup_centered()
  266. return
  267. #check if the reuse folder toggle is set and a folder has been previously chosen
  268. if foldertoggle.button_pressed == true and lastoutputfolder != "none":
  269. _on_file_dialog_dir_selected(lastoutputfolder)
  270. else:
  271. $FileDialog.show()
  272. func _on_file_dialog_dir_selected(dir: String) -> void:
  273. lastoutputfolder = dir
  274. console_output.clear()
  275. var interface_settings = ConfigHandler.load_interface_settings()
  276. if interface_settings.disable_progress_bar == false:
  277. $ProgressWindow.show()
  278. else:
  279. if $Console.is_visible():
  280. $Console.hide()
  281. await get_tree().process_frame # Wait a frame to allow hide to complete
  282. $Console.popup_centered()
  283. else:
  284. $Console.popup_centered()
  285. await get_tree().process_frame
  286. run_thread.log_console("Generating processing queue", true)
  287. await get_tree().process_frame
  288. #get the current time in hh-mm-ss format as default : causes file name issues
  289. var time_dict = Time.get_time_dict_from_system()
  290. # Pad with zeros to ensure two digits for hour, minute, second
  291. var hour = str(time_dict.hour).pad_zeros(2)
  292. var minute = str(time_dict.minute).pad_zeros(2)
  293. var second = str(time_dict.second).pad_zeros(2)
  294. var time_str = hour + "-" + minute + "-" + second
  295. Global.outfile = dir + "/" + outfilename.text.get_basename() + "_" + Time.get_date_string_from_system() + "_" + time_str
  296. run_thread.log_console("Output directory and file name(s):" + Global.outfile, true)
  297. await get_tree().process_frame
  298. run_thread.run_thread_with_branches()
  299. func _toggle_delete(toggled_on: bool):
  300. delete_intermediate_outputs = toggled_on
  301. print(toggled_on)
  302. func _on_console_close_requested() -> void:
  303. $Console.hide()
  304. func _on_console_open_folder_button_down() -> void:
  305. $Console.hide()
  306. OS.shell_open(Global.outfile.get_base_dir())
  307. func _on_ok_button_2_button_down() -> void:
  308. $NoInputPopup.hide()
  309. func _on_ok_button_3_button_down() -> void:
  310. $MultipleConnectionsPopup.hide()
  311. func _on_settings_button_index_pressed(index: int) -> void:
  312. var interface_settings = ConfigHandler.load_interface_settings()
  313. match index:
  314. 0:
  315. $Settings.cdpprogs_location = cdpprogs_location
  316. $Settings.popup_centered()
  317. 1:
  318. $AudioSettings.popup_centered()
  319. 2:
  320. if $Console.is_visible():
  321. $Console.hide()
  322. await get_tree().process_frame # Wait a frame to allow hide to complete
  323. $Console.popup_centered()
  324. else:
  325. $Console.popup_centered()
  326. func _on_file_button_index_pressed(index: int) -> void:
  327. match index:
  328. 0:
  329. if changesmade == true:
  330. savestate = "newfile"
  331. $SaveChangesPopup.popup_centered()
  332. else:
  333. new_patch()
  334. currentfile = "none" #reset current file to none for save tracking
  335. 1:
  336. if currentfile == "none":
  337. savestate = "saveas"
  338. $SaveDialog.popup_centered()
  339. else:
  340. save_load.save_graph_edit(currentfile)
  341. print("save pressed, changes made =")
  342. print(changesmade)
  343. print("current file =")
  344. print(currentfile)
  345. 2:
  346. savestate = "saveas"
  347. $SaveDialog.popup_centered()
  348. 3:
  349. if changesmade == true:
  350. savestate = "load"
  351. $SaveChangesPopup.popup_centered()
  352. else:
  353. $LoadDialog.popup_centered()
  354. func _on_save_dialog_file_selected(path: String) -> void:
  355. save_load.save_graph_edit(path) #save file
  356. #check what the user was trying to do before save and do that action
  357. if savestate == "newfile":
  358. new_patch()
  359. currentfile = "none" #reset current file to none for save tracking
  360. elif savestate == "load":
  361. $LoadDialog.popup_centered()
  362. elif savestate == "helpfile":
  363. currentfile = "none" #reset current file to none for save tracking so user cant save over help file
  364. save_load.load_graph_edit(helpfile)
  365. elif savestate == "quit":
  366. await get_tree().create_timer(0.25).timeout #little pause so that it feels like it actually saved even though it did
  367. get_tree().quit()
  368. elif savestate == "saveas":
  369. currentfile = path
  370. savestate = "none" #reset save state, not really needed but feels good
  371. func _on_load_dialog_file_selected(path: String) -> void:
  372. currentfile = path #tracking path here only means "save" only saves patches the user has loaded rather than overwriting help files
  373. save_load.load_graph_edit(path)
  374. func _on_help_button_index_pressed(index: int) -> void:
  375. match index:
  376. 0:
  377. pass
  378. 1:
  379. if changesmade == true:
  380. savestate = "helpfile"
  381. helpfile = "res://examples/getting_started.thd"
  382. $SaveChangesPopup.popup_centered()
  383. else:
  384. currentfile = "none" #reset current file to none for save tracking so user cant save over help file
  385. save_load.load_graph_edit("res://examples/getting_started.thd")
  386. 2:
  387. if changesmade == true:
  388. savestate = "helpfile"
  389. helpfile = "res://examples/navigating.thd"
  390. $SaveChangesPopup.popup_centered()
  391. else:
  392. currentfile = "none" #reset current file to none for save tracking so user cant save over help file
  393. save_load.load_graph_edit("res://examples/navigating.thd")
  394. 3:
  395. if changesmade == true:
  396. savestate = "helpfile"
  397. helpfile = "res://examples/building_a_thread.thd"
  398. $SaveChangesPopup.popup_centered()
  399. else:
  400. currentfile = "none" #reset current file to none for save tracking so user cant save over help file
  401. save_load.load_graph_edit("res://examples/building_a_thread.thd")
  402. 4:
  403. if changesmade == true:
  404. savestate = "helpfile"
  405. helpfile = "res://examples/frequency_domain.thd"
  406. $SaveChangesPopup.popup_centered()
  407. else:
  408. currentfile = "none" #reset current file to none for save tracking so user cant save over help file
  409. save_load.load_graph_edit("res://examples/frequency_domain.thd")
  410. 5:
  411. if changesmade == true:
  412. savestate = "helpfile"
  413. helpfile = "res://examples/automation.thd"
  414. $SaveChangesPopup.popup_centered()
  415. else:
  416. currentfile = "none" #reset current file to none for save tracking so user cant save over help file
  417. save_load.load_graph_edit("res://examples/automation.thd")
  418. 6:
  419. if changesmade == true:
  420. savestate = "helpfile"
  421. helpfile = "res://examples/trimming.thd"
  422. $SaveChangesPopup.popup_centered()
  423. else:
  424. currentfile = "none" #reset current file to none for save tracking so user cant save over help file
  425. save_load.load_graph_edit("res://examples/trimming.thd")
  426. 7:
  427. if changesmade == true:
  428. savestate = "helpfile"
  429. helpfile = "res://examples/multiple_inputs.thd"
  430. $SaveChangesPopup.popup_centered()
  431. else:
  432. currentfile = "none" #reset current file to none for save tracking so user cant save over help file
  433. save_load.load_graph_edit("res://examples/multiple_inputs.thd")
  434. 8:
  435. if changesmade == true:
  436. savestate = "helpfile"
  437. helpfile = "res://examples/preview_nodes.thd"
  438. $SaveChangesPopup.popup_centered()
  439. else:
  440. currentfile = "none" #reset current file to none for save tracking so user cant save over help file
  441. save_load.load_graph_edit("res://examples/preview_nodes.thd")
  442. 9:
  443. pass
  444. 10:
  445. if changesmade == true:
  446. savestate = "helpfile"
  447. helpfile = "res://examples/wetdry.thd"
  448. $SaveChangesPopup.popup_centered()
  449. else:
  450. currentfile = "none" #reset current file to none for save tracking so user cant save over help file
  451. save_load.load_graph_edit("res://examples/wetdry.thd")
  452. 11:
  453. if changesmade == true:
  454. savestate = "helpfile"
  455. helpfile = "res://examples/resonant_filters.thd"
  456. $SaveChangesPopup.popup_centered()
  457. else:
  458. currentfile = "none" #reset current file to none for save tracking so user cant save over help file
  459. save_load.load_graph_edit("res://examples/resonant_filters.thd")
  460. 12:
  461. pass
  462. 13:
  463. OS.shell_open("https://www.composersdesktop.com/docs/html/ccdpndex.htm")
  464. 14:
  465. OS.shell_open("https://github.com/j-p-higgins/SoundThread/issues")
  466. #func _recycle_outfile():
  467. #if outfile != "no file":
  468. #input_audio_player.recycle_outfile(outfile)
  469. func _on_save_changes_button_down() -> void:
  470. $SaveChangesPopup.hide()
  471. if currentfile == "none":
  472. $SaveDialog.show()
  473. else:
  474. save_load.save_graph_edit(currentfile)
  475. if savestate == "newfile":
  476. new_patch()
  477. currentfile = "none" #reset current file to none for save tracking
  478. elif savestate == "load":
  479. $LoadDialog.popup_centered()
  480. elif savestate == "helpfile":
  481. currentfile = "none" #reset current file to none for save tracking so user cant save over help file
  482. save_load.load_graph_edit(helpfile)
  483. elif savestate == "quit":
  484. await get_tree().create_timer(0.25).timeout #little pause so that it feels like it actually saved even though it did
  485. get_tree().quit()
  486. savestate = "none"
  487. func _on_dont_save_changes_button_down() -> void:
  488. $SaveChangesPopup.hide()
  489. if savestate == "newfile":
  490. new_patch()
  491. currentfile = "none" #reset current file to none for save tracking
  492. elif savestate == "load":
  493. $LoadDialog.popup_centered()
  494. elif savestate == "helpfile":
  495. currentfile = "none" #reset current file to none for save tracking so user cant save over help file
  496. save_load.load_graph_edit(helpfile)
  497. elif savestate == "quit":
  498. get_tree().quit()
  499. savestate = "none"
  500. func _on_cancel_changes_button_down() -> void:
  501. $SaveChangesPopup.hide()
  502. savestate = "none"
  503. func _notification(what):
  504. if what == NOTIFICATION_WM_CLOSE_REQUEST:
  505. run_thread._on_kill_process_button_down()
  506. $Console.hide()
  507. if changesmade == true:
  508. savestate = "quit"
  509. $SaveChangesPopup.popup_centered()
  510. #$HelpWindow.hide()
  511. else:
  512. get_tree().quit() # default behavior
  513. func _open_output_folder():
  514. if lastoutputfolder != "none":
  515. OS.shell_open(lastoutputfolder)
  516. func _on_rich_text_label_meta_clicked(meta: Variant) -> void:
  517. print(str(meta))
  518. OS.shell_open(str(meta))
  519. func _on_graph_edit_popup_request(at_position: Vector2) -> void:
  520. effect_position = graph_edit.get_local_mouse_position()
  521. #get the mouse position in screen coordinates
  522. var mouse_screen_pos = DisplayServer.mouse_get_position()
  523. #get the window position in screen coordinates
  524. var window_screen_pos = get_window().position
  525. #get the window size relative to its scaling for retina displays
  526. var window_size = get_window().size * DisplayServer.screen_get_scale()
  527. #see if it was empty space or a node that was right clicked
  528. var clicked_node
  529. for child in graph_edit.get_children():
  530. if child is GraphNode:
  531. if Rect2(child.position, child.size).has_point(effect_position):
  532. clicked_node = child
  533. break
  534. if clicked_node and clicked_node.get_meta("command") != "outputfile":
  535. var title = clicked_node.title
  536. $SearchMenu/VBoxContainer/ReplaceLabel.text = "Replace " + title
  537. $SearchMenu/VBoxContainer/ReplaceLabel.show()
  538. $SearchMenu.replace_node = true
  539. $SearchMenu.node_to_replace = clicked_node
  540. else:
  541. $SearchMenu/VBoxContainer/ReplaceLabel.hide()
  542. $SearchMenu.replace_node = false
  543. #calculate the xy position of the mouse clamped to the size of the window and menu so it doesn't go off the screen
  544. var clamped_x = clamp(mouse_screen_pos.x, window_screen_pos.x, window_screen_pos.x + window_size.x - $SearchMenu.size.x)
  545. var clamped_y = clamp(mouse_screen_pos.y, window_screen_pos.y, window_screen_pos.y + window_size.y - (420 * DisplayServer.screen_get_scale()))
  546. #position and show the menu
  547. $SearchMenu.position = Vector2(clamped_x, clamped_y)
  548. $SearchMenu.popup()
  549. func _on_audio_settings_close_requested() -> void:
  550. $AudioSettings.hide()
  551. func _on_open_audio_settings_button_down() -> void:
  552. $AudioDevicePopup.hide()
  553. $AudioSettings.popup_centered()
  554. func _on_audio_device_popup_close_requested() -> void:
  555. $AudioDevicePopup.hide()
  556. func _on_mainmenu_close_requested() -> void:
  557. #closes menu if click is anywhere other than the menu as it is a window with popup set to true
  558. $mainmenu.hide()
  559. func open_explore():
  560. effect_position = graph_edit.get_local_mouse_position()
  561. #get the mouse position in screen coordinates
  562. var mouse_screen_pos = DisplayServer.mouse_get_position()
  563. #get the window position in screen coordinates
  564. var window_screen_pos = get_window().position
  565. #get the window size relative to its scaling for retina displays
  566. var window_size = get_window().size * DisplayServer.screen_get_scale()
  567. #get the size of the popup menu
  568. var popup_size = $mainmenu.size
  569. #calculate the xy position of the mouse clamped to the size of the window and menu so it doesn't go off the screen
  570. var clamped_x = clamp(mouse_screen_pos.x, window_screen_pos.x, window_screen_pos.x + window_size.x - popup_size.x)
  571. var clamped_y = clamp(mouse_screen_pos.y, window_screen_pos.y, window_screen_pos.y + window_size.y - popup_size.y)
  572. #position and show the menu
  573. $mainmenu.position = Vector2(clamped_x, clamped_y)
  574. $mainmenu.popup()
  575. func change_console_settings(toggled: bool):
  576. $Console.always_on_top = toggled
  577. func _on_kill_process_button_down() -> void:
  578. run_thread._on_kill_process_button_down()
  579. func invert_theme_toggled(toggled: bool):
  580. if toggled:
  581. var inverted = invert_theme(main_theme)
  582. get_tree().root.theme = inverted # force refresh
  583. $MenuBarBackground.color = Color(0.934, 0.934, 0.934)
  584. for color_rect in get_tree().get_nodes_in_group("invertable_background"):
  585. if color_rect is ColorRect:
  586. color_rect.color = Color(0.898, 0.898, 0.898, 0.6)
  587. else:
  588. get_tree().root.theme = main_theme # force refresheme = main_theme
  589. $MenuBarBackground.color = Color(0.065, 0.065, 0.065)
  590. for color_rect in get_tree().get_nodes_in_group("invertable_background"):
  591. if color_rect is ColorRect:
  592. color_rect.color = Color(0.102, 0.102, 0.102, 0.6)
  593. func invert_theme(theme: Theme) -> Theme:
  594. var inverted_theme = theme.duplicate(true) # deep copy
  595. # Check all types and color names in the theme
  596. var types = inverted_theme.get_type_list()
  597. for type in types:
  598. var color_names = inverted_theme.get_color_list(type)
  599. for cname in color_names:
  600. var col = inverted_theme.get_color(cname, type)
  601. var inverted = Color(1.0 - col.r, 1.0 - col.g, 1.0 - col.b, col.a)
  602. inverted_theme.set_color(cname, type, inverted)
  603. var style_names = inverted_theme.get_stylebox_list(type)
  604. for sname in style_names:
  605. if type == "GraphEdit" and sname == "panel":
  606. continue
  607. var sb = inverted_theme.get_stylebox(sname, type)
  608. var new_sb = sb.duplicate()
  609. if new_sb is StyleBoxFlat:
  610. var col = new_sb.bg_color
  611. new_sb.bg_color = Color(1.0 - col.r, 1.0 - col.g, 1.0 - col.b, col.a)
  612. inverted_theme.set_stylebox(sname, type, new_sb)
  613. return inverted_theme
  614. func swap_zoom_and_move(toggled: bool):
  615. if toggled:
  616. graph_edit.set_panning_scheme(1)
  617. else:
  618. graph_edit.set_panning_scheme(0)