mirror of
https://codeberg.org/StreamGraph/StreamGraph.git
synced 2024-11-13 19:49:55 +01:00
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:
parent
882d817310
commit
eeb509cae0
24 changed files with 891 additions and 61 deletions
|
@ -15,6 +15,8 @@ var node_type: String
|
|||
|
||||
var description: String
|
||||
var aliases: Array[String]
|
||||
var category: String
|
||||
var appears_in_search: bool = true
|
||||
|
||||
var props_to_serialize: Array[StringName]
|
||||
|
||||
|
|
|
@ -2,12 +2,16 @@ extends Node
|
|||
|
||||
const BASE_NODE_PATH := "res://classes/deck/nodes/"
|
||||
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]
|
||||
var nodes: Dictionary = {}
|
||||
|
||||
|
||||
func _init() -> void:
|
||||
load_favorites()
|
||||
#if load_node_index():
|
||||
#return
|
||||
|
||||
|
@ -23,7 +27,15 @@ func _init() -> void:
|
|||
func(accum, el) -> void:
|
||||
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
|
||||
current_file = dir.get_next()
|
||||
|
||||
|
@ -68,28 +80,71 @@ func load_node_index() -> bool:
|
|||
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:
|
||||
var name: String
|
||||
var type: String
|
||||
var description: String
|
||||
var aliases: String
|
||||
var category: String
|
||||
var appears_in_search: bool
|
||||
|
||||
var script_path: String
|
||||
|
||||
|
||||
func _init(p_script_path: String, p_name: String, p_description: String, p_aliases: String) -> void:
|
||||
script_path = p_script_path
|
||||
func _init(
|
||||
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
|
||||
description = p_description
|
||||
aliases = p_aliases
|
||||
name = p_name
|
||||
type = p_type
|
||||
description = p_description
|
||||
aliases = p_aliases
|
||||
category = p_category
|
||||
appears_in_search = p_appears_in_search
|
||||
|
||||
|
||||
func to_dictionary() -> Dictionary:
|
||||
var d := {
|
||||
"name": name,
|
||||
"type": type,
|
||||
"description": description,
|
||||
"aliases": aliases,
|
||||
"script_path": script_path
|
||||
"script_path": script_path,
|
||||
"category": category,
|
||||
"appears_in_search": appears_in_search,
|
||||
}
|
||||
return d
|
||||
|
||||
|
@ -98,7 +153,11 @@ class NodeDescriptor:
|
|||
var nd := NodeDescriptor.new(
|
||||
data.get("script_path", ""),
|
||||
data.get("name", ""),
|
||||
data.get("type", ""),
|
||||
data.get("description", ""),
|
||||
data.get("aliases", ""))
|
||||
data.get("aliases", ""),
|
||||
data.get("category", ""),
|
||||
data.get("appears_in_search", false),
|
||||
)
|
||||
|
||||
return nd
|
||||
|
|
|
@ -5,6 +5,7 @@ func _init() -> void:
|
|||
name = "Button"
|
||||
node_type = "button"
|
||||
description = "a button"
|
||||
category = "general"
|
||||
|
||||
add_output_port(
|
||||
Deck.Types.BOOL,
|
||||
|
|
|
@ -5,6 +5,7 @@ func _init() -> void:
|
|||
name = "Get Deck Var"
|
||||
node_type = name.to_snake_case()
|
||||
description = "retrieve a deck variable"
|
||||
category = "general"
|
||||
|
||||
add_output_port(
|
||||
Deck.Types.STRING,
|
||||
|
|
|
@ -9,6 +9,7 @@ func _init() -> void:
|
|||
name = "Group input"
|
||||
node_type = "group_input"
|
||||
props_to_serialize = [&"output_count"]
|
||||
appears_in_search = false
|
||||
|
||||
add_output_port(
|
||||
Deck.Types.STRING,
|
||||
|
|
|
@ -11,6 +11,7 @@ func _init() -> void:
|
|||
name = "Group"
|
||||
node_type = "group_node"
|
||||
props_to_serialize = [&"group_id", &"extra_ports"]
|
||||
appears_in_search = false
|
||||
|
||||
|
||||
func _pre_connection() -> void:
|
||||
|
|
|
@ -9,6 +9,7 @@ func _init() -> void:
|
|||
name = "Group output"
|
||||
node_type = "group_output"
|
||||
props_to_serialize = [&"input_count"]
|
||||
appears_in_search = false
|
||||
|
||||
add_input_port(
|
||||
Deck.Types.STRING,
|
||||
|
|
|
@ -9,6 +9,7 @@ func _init() -> void:
|
|||
description = "print a value"
|
||||
|
||||
props_to_serialize = [&"times_activated"]
|
||||
category = "general"
|
||||
|
||||
add_input_port(
|
||||
Deck.Types.STRING,
|
||||
|
|
|
@ -5,6 +5,7 @@ func _init() -> void:
|
|||
name = "Set Deck Var"
|
||||
node_type = name.to_snake_case()
|
||||
description = "set deck variable"
|
||||
category = "general"
|
||||
|
||||
add_input_port(
|
||||
Deck.Types.STRING,
|
||||
|
|
|
@ -4,6 +4,8 @@ extends DeckNode
|
|||
func _init() -> void:
|
||||
node_type = "string_constant"
|
||||
name = "String Constant"
|
||||
category = "general"
|
||||
|
||||
add_output_port(
|
||||
Deck.Types.STRING,
|
||||
"Text",
|
||||
|
|
|
@ -4,6 +4,7 @@ extends DeckNode
|
|||
func _init() -> void:
|
||||
node_type = "test_interleaved"
|
||||
name = "Test Interleaved"
|
||||
category = "test"
|
||||
|
||||
for i in 4:
|
||||
add_output_port(
|
||||
|
|
99
classes/deck/search_provider.gd
Normal file
99
classes/deck/search_provider.gd
Normal 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
|
306
graph_node_renderer/add_node_menu.gd
Normal file
306
graph_node_renderer/add_node_menu.gd
Normal 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
|
44
graph_node_renderer/add_node_menu.tscn
Normal file
44
graph_node_renderer/add_node_menu.tscn
Normal 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"]
|
|
@ -2,6 +2,13 @@ extends GraphEdit
|
|||
class_name DeckRendererGraphEdit
|
||||
|
||||
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:
|
||||
set(v):
|
||||
|
@ -13,60 +20,13 @@ signal group_enter_requested(group_id: String)
|
|||
|
||||
|
||||
func _ready() -> void:
|
||||
var add_button := Button.new()
|
||||
add_button.text = "Button"
|
||||
var add_print := Button.new()
|
||||
add_print.text = "Print"
|
||||
var get_var := Button.new()
|
||||
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_node_menu = ADD_NODE_MENU_SCENE.instantiate()
|
||||
search_popup_panel = PopupPanel.new()
|
||||
search_popup_panel.add_child(add_node_menu)
|
||||
search_popup_panel.size = add_node_popup_size
|
||||
add_child(search_popup_panel, false, Node.INTERNAL_MODE_BACK)
|
||||
|
||||
add_button.pressed.connect(
|
||||
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)
|
||||
)
|
||||
add_node_menu.node_selected.connect(_on_add_node_menu_node_selected)
|
||||
|
||||
connection_request.connect(attempt_connection)
|
||||
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:
|
||||
deck.set_meta("offset", offset)
|
||||
|
||||
|
@ -210,3 +178,24 @@ func _input(event: InputEvent) -> void:
|
|||
print("tried to enter group")
|
||||
group_enter_requested.emit((get_selected_nodes()[0] as DeckNodeRendererGraphNode).node.group_id)
|
||||
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()
|
||||
|
|
|
@ -11,4 +11,5 @@ grow_vertical = 2
|
|||
right_disconnects = true
|
||||
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"]
|
||||
|
|
85
graph_node_renderer/textures/collapse-icon-collapsed.svg
Normal file
85
graph_node_renderer/textures/collapse-icon-collapsed.svg
Normal 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 |
|
@ -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
|
85
graph_node_renderer/textures/collapse-icon.svg
Normal file
85
graph_node_renderer/textures/collapse-icon.svg
Normal 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 |
37
graph_node_renderer/textures/collapse-icon.svg.import
Normal file
37
graph_node_renderer/textures/collapse-icon.svg.import
Normal 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
|
1
graph_node_renderer/textures/favorite-icon.svg
Normal file
1
graph_node_renderer/textures/favorite-icon.svg
Normal 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 |
37
graph_node_renderer/textures/favorite-icon.svg.import
Normal file
37
graph_node_renderer/textures/favorite-icon.svg.import
Normal 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
|
1
graph_node_renderer/textures/non-favorite-icon.svg
Normal file
1
graph_node_renderer/textures/non-favorite-icon.svg
Normal 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 |
37
graph_node_renderer/textures/non-favorite-icon.svg.import
Normal file
37
graph_node_renderer/textures/non-favorite-icon.svg.import
Normal 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
|
Loading…
Reference in a new issue