add node menu (#3)

we finally have a way to add nodes from a menu!! lets goooooooo

Reviewed-on: https://codeberg.org/Eroax/Re-DotDeck/pulls/3
Co-authored-by: Lera Elvoé <yagich@poto.cafe>
Co-committed-by: Lera Elvoé <yagich@poto.cafe>
This commit is contained in:
Lera Elvoé 2023-11-23 06:38:10 +00:00 committed by yagich
parent 882d817310
commit eeb509cae0
24 changed files with 891 additions and 61 deletions

View file

@ -15,6 +15,8 @@ var node_type: String
var description: String var description: String
var aliases: Array[String] var aliases: Array[String]
var category: String
var appears_in_search: bool = true
var props_to_serialize: Array[StringName] var props_to_serialize: Array[StringName]

View file

@ -2,12 +2,16 @@ extends Node
const BASE_NODE_PATH := "res://classes/deck/nodes/" const BASE_NODE_PATH := "res://classes/deck/nodes/"
const NODE_INDEX_CACHE_PATH := "user://nodes_index.json" const NODE_INDEX_CACHE_PATH := "user://nodes_index.json"
const FAVORITE_NODES_PATH := "user://favorite_nodes.json"
var favorite_nodes: Array[String]
# Dictionary[node_type, NodeDescriptor] # Dictionary[node_type, NodeDescriptor]
var nodes: Dictionary = {} var nodes: Dictionary = {}
func _init() -> void: func _init() -> void:
load_favorites()
#if load_node_index(): #if load_node_index():
#return #return
@ -23,7 +27,15 @@ func _init() -> void:
func(accum, el) -> void: func(accum, el) -> void:
accum += el accum += el
, "") , "")
var descriptor := NodeDescriptor.new(script_path, node.name, node.description, aliases) var descriptor := NodeDescriptor.new(
script_path,
node.name,
node.node_type,
node.description,
aliases,
node.category,
node.appears_in_search,
)
nodes[node.node_type] = descriptor nodes[node.node_type] = descriptor
current_file = dir.get_next() current_file = dir.get_next()
@ -68,28 +80,71 @@ func load_node_index() -> bool:
return true return true
func set_node_favorite(node_type: String, favorite: bool) -> void:
if (favorite && node_type in favorite_nodes) || (!favorite && !(node_type in favorite_nodes)):
return
if favorite:
favorite_nodes.append(node_type)
else:
favorite_nodes.erase(node_type)
var f := FileAccess.open(FAVORITE_NODES_PATH, FileAccess.WRITE)
f.store_string(JSON.stringify(favorite_nodes, "\t"))
func load_favorites() -> void:
var f := FileAccess.open(FAVORITE_NODES_PATH, FileAccess.READ)
if !f:
return
var data: Array = JSON.parse_string(f.get_as_text())
favorite_nodes.clear()
favorite_nodes.assign(data)
func is_node_favorite(node_type: String) -> bool:
return node_type in favorite_nodes
class NodeDescriptor: class NodeDescriptor:
var name: String var name: String
var type: String
var description: String var description: String
var aliases: String var aliases: String
var category: String
var appears_in_search: bool
var script_path: String var script_path: String
func _init(p_script_path: String, p_name: String, p_description: String, p_aliases: String) -> void: func _init(
script_path = p_script_path p_script_path: String,
p_name: String,
p_type: String,
p_description: String,
p_aliases: String,
p_category: String,
p_appears_in_search: bool,
) -> void:
script_path = p_script_path
name = p_name name = p_name
description = p_description type = p_type
aliases = p_aliases description = p_description
aliases = p_aliases
category = p_category
appears_in_search = p_appears_in_search
func to_dictionary() -> Dictionary: func to_dictionary() -> Dictionary:
var d := { var d := {
"name": name, "name": name,
"type": type,
"description": description, "description": description,
"aliases": aliases, "aliases": aliases,
"script_path": script_path "script_path": script_path,
"category": category,
"appears_in_search": appears_in_search,
} }
return d return d
@ -98,7 +153,11 @@ class NodeDescriptor:
var nd := NodeDescriptor.new( var nd := NodeDescriptor.new(
data.get("script_path", ""), data.get("script_path", ""),
data.get("name", ""), data.get("name", ""),
data.get("type", ""),
data.get("description", ""), data.get("description", ""),
data.get("aliases", "")) data.get("aliases", ""),
data.get("category", ""),
data.get("appears_in_search", false),
)
return nd return nd

