search_menu.gd 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157
  1. extends PopupPanel
  2. @onready var item_container: VBoxContainer = $VBoxContainer/ScrollContainer/ItemContainer
  3. @onready var scroll_container: ScrollContainer = $VBoxContainer/ScrollContainer
  4. @onready var search_bar = $VBoxContainer/SearchBar
  5. @onready var mode_label = $VBoxContainer/ReplaceLabel
  6. var node_data = {} #stores node data for each node to display in help popup
  7. var replace_node = false
  8. var node_to_replace
  9. var connect_to_node = false
  10. var node_to_connect_to
  11. var uiscale
  12. var favourites
  13. signal make_node(command)
  14. signal swap_node(node_to_replace, command)
  15. signal connect_to_clicked_node(node_to_connect_to, command)
  16. func _ready() -> void:
  17. #parse json
  18. var file = FileAccess.open("res://scenes/main/process_help.json", FileAccess.READ)
  19. if file:
  20. var result = JSON.parse_string(file.get_as_text())
  21. if typeof(result) == TYPE_DICTIONARY:
  22. node_data = result
  23. else:
  24. push_error("Invalid JSON")
  25. #honestly not sure what of these is actually doing things
  26. item_container.custom_minimum_size.x = scroll_container.size.x
  27. scroll_container.size_flags_vertical = Control.SIZE_EXPAND_FILL
  28. scroll_container.set("theme_override_constants/maximum_height", 400)
  29. func _on_about_to_popup() -> void:
  30. var interface_settings = ConfigHandler.load_interface_settings()
  31. favourites = interface_settings.favourites
  32. display_items("") #populate menu when needed
  33. search_bar.clear()
  34. search_bar.grab_focus()
  35. func display_items(filter: String):
  36. # Remove all existing items from the VBoxContainer
  37. for child in item_container.get_children():
  38. child.queue_free()
  39. var filters = filter.to_lower().split(" ", false)
  40. for key in node_data.keys():
  41. var item = node_data[key]
  42. var title = item.get("title", "")
  43. #check if searching for favourites
  44. if filters.has("*"):
  45. if favourites.has(key) == false:
  46. continue
  47. #filter out output node
  48. if title == "Output File":
  49. continue
  50. var category = item.get("category", "")
  51. var subcategory = item.get("subcategory", "")
  52. var short_desc = item.get("short_description", "")
  53. var command = key.replace("_", " ")
  54. # Combine all searchable text into one lowercase string
  55. var searchable_text = "%s %s %s %s %s" % [title, short_desc, category, subcategory, key]
  56. searchable_text = searchable_text.to_lower()
  57. # If filter is not empty, skip non-matches populate all other buttons
  58. if filter != "":
  59. var match_all_words = true
  60. for word in filters:
  61. if word == "*":
  62. continue
  63. if word != "" and not searchable_text.findn(word) != -1:
  64. match_all_words = false
  65. break
  66. if not match_all_words:
  67. continue
  68. var btn = Button.new()
  69. btn.size_flags_horizontal = Control.SIZE_EXPAND_FILL #make buttons wide
  70. btn.alignment = 0 #left align text
  71. btn.clip_text = true #clip off labels that are too long
  72. btn.text_overrun_behavior = TextServer.OVERRUN_TRIM_ELLIPSIS #and replace with ...
  73. var button_text = ""
  74. if favourites.has(key):
  75. button_text += "★ "
  76. if category.to_lower() == "pvoc": #format node names correctly, only show the category for PVOC
  77. button_text += "%s %s: %s - %s" % [category.to_upper(), subcategory.to_pascal_case(), title, short_desc]
  78. elif title.to_lower() == "input file":
  79. button_text += "%s - %s" % [title, short_desc]
  80. else:
  81. button_text += "%s: %s - %s" % [subcategory.to_pascal_case(), title, short_desc]
  82. btn.text = button_text
  83. btn.connect("pressed", Callable(self, "_on_item_selected").bind(key)) #pass key (process name) when button is pressed
  84. #apply custom focus theme for keyboard naviagation
  85. var theme := Theme.new()
  86. var style_focus := StyleBoxFlat.new()
  87. style_focus.bg_color = Color.hex(0xffffff4a)
  88. theme.set_stylebox("focus", "Button", style_focus)
  89. btn.theme = theme
  90. item_container.add_child(btn)
  91. #resize menu within certain bounds #50
  92. await get_tree().process_frame
  93. #if DisplayServer.screen_get_dpi(0) >= 144:
  94. #self.size.y = min((item_container.size.y + search_bar.size.y + 12) * 2, 820) #i think this will scale for retina screens but might be wrong
  95. #else:
  96. if mode_label.visible:
  97. self.size.y = min((item_container.size.y + search_bar.size.y + 16 + mode_label.size.y) * uiscale, 410 * uiscale)
  98. else:
  99. self.size.y = min((item_container.size.y + search_bar.size.y + 12) * uiscale, 410 * uiscale)
  100. #highlight first button
  101. _on_search_bar_editing_toggled(true)
  102. func _on_search_bar_text_changed(new_text: String) -> void:
  103. display_items(new_text)
  104. func _on_item_selected(key: String):
  105. self.hide()
  106. if replace_node == true:
  107. swap_node.emit(node_to_replace, key)
  108. elif connect_to_node == true:
  109. connect_to_clicked_node.emit(node_to_connect_to, key)
  110. else:
  111. make_node.emit(key) # send out signal to main patch
  112. func _on_search_bar_text_submitted(new_text: String) -> void:
  113. var button = item_container.get_child(0)
  114. if button and button is Button:
  115. button.emit_signal("pressed")
  116. func _on_search_bar_editing_toggled(toggled_on: bool) -> void:
  117. #highlight first button when editing is toggled
  118. var button = item_container.get_child(0)
  119. if toggled_on:
  120. if button and button is Button:
  121. var base_stylebox = button.get_theme_stylebox("normal", "Button")
  122. var new_stylebox = base_stylebox.duplicate()
  123. new_stylebox.bg_color = Color.hex(0xffffff4a)
  124. button.add_theme_stylebox_override("normal", new_stylebox)
  125. #skip this button on tab navigation
  126. button.focus_mode = Control.FOCUS_CLICK
  127. else:
  128. if button and button is Button:
  129. button.remove_theme_stylebox_override("normal")