2023-12-15 22:44:25 +01:00
# (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)
2023-11-23 07:38:10 +01:00
extends MarginContainer
class_name AddNodeMenu
2023-11-25 11:40:53 +01:00
## A menu for adding nodes with a search bar.
2023-11-23 07:38:10 +01:00
@ onready var search_line_edit : LineEdit = % SearchLineEdit
@ onready var scroll_content_container : VBoxContainer = % ScrollContentContainer
@ onready var scroll_container : ScrollContainer = $ VBoxContainer / ScrollContainer
2023-11-25 11:40:53 +01:00
## The categories currently shown in the menu.
2023-11-23 07:38:10 +01:00
var categories : Dictionary = { } # Dictionary[String, Category]
2023-11-25 11:40:53 +01:00
## A list of categories to remember the collapsed state of so they remain collapsed when the search list is rebuilt.
2023-11-23 07:38:10 +01:00
var collapsed_categories : Array [ String ]
2023-11-25 11:40:53 +01:00
## Emitted when a node is selected either by clicking on it or pressing [constant @GlobalScope.KEY_ENTER] while the [member search_line_edit] is focused.
2023-11-23 07:38:10 +01:00
signal node_selected ( type : String )
func _ready ( ) - > void :
search ( " " )
2023-11-25 11:40:53 +01:00
## Add a new category to the menu.
2023-11-23 07:38:10 +01:00
func add_category ( category_name : String ) - > void :
2024-01-20 06:11:38 +01:00
var c : = Category . new ( NodeDB . get_category_capitalization ( category_name ) )
2023-11-23 07:38:10 +01:00
categories [ category_name ] = c
scroll_content_container . add_child ( c )
c . collapse_toggled . connect ( _on_category_collapse_toggled . bind ( category_name ) )
c . item_pressed . connect (
func ( item : int ) :
node_selected . emit ( c . get_item_metadata ( item , " type " ) )
)
c . item_favorite_button_toggled . connect (
func ( item : int , toggled : bool ) :
NodeDB . set_node_favorite ( c . get_item_metadata ( item , " type " ) , toggled )
)
2023-11-25 11:40:53 +01:00
## Add an item to a category.
2023-11-23 07:38:10 +01:00
func add_category_item ( category : String , item : String , tooltip : String = " " , favorite : bool = false ) - > void :
var c : Category = categories [ category ]
c . add_item ( item , tooltip , favorite )
2023-11-25 11:40:53 +01:00
## Wrapper around [method add_category_item] and [method add_category]. Adds an item to a [param category], creating the category if it doesn't exist yet.
2023-11-23 07:38:10 +01:00
func add_item ( category : String , item : String , tooltip : String = " " , favorite : bool = false ) - > void :
2024-02-21 07:11:29 +01:00
if not categories . has ( category ) :
2023-11-23 07:38:10 +01:00
add_category ( category )
add_category_item ( category , item , tooltip , favorite )
2023-11-25 11:40:53 +01:00
## Get a [AddNodeMenu.Category] node by its' identifier.
2023-11-23 07:38:10 +01:00
func get_category ( category : String ) - > Category :
return categories [ category ]
2023-11-25 11:40:53 +01:00
## Focus the search bar and select all its' text.
2023-11-23 07:38:10 +01:00
func focus_search_bar ( ) - > void :
search_line_edit . select_all ( )
search_line_edit . grab_focus ( )
2023-11-25 11:40:53 +01:00
## Searches for a node using [SearchProvider] and puts the results as items.
2023-11-23 07:38:10 +01:00
func search ( text : String ) - > void :
scroll_content_container . get_children ( ) . map ( func ( c : Node ) : c . queue_free ( ) )
categories . clear ( )
var search_results : = SearchProvider . search ( text )
if search_results . is_empty ( ) :
return
for nd in search_results :
add_item ( nd . category , nd . name , nd . description , NodeDB . is_node_favorite ( nd . type ) )
var c : = get_category ( nd . category )
c . set_item_metadata ( c . get_item_count ( ) - 1 , " type " , nd . type )
c . set_item_metadata ( c . get_item_count ( ) - 1 , " node_descriptor " , weakref ( nd ) )
c . set_collapsed ( nd . category in collapsed_categories )
get_category ( categories . keys ( ) [ 0 ] ) . highlight_item ( 0 )
2023-11-25 11:40:53 +01:00
## Callback for [member search_line_edit]'s input events. Handles highlighting items when navigating with up/down arrow keys.
2023-11-23 07:38:10 +01:00
func _on_search_line_edit_gui_input ( event : InputEvent ) - > void :
if event . is_action_pressed ( " ui_down " ) :
2024-01-25 08:36:21 +01:00
if scroll_content_container . get_child_count ( ) == 0 :
return
2023-12-15 22:44:25 +01:00
search_line_edit . accept_event ( )
2023-11-23 07:38:10 +01:00
var category : Category
for i : String in categories :
var c : Category = categories [ i ]
if c . get_highlighted_item ( ) != - 1 :
category = c
break
var item : = category . get_highlighted_item ( )
if item + 1 == category . get_item_count ( ) :
# reached the end of items in the current category
category . unhighlight_all ( )
2023-11-25 12:28:57 +01:00
var nc : = get_next_visible_category ( category . get_index ( ) )
2023-11-23 07:38:10 +01:00
nc . highlight_item ( 0 )
scroll_container . ensure_control_visible ( nc . get_child ( 0 ) )
return
category . highlight_item ( item + 1 )
scroll_container . ensure_control_visible ( category . get_child ( item + 1 ) )
if event . is_action_pressed ( " ui_up " ) :
2024-01-25 08:36:21 +01:00
if scroll_content_container . get_child_count ( ) == 0 :
return
2023-12-15 22:44:25 +01:00
search_line_edit . accept_event ( )
2023-11-23 07:38:10 +01:00
var category : Category
for i : String in categories :
var c : Category = categories [ i ]
if c . get_highlighted_item ( ) != - 1 :
category = c
break
var item : = category . get_highlighted_item ( )
if item - 1 == - 1 :
# reached the beginning of items in the current category
category . unhighlight_all ( )
2023-11-25 12:28:57 +01:00
var nc : = get_previous_visible_category ( category . get_index ( ) )
2023-11-23 07:38:10 +01:00
nc . highlight_item ( nc . get_item_count ( ) - 1 )
scroll_container . ensure_control_visible ( nc . get_child ( nc . get_item_count ( ) - 1 ) )
return
category . highlight_item ( item - 1 )
scroll_container . ensure_control_visible ( category . get_child ( item - 1 ) )
2023-11-25 12:28:57 +01:00
## Returns the next uncollapsed category, starting from index [code]at[/code], wrapping around if no other
## categories are uncollapsed.
func get_next_visible_category ( at : int ) - > Category :
var i : = at
var s : = 0
while s < scroll_content_container . get_child_count ( ) :
i = ( i + 1 ) % scroll_content_container . get_child_count ( )
2024-02-21 07:11:29 +01:00
if not ( scroll_content_container . get_child ( i ) as Category ) . is_collapsed ( ) :
2023-11-25 12:28:57 +01:00
return scroll_content_container . get_child ( i )
s += 1
return scroll_content_container . get_child ( at )
## Returns the previous uncollapsed category, starting from index [code]at[/code], wrapping around if no other
## categories are uncollapsed.
func get_previous_visible_category ( at : int ) - > Category :
var i : = at
var s : = 0
while s < scroll_content_container . get_child_count ( ) :
i = ( i - 1 ) % scroll_content_container . get_child_count ( )
2024-02-21 07:11:29 +01:00
if not ( scroll_content_container . get_child ( i ) as Category ) . is_collapsed ( ) :
2023-11-25 12:28:57 +01:00
return scroll_content_container . get_child ( i )
s += 1
return scroll_content_container . get_child ( at )
2023-11-25 11:40:53 +01:00
## Callback for [member search_line_edit]. Handles emitting [signal node_selected]
2023-11-23 07:38:10 +01:00
func _on_search_line_edit_text_submitted ( _new_text : String ) - > void :
2024-01-25 08:36:21 +01:00
if scroll_content_container . get_child_count ( ) == 0 :
return
2023-11-23 07:38:10 +01:00
var category : Category
for i : String in categories :
var c : Category = categories [ i ]
if c . get_highlighted_item ( ) != - 1 :
category = c
break
node_selected . emit ( category . get_item_metadata ( category . get_highlighted_item ( ) , " type " ) )
func _on_category_collapse_toggled ( collapsed : bool , category : String ) - > void :
if collapsed :
collapsed_categories . append ( category )
else :
collapsed_categories . erase ( category )
2023-11-25 11:40:53 +01:00
## A collapsible item menu.
##
## An analog to [Tree] and [ItemList] made with nodes. Allows collapsing its
## children [AddNodeMenu.CategoryItem] nodes and highlighting them.[br]
## [b]Note:[/b] only one [AddNodeMenu.CategoryItem] can be highlighted at any time.
2023-11-23 07:38:10 +01:00
class Category extends VBoxContainer :
const COLLAPSE_ICON : = preload ( " res://graph_node_renderer/textures/collapse-icon.svg " )
const COLLAPSE_ICON_COLLAPSED : = preload ( " res://graph_node_renderer/textures/collapse-icon-collapsed.svg " )
var collapse_button : Button
2023-11-25 11:40:53 +01:00
## Emitted when a child item has been pressed.
2023-11-23 07:38:10 +01:00
signal item_pressed ( item : int )
2023-11-25 11:40:53 +01:00
## Emitted when a child item's favorite button has been pressed.
2023-11-23 07:38:10 +01:00
signal item_favorite_button_toggled ( item : int , toggled : bool )
2023-11-25 11:40:53 +01:00
## Emitted when the category's collapsed state has been toggled.
2023-11-23 07:38:10 +01:00
signal collapse_toggled ( collapsed : bool )
func _init ( p_name : String ) - > void :
collapse_button = Button . new ( )
collapse_button . alignment = HORIZONTAL_ALIGNMENT_LEFT
collapse_button . icon = COLLAPSE_ICON
collapse_button . toggle_mode = true
collapse_button . flat = true
collapse_button . size_flags_vertical = Control . SIZE_EXPAND_FILL
collapse_button . text = p_name
collapse_button . toggled . connect (
func ( toggled : bool ) :
collapse_toggled . emit ( toggled )
)
add_child ( collapse_button , false , Node . INTERNAL_MODE_FRONT )
renamed . connect ( func ( ) :
collapse_button . name = name
)
collapse_button . toggled . connect ( set_collapsed )
2023-11-25 11:40:53 +01:00
## If [param collapsed] is [code]true[/code], collapses the category, hiding its children.
2023-11-23 07:38:10 +01:00
func set_collapsed ( collapsed : bool ) - > void :
collapse_button . icon = COLLAPSE_ICON_COLLAPSED if collapsed else COLLAPSE_ICON
collapse_button . set_pressed_no_signal ( collapsed )
for c : CategoryItem in get_children ( ) :
2024-02-21 07:11:29 +01:00
c . visible = not collapsed
2023-11-23 07:38:10 +01:00
2023-11-25 11:40:53 +01:00
## Add an item to the category.
2023-11-23 07:38:10 +01:00
func add_item ( p_name : String , p_tooltip : String , p_favorite : bool = false ) - > void :
var item : = CategoryItem . new ( p_name , p_tooltip , p_favorite )
item . favorite_toggled . connect (
func ( toggled : bool ) :
item_favorite_button_toggled . emit ( item . get_index ( ) , toggled )
)
item . pressed . connect (
func ( ) :
item_pressed . emit ( item . get_index ( ) )
)
add_child ( item )
2023-11-25 11:40:53 +01:00
## Set an item's metadata at index [param item].
2023-11-23 07:38:10 +01:00
func set_item_metadata ( item : int , key : StringName , metadata : Variant ) - > void :
get_child ( item ) . set_meta ( key , metadata )
2023-11-25 11:40:53 +01:00
## Retrieve an item's metadata at index [param item].
2023-11-23 07:38:10 +01:00
func get_item_metadata ( item : int , key : StringName ) - > Variant :
return get_child ( item ) . get_meta ( key )
2023-11-25 11:40:53 +01:00
## Get the amount of items in this category.
2023-11-23 07:38:10 +01:00
func get_item_count ( ) - > int :
return get_child_count ( )
2023-11-25 11:40:53 +01:00
## Toggle an item at index [param item]'s favorite state.
2023-11-23 07:38:10 +01:00
func set_item_favorite ( item : int , favorite : bool ) - > void :
var _item : = get_child ( item ) as CategoryItem
_item . set_favorite ( favorite )
2023-11-25 11:40:53 +01:00
## Returns [code]true[/code] if the item at [param item] is marked as favorite.
2023-11-23 07:38:10 +01:00
func is_item_favorite ( item : int ) - > bool :
var _item : = get_child ( item ) as CategoryItem
return _item . is_favorite ( )
2023-11-25 11:40:53 +01:00
## Highlight item at index [item], and unhighlight all other items.
2023-11-23 07:38:10 +01:00
func highlight_item ( item : int ) - > void :
for c : CategoryItem in get_children ( ) :
c . set_highlighted ( c . get_index ( ) == item )
2023-11-25 11:40:53 +01:00
## Unhighlight all items.
2023-11-23 07:38:10 +01:00
func unhighlight_all ( ) - > void :
for c : CategoryItem in get_children ( ) :
c . set_highlighted ( false )
2023-11-25 11:40:53 +01:00
## Returns the index of the currently highlighted item. Returns [code]-1[/code] if no item is highlighted in this category.
2023-11-23 07:38:10 +01:00
func get_highlighted_item ( ) - > int :
for c : CategoryItem in get_children ( ) :
if c . is_highlighted :
return c . get_index ( )
return - 1
2023-11-25 12:28:57 +01:00
func is_collapsed ( ) - > bool :
return collapse_button . button_pressed
2023-11-25 11:40:53 +01:00
## Represents an item in a [AddNodeMenu.Category].
##
## A selectable and highlightable category item with a favorite button.
2023-11-23 07:38:10 +01:00
class CategoryItem extends HBoxContainer :
const FAVORITE_ICON : = preload ( " res://graph_node_renderer/textures/favorite-icon.svg " )
const NON_FAVORITE_ICON : = preload ( " res://graph_node_renderer/textures/non-favorite-icon.svg " )
const ITEM_MARGIN : = 16
2023-11-25 11:40:53 +01:00
## The stylebox to use if this item is highlighted.
2023-11-23 07:38:10 +01:00
var highlighted_stylebox : = StyleBoxFlat . new ( )
var is_highlighted : bool
var fav_button : Button
var name_button : Button
var panel : PanelContainer
2023-11-25 11:40:53 +01:00
## Emitted when this item has been pressed.
2023-11-23 07:38:10 +01:00
signal pressed
2023-11-25 11:40:53 +01:00
## Emitted when this item's [member fav_button] has been pressed.
2023-11-23 07:38:10 +01:00
signal favorite_toggled ( toggled : bool )
func _init ( p_name : String , p_tooltip : String , p_favorite : bool ) - > void :
fav_button = Button . new ( )
fav_button . icon = FAVORITE_ICON if p_favorite else NON_FAVORITE_ICON
fav_button . toggle_mode = true
fav_button . set_pressed_no_signal ( p_favorite )
fav_button . flat = true
fav_button . toggled . connect (
func ( toggled : bool ) :
favorite_toggled . emit ( toggled )
)
fav_button . toggled . connect ( set_favorite )
name_button = Button . new ( )
name_button . size_flags_horizontal = Control . SIZE_EXPAND_FILL
name_button . text = p_name
name_button . flat = true
name_button . alignment = HORIZONTAL_ALIGNMENT_LEFT
name_button . tooltip_text = p_tooltip
name_button . pressed . connect (
func ( ) :
pressed . emit ( )
)
var mc : = MarginContainer . new ( )
mc . add_theme_constant_override ( & " margin_left " , ITEM_MARGIN )
panel = PanelContainer . new ( )
panel . size_flags_horizontal = Control . SIZE_EXPAND_FILL
panel . add_theme_stylebox_override ( & " panel " , highlighted_stylebox )
highlighted_stylebox . bg_color = Color ( 0.0 , 0.0 , 0.0 , 0.15 )
panel . self_modulate = Color . TRANSPARENT
var inner_hb : = HBoxContainer . new ( )
inner_hb . add_child ( fav_button )
inner_hb . add_child ( name_button )
panel . add_child ( inner_hb )
add_child ( mc )
add_child ( panel )
2023-11-25 11:40:53 +01:00
## Toggle this item's favorite state.
2023-11-23 07:38:10 +01:00
func set_favorite ( favorite : bool ) - > void :
fav_button . icon = FAVORITE_ICON if favorite else NON_FAVORITE_ICON
fav_button . set_pressed_no_signal ( favorite )
2023-11-25 11:40:53 +01:00
## Returns [code]true[/code] if this item is marked as favorite.
2023-11-23 07:38:10 +01:00
func is_favorite ( ) - > bool :
return fav_button . icon == FAVORITE_ICON
2023-11-25 11:40:53 +01:00
## Toggle this item's highlighted state.
2023-11-23 07:38:10 +01:00
func set_highlighted ( highlighted : bool ) - > void :
is_highlighted = highlighted
if highlighted :
panel . self_modulate = Color . WHITE
else :
panel . self_modulate = Color . TRANSPARENT