View file

@ -5,6 +5,7 @@ func _init() -> void:
name = "Button" name = "Button"
node_type = "button" node_type = "button"
description = "a button" description = "a button"
category = "general"
add_output_port( add_output_port(
Deck.Types.BOOL, Deck.Types.BOOL,

View file

@ -5,6 +5,7 @@ func _init() -> void:
name = "Get Deck Var" name = "Get Deck Var"
node_type = name.to_snake_case() node_type = name.to_snake_case()
description = "retrieve a deck variable" description = "retrieve a deck variable"
category = "general"
add_output_port( add_output_port(
Deck.Types.STRING, Deck.Types.STRING,

View file

@ -9,6 +9,7 @@ func _init() -> void:
name = "Group input" name = "Group input"
node_type = "group_input" node_type = "group_input"
props_to_serialize = [&"output_count"] props_to_serialize = [&"output_count"]
appears_in_search = false
add_output_port( add_output_port(
Deck.Types.STRING, Deck.Types.STRING,

View file

@ -11,6 +11,7 @@ func _init() -> void:
name = "Group" name = "Group"
node_type = "group_node" node_type = "group_node"
props_to_serialize = [&"group_id", &"extra_ports"] props_to_serialize = [&"group_id", &"extra_ports"]
appears_in_search = false
func _pre_connection() -> void: func _pre_connection() -> void:

View file

@ -9,6 +9,7 @@ func _init() -> void:
name = "Group output" name = "Group output"
node_type = "group_output" node_type = "group_output"
props_to_serialize = [&"input_count"] props_to_serialize = [&"input_count"]
appears_in_search = false
add_input_port( add_input_port(
Deck.Types.STRING, Deck.Types.STRING,

View file

@ -9,6 +9,7 @@ func _init() -> void:
description = "print a value" description = "print a value"
props_to_serialize = [&"times_activated"] props_to_serialize = [&"times_activated"]
category = "general"
add_input_port( add_input_port(
Deck.Types.STRING, Deck.Types.STRING,

View file

@ -5,6 +5,7 @@ func _init() -> void:
name = "Set Deck Var" name = "Set Deck Var"
node_type = name.to_snake_case() node_type = name.to_snake_case()
description = "set deck variable" description = "set deck variable"
category = "general"
add_input_port( add_input_port(
Deck.Types.STRING, Deck.Types.STRING,

View file

@ -4,6 +4,8 @@ extends DeckNode
func _init() -> void: func _init() -> void:
node_type = "string_constant" node_type = "string_constant"
name = "String Constant" name = "String Constant"
category = "general"
add_output_port( add_output_port(
Deck.Types.STRING, Deck.Types.STRING,
"Text", "Text",

View file

@ -4,6 +4,7 @@ extends DeckNode
func _init() -> void: func _init() -> void:
node_type = "test_interleaved" node_type = "test_interleaved"
name = "Test Interleaved" name = "Test Interleaved"
category = "test"
for i in 4: for i in 4:
add_output_port( add_output_port(

View file

@ -0,0 +1,99 @@
class_name SearchProvider
static var filters: Array[Filter] = [
# favorites filter. will only show nodes marked as favorite. syntax: "#f"
Filter.new(
func(search_string: String) -> bool:
return "#f" in search_string,
func(element: NodeDB.NodeDescriptor, _search_string: String, _pre_strip_string: String) -> bool:
return NodeDB.is_node_favorite(element.type),
func(search_string: String) -> String:
return search_string.replace("#f", "")
),
# category filter. will only match nodes that are in a certain category. syntax: "#c category_name"
Filter.new(
func(search_string: String) -> bool:
const p := r"#c\s[\w]+"
var r := RegEx.create_from_string(p)
var c := r.search(search_string)
return c != null,
func(element: NodeDB.NodeDescriptor, _search_string: String, pre_strip_string: String) -> bool:
const p := r"#c\s[\w]+"
var r := RegEx.create_from_string(p)
print("pre: ", pre_strip_string)
var c := r.search(pre_strip_string).get_string().split("#c ", false)[0]
return c.is_subsequence_ofn(element.category),
func(search_string: String) -> String:
const p := r"#c\s[\w]+"
var r := RegEx.create_from_string(p)
var c := r.search(search_string).get_string()
prints("c:", c, "r:", search_string.replace(c, ""))
return search_string.replace(c, "")
),
]
static func search(term: String) -> Array[NodeDB.NodeDescriptor]:
var res: Array[NodeDB.NodeDescriptor] = []
var filters_to_apply := filters.filter(
func(f: Filter):
return f.should_apply.call(term)
)
var cleaned_search_string := term
# strip string of filter-specific substrings
for f: Filter in filters_to_apply:
f.pre_strip_string = cleaned_search_string
cleaned_search_string = f.strip_string.call(cleaned_search_string)
cleaned_search_string = cleaned_search_string.strip_edges()
for node_type: String in NodeDB.nodes:
var nd: NodeDB.NodeDescriptor = NodeDB.nodes[node_type]
if !nd.appears_in_search:
continue
var full_search_string := nd.name + nd.aliases
if cleaned_search_string.is_subsequence_ofn(full_search_string):
res.append(nd)
# no filters apply, just return the results straight
if filters_to_apply.is_empty():
return res
# apply filters
var filtered_res: Array[NodeDB.NodeDescriptor] = res.duplicate()
for f: Filter in filters_to_apply:
filtered_res = filtered_res.filter(f.match_term.bind(cleaned_search_string, f.pre_strip_string))
return filtered_res
class Filter:
## Return [code]true[/code] if this filter should be applied to the search.[br]
## [code]Callable(search_string: String) -> bool[/code]
var should_apply: Callable
## Return a [code]bool[/code] if the provided [code]NodeDescriptor[/code]
## should be included in the search results array.[br]
## [code]Callable(element: NodeDB.NodeDescriptor, search_string: String, pre_strip_string: String) -> bool[/code]
var match_term: Callable
## Return a string that's stripped of this filter's shorthand.
## [code]Callable(search_string: String) -> String[/code]
var strip_string: Callable
## The search string as it was before [member strip_string] was called. Useful for filters that use arguments, like the category filter.
var pre_strip_string: String
func _init(p_should_apply: Callable, p_match_term: Callable, p_strip_string: Callable) -> void:
should_apply = p_should_apply
match_term = p_match_term
strip_string = p_strip_string

View file

@ -0,0 +1,306 @@
extends MarginContainer
class_name AddNodeMenu
@onready var search_line_edit: LineEdit = %SearchLineEdit
@onready var scroll_content_container: VBoxContainer = %ScrollContentContainer
@onready var scroll_container: ScrollContainer = $VBoxContainer/ScrollContainer
var categories: Dictionary = {} # Dictionary[String, Category]
var collapsed_categories: Array[String]
signal node_selected(type: String)
func _ready() -> void:
search("")
func add_category(category_name: String) -> void:
var c := Category.new(category_name.capitalize())
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)
)
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)
func add_item(category: String, item: String, tooltip: String = "", favorite: bool = false) -> void:
if !categories.has(category):
add_category(category)
add_category_item(category, item, tooltip, favorite)
func get_category(category: String) -> Category:
return categories[category]
func focus_search_bar() -> void:
search_line_edit.select_all()
search_line_edit.grab_focus()
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)
func _on_search_line_edit_gui_input(event: InputEvent) -> void:
if event.is_action_pressed("ui_down"):
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()
var nc: Category
if category.get_index() + 1 == scroll_content_container.get_child_count():
# reached the end, get the first category
nc = scroll_content_container.get_child(0)
else:
# there is another category after this
nc = scroll_content_container.get_child(category.get_index() + 1)
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"):
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()
var nc: Category
if category.get_index() - 1 == -1:
# reached the beginning, get the last category
nc = scroll_content_container.get_child(scroll_content_container.get_child_count() - 1)
else:
# there is another category before this
nc = scroll_content_container.get_child(category.get_index() - 1)
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))
func _on_search_line_edit_text_submitted(_new_text: String) -> void:
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)
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
signal item_pressed(item: int)
signal item_favorite_button_toggled(item: int, toggled: bool)
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)
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():
c.visible = !collapsed
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)
func set_item_metadata(item: int, key: StringName, metadata: Variant) -> void:
get_child(item).set_meta(key, metadata)
func get_item_metadata(item: int, key: StringName) -> Variant:
return get_child(item).get_meta(key)
func get_item_count() -> int:
return get_child_count()
func set_item_favorite(item:int, favorite: bool) -> void:
var _item := get_child(item) as CategoryItem
_item.set_favorite(favorite)
func is_item_favorite(item: int) -> bool:
var _item := get_child(item) as CategoryItem
return _item.is_favorite()
func highlight_item(item: int) -> void:
for c: CategoryItem in get_children():
c.set_highlighted(c.get_index() == item)
func unhighlight_all() -> void:
for c: CategoryItem in get_children():
c.set_highlighted(false)
func get_highlighted_item() -> int:
for c: CategoryItem in get_children():
if c.is_highlighted:
return c.get_index()
return -1
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
var highlighted_stylebox := StyleBoxFlat.new()
var is_highlighted: bool
var fav_button: Button
var name_button: Button
var panel: PanelContainer
signal pressed
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)
func set_favorite(favorite: bool) -> void:
fav_button.icon = FAVORITE_ICON if favorite else NON_FAVORITE_ICON
fav_button.set_pressed_no_signal(favorite)
func is_favorite() -> bool:
return fav_button.icon == FAVORITE_ICON
func set_highlighted(highlighted: bool) -> void:
is_highlighted = highlighted
if highlighted:
panel.self_modulate = Color.WHITE
else:
panel.self_modulate = Color.TRANSPARENT

View file

@ -0,0 +1,44 @@
[gd_scene load_steps=2 format=3 uid="uid://dqp08ahaho0q2"]
[ext_resource type="Script" path="res://graph_node_renderer/add_node_menu.gd" id="1_m7f7p"]
[node name="AddNodeMenu" type="MarginContainer"]
offset_right = 436.0
offset_bottom = 57.0
theme_override_constants/margin_left = 3
theme_override_constants/margin_top = 3
theme_override_constants/margin_right = 3
theme_override_constants/margin_bottom = 3
script = ExtResource("1_m7f7p")
[node name="VBoxContainer" type="VBoxContainer" parent="."]
layout_mode = 2
size_flags_horizontal = 3
size_flags_vertical = 3
[node name="SearchLineEdit" type="LineEdit" parent="VBoxContainer"]
unique_name_in_owner = true
layout_mode = 2
placeholder_text = "Search"
clear_button_enabled = true
[node name="HSeparator" type="HSeparator" parent="VBoxContainer"]
layout_mode = 2
[node name="ScrollContainer" type="ScrollContainer" parent="VBoxContainer"]
layout_mode = 2
size_flags_vertical = 3
[node name="MarginContainer" type="MarginContainer" parent="VBoxContainer/ScrollContainer"]
layout_mode = 2
size_flags_horizontal = 3
theme_override_constants/margin_right = 6
[node name="ScrollContentContainer" type="VBoxContainer" parent="VBoxContainer/ScrollContainer/MarginContainer"]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 3
[connection signal="gui_input" from="VBoxContainer/SearchLineEdit" to="." method="_on_search_line_edit_gui_input"]
[connection signal="text_changed" from="VBoxContainer/SearchLineEdit" to="." method="search"]
[connection signal="text_submitted" from="VBoxContainer/SearchLineEdit" to="." method="_on_search_line_edit_text_submitted"]

View file

@ -2,6 +2,13 @@ extends GraphEdit
class_name DeckRendererGraphEdit class_name DeckRendererGraphEdit
const NODE_SCENE := preload("res://graph_node_renderer/deck_node_renderer_graph_node.tscn") const NODE_SCENE := preload("res://graph_node_renderer/deck_node_renderer_graph_node.tscn")
const ADD_NODE_MENU_SCENE := preload("res://graph_node_renderer/add_node_menu.tscn")
var search_popup_panel: PopupPanel
var add_node_menu: AddNodeMenu
@export var add_node_popup_size: Vector2i = Vector2i(500, 300)
var popup_position: Vector2
var deck: Deck: var deck: Deck:
set(v): set(v):
@ -13,60 +20,13 @@ signal group_enter_requested(group_id: String)
func _ready() -> void: func _ready() -> void:
var add_button := Button.new() add_node_menu = ADD_NODE_MENU_SCENE.instantiate()
add_button.text = "Button" search_popup_panel = PopupPanel.new()
var add_print := Button.new() search_popup_panel.add_child(add_node_menu)
add_print.text = "Print" search_popup_panel.size = add_node_popup_size
var get_var := Button.new() add_child(search_popup_panel, false, Node.INTERNAL_MODE_BACK)
get_var.text = "Get Var"
var set_var := Button.new()
set_var.text = "Set Var"
var test := Button.new()
test.text = "Interleaved"
var str_const := Button.new()
str_const.text = "String"
get_menu_hbox().add_child(add_button)
get_menu_hbox().add_child(add_print)
get_menu_hbox().add_child(get_var)
get_menu_hbox().add_child(set_var)
get_menu_hbox().add_child(test)
get_menu_hbox().add_child(str_const)
add_button.pressed.connect( add_node_menu.node_selected.connect(_on_add_node_menu_node_selected)
func():
var node := NodeDB.instance_node("button")
deck.add_node_inst(node)
)
add_print.pressed.connect(
func():
var node := NodeDB.instance_node("print")
deck.add_node_inst(node)
)
get_var.pressed.connect(
func():
var node := NodeDB.instance_node("get_deck_var")
deck.add_node_inst(node)
)
set_var.pressed.connect(
func():
var node := NodeDB.instance_node("set_deck_var")
deck.add_node_inst(node)
)
test.pressed.connect(
func():
var node := NodeDB.instance_node("test_interleaved")
deck.add_node_inst(node)
)
str_const.pressed.connect(
func():
var node := NodeDB.instance_node("string_constant")
deck.add_node_inst(node)
)
connection_request.connect(attempt_connection) connection_request.connect(attempt_connection)
disconnection_request.connect(attempt_disconnect) disconnection_request.connect(attempt_disconnect)
@ -105,6 +65,14 @@ func attempt_disconnect(from_node_name: StringName, from_port: int, to_node_name
) )
func get_node_renderer(node: DeckNode) -> DeckNodeRendererGraphNode:
for i: DeckNodeRendererGraphNode in get_children().slice(1):
if i.node == node:
return i
return null
func _on_scroll_offset_changed(offset: Vector2) -> void: func _on_scroll_offset_changed(offset: Vector2) -> void:
deck.set_meta("offset", offset) deck.set_meta("offset", offset)
@ -210,3 +178,24 @@ func _input(event: InputEvent) -> void:
print("tried to enter group") print("tried to enter group")
group_enter_requested.emit((get_selected_nodes()[0] as DeckNodeRendererGraphNode).node.group_id) group_enter_requested.emit((get_selected_nodes()[0] as DeckNodeRendererGraphNode).node.group_id)
get_viewport().set_input_as_handled() get_viewport().set_input_as_handled()
func _on_popup_request(p_popup_position: Vector2) -> void:
var p := get_viewport_rect().position + get_global_mouse_position()
p += Vector2(10, 10)
var r := Rect2i(p, search_popup_panel.size)
search_popup_panel.popup_on_parent(r)
add_node_menu.focus_search_bar()
popup_position = p_popup_position
func _on_add_node_menu_node_selected(type: String) -> void:
var node := NodeDB.instance_node(type) as DeckNode
deck.add_node_inst(node)
var node_pos := ((scroll_offset + popup_position) / zoom)
if snapping_enabled:
node_pos = node_pos.snapped(Vector2(snapping_distance, snapping_distance))
get_node_renderer(node).position_offset = node_pos
search_popup_panel.hide()

View file

@ -11,4 +11,5 @@ grow_vertical = 2
right_disconnects = true right_disconnects = true
script = ExtResource("1_pojfs") script = ExtResource("1_pojfs")
[connection signal="popup_request" from="." to="." method="_on_popup_request"]
[connection signal="scroll_offset_changed" from="." to="." method="_on_scroll_offset_changed"] [connection signal="scroll_offset_changed" from="." to="." method="_on_scroll_offset_changed"]

View file

@ -0,0 +1,85 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="16"
height="16"
viewBox="0 0 4.2333332 4.2333333"
version="1.1"
id="svg1"
inkscape:version="1.3 (0e150ed6c4, 2023-07-21)"
sodipodi:docname="collapse-icon-collapsed.svg"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<sodipodi:namedview
id="namedview1"
pagecolor="#000000"
bordercolor="#000000"
borderopacity="0.25"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
inkscape:document-units="px"
showgrid="true"
inkscape:zoom="23.147924"
inkscape:cx="8.9208864"
inkscape:cy="7.9920774"
inkscape:window-width="1920"
inkscape:window-height="1016"
inkscape:window-x="1440"
inkscape:window-y="36"
inkscape:window-maximized="1"
inkscape:current-layer="layer1">
<sodipodi:guide
position="0,4.2333332"
orientation="0,16"
id="guide1"
inkscape:locked="false" />
<sodipodi:guide
position="4.2333332,4.2333332"
orientation="16,0"
id="guide2"
inkscape:locked="false" />
<sodipodi:guide
position="4.2333332,0"
orientation="0,-16"
id="guide3"
inkscape:locked="false" />
<sodipodi:guide
position="0,0"
orientation="-16,0"
id="guide4"
inkscape:locked="false" />
<inkscape:grid
id="grid4"
units="px"
originx="0"
originy="0"
spacingx="0.26458333"
spacingy="0.26458333"
empcolor="#0099e5"
empopacity="0.30196078"
color="#0099e5"
opacity="0.14901961"
empspacing="4"
dotted="false"
gridanglex="30"
gridanglez="30"
visible="true" />
</sodipodi:namedview>
<defs
id="defs1" />
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1">
<path
style="fill:none;fill-opacity:1;stroke:#ffffff;stroke-width:0.529167;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
d="M 1.3229241,3.704159 2.910424,2.3812424 1.3229241,1.0583258"
id="path4"
sodipodi:nodetypes="ccc" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.4 KiB

View file

@ -0,0 +1,37 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://5gwj26ab5t6s"
path="res://.godot/imported/collapse-icon-collapsed.svg-811c248a92d92ca04d781df393765972.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://graph_node_renderer/textures/collapse-icon-collapsed.svg"
dest_files=["res://.godot/imported/collapse-icon-collapsed.svg-811c248a92d92ca04d781df393765972.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1
svg/scale=1.0
editor/scale_with_editor_scale=false
editor/convert_colors_with_editor_theme=false

View file

@ -0,0 +1,85 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="16"
height="16"
viewBox="0 0 4.2333332 4.2333333"
version="1.1"
id="svg1"
inkscape:version="1.3 (0e150ed6c4, 2023-07-21)"
sodipodi:docname="collapse-icon.svg"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<sodipodi:namedview
id="namedview1"
pagecolor="#000000"
bordercolor="#000000"
borderopacity="0.25"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
inkscape:document-units="px"
showgrid="true"
inkscape:zoom="11.573962"
inkscape:cx="-3.8448372"
inkscape:cy="7.6896745"
inkscape:window-width="1920"
inkscape:window-height="1016"
inkscape:window-x="1440"
inkscape:window-y="36"
inkscape:window-maximized="1"
inkscape:current-layer="layer1">
<sodipodi:guide
position="0,4.2333332"
orientation="0,16"
id="guide1"
inkscape:locked="false" />
<sodipodi:guide
position="4.2333332,4.2333332"
orientation="16,0"
id="guide2"
inkscape:locked="false" />
<sodipodi:guide
position="4.2333332,0"
orientation="0,-16"
id="guide3"
inkscape:locked="false" />
<sodipodi:guide
position="0,0"
orientation="-16,0"
id="guide4"
inkscape:locked="false" />
<inkscape:grid
id="grid4"
units="px"
originx="0"
originy="0"
spacingx="0.26458333"
spacingy="0.26458333"
empcolor="#0099e5"
empopacity="0.30196078"
color="#0099e5"
opacity="0.14901961"
empspacing="4"
dotted="false"
gridanglex="30"
gridanglez="30"
visible="true" />
</sodipodi:namedview>
<defs
id="defs1" />
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1">
<path
style="fill:none;fill-opacity:1;stroke:#ffffff;stroke-width:0.529167;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
d="M 0.79374997,1.5874999 2.1166666,3.1749998 3.4395832,1.5874999"
id="path4"
sodipodi:nodetypes="ccc" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.4 KiB

View file

@ -0,0 +1,37 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://yrjhesfwqp6o"
path="res://.godot/imported/collapse-icon.svg-9bebc4e2d470036ab276875c18b86afd.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://graph_node_renderer/textures/collapse-icon.svg"
dest_files=["res://.godot/imported/collapse-icon.svg-9bebc4e2d470036ab276875c18b86afd.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1
svg/scale=1.0
editor/scale_with_editor_scale=false
editor/convert_colors_with_editor_theme=false

View file

@ -0,0 +1 @@
<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="M8 1.7 5.626 5.797 1 6.895l3.236 3.408-.359 4.673 4.14-1.977 4.157 1.942-.396-4.653L15 6.895l-4.626-1.098z" fill="#e0e0e0"/></svg>

After

Width:  |  Height:  |  Size: 223 B

View file

@ -0,0 +1,37 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://b4qr820crboug"
path="res://.godot/imported/favorite-icon.svg-9ed8d8558aa33c9d7305523c2c289a43.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://graph_node_renderer/textures/favorite-icon.svg"
dest_files=["res://.godot/imported/favorite-icon.svg-9ed8d8558aa33c9d7305523c2c289a43.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1
svg/scale=1.0
editor/scale_with_editor_scale=false
editor/convert_colors_with_editor_theme=false

View file

@ -0,0 +1 @@
<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="M8 1.725 5.625 5.822 1 6.92l3.236 3.406L3.876 15l4.14-1.977 4.158 1.942-.397-4.652L15 6.92l-4.625-1.098L8 1.725zM8 4l1.658 2.777 3.233.744-2.25 2.301.275 3.155-2.904-1.317L5.119 13l.252-3.168-2.262-2.31 3.233-.745L8 4z" fill="#e0e0e0"/></svg>

After

Width:  |  Height:  |  Size: 335 B

View file

@ -0,0 +1,37 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://c3sndmgw0mfpb"
path="res://.godot/imported/non-favorite-icon.svg-7d5ddab226c43aa43af383d3db45c122.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://graph_node_renderer/textures/non-favorite-icon.svg"
dest_files=["res://.godot/imported/non-favorite-icon.svg-7d5ddab226c43aa43af383d3db45c122.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1
svg/scale=1.0
editor/scale_with_editor_scale=false
editor/convert_colors_with_editor_theme=false