Skip to content

Commit 760a579

Browse files
authored
Merge pull request #206 from endlessm/T35564-add-block-builder
Add dynamic options lists for blocks
2 parents 4d2f4e9 + e812651 commit 760a579

39 files changed

+904
-652
lines changed

addons/block_code/block_code_plugin.gd

+1-6
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
@tool
22
extends EditorPlugin
3+
34
const MainPanelScene := preload("res://addons/block_code/ui/main_panel.tscn")
45
const MainPanel = preload("res://addons/block_code/ui/main_panel.gd")
56
const Types = preload("res://addons/block_code/types/types.gd")
@@ -93,16 +94,10 @@ func _exit_tree():
9394

9495

9596
func _ready():
96-
connect("scene_changed", _on_scene_changed)
9797
editor_inspector.connect("edited_object_changed", _on_editor_inspector_edited_object_changed)
98-
_on_scene_changed(EditorInterface.get_edited_scene_root())
9998
_on_editor_inspector_edited_object_changed()
10099

101100

102-
func _on_scene_changed(scene_root: Node):
103-
main_panel.switch_scene(scene_root)
104-
105-
106101
func _on_editor_inspector_edited_object_changed():
107102
var edited_object = editor_inspector.get_edited_object()
108103
#var edited_node = edited_object as Node
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
@tool
2+
extends BlockExtension
3+
4+
const OptionData = preload("res://addons/block_code/code_generation/option_data.gd")
5+
6+
7+
func get_defaults_for_node(context_node: Node) -> Dictionary:
8+
var animation_player = context_node as AnimationPlayer
9+
10+
if not animation_player:
11+
return {}
12+
13+
var animation_list = animation_player.get_animation_list()
14+
15+
return {"animation": OptionData.new(animation_list)}

addons/block_code/blocks/graphics/animationplayer_play.tres

+11-3
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,13 @@
1-
[gd_resource type="Resource" load_steps=4 format=3 uid="uid://c5e1byehtxwc0"]
1+
[gd_resource type="Resource" load_steps=6 format=3 uid="uid://c5e1byehtxwc0"]
22

33
[ext_resource type="Script" path="res://addons/block_code/code_generation/block_definition.gd" id="1_emeuv"]
44
[ext_resource type="Script" path="res://addons/block_code/code_generation/option_data.gd" id="1_xu43h"]
5+
[ext_resource type="Script" path="res://addons/block_code/blocks/graphics/animationplayer_play.gd" id="2_7ymgi"]
6+
7+
[sub_resource type="Resource" id="Resource_qpxn2"]
8+
script = ExtResource("1_xu43h")
9+
selected = 0
10+
items = []
511

612
[sub_resource type="Resource" id="Resource_vnp2w"]
713
script = ExtResource("1_xu43h")
@@ -16,14 +22,16 @@ description = "Play the animation."
1622
category = "Graphics | Animation"
1723
type = 2
1824
variant_type = 0
19-
display_template = "Play {animation: STRING} {direction: OPTION}"
20-
code_template = "if \"{direction}\" == \"ahead\":
25+
display_template = "Play {animation: STRING} {direction: NIL}"
26+
code_template = "if {direction} == \"ahead\":
2127
play({animation})
2228
else:
2329
play_backwards({animation})
2430
"
2531
defaults = {
32+
"animation": SubResource("Resource_qpxn2"),
2633
"direction": SubResource("Resource_vnp2w")
2734
}
2835
signal_name = ""
2936
scope = ""
37+
extension_script = ExtResource("2_7ymgi")

addons/block_code/blocks/logic/compare.tres

+2-2
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,8 @@ description = ""
1616
category = "Logic | Comparison"
1717
type = 3
1818
variant_type = 1
19-
display_template = "{float1: FLOAT} {op: OPTION} {float2: FLOAT}"
20-
code_template = "{float1} {op} {float2}"
19+
display_template = "{float1: FLOAT} {op: NIL} {float2: FLOAT}"
20+
code_template = "{float1} {{op}} {float2}"
2121
defaults = {
2222
"float1": 1.0,
2323
"float2": 1.0,

addons/block_code/blocks/sounds/pause_continue_sound.tres

+3-3
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
[sub_resource type="Resource" id="Resource_lalgp"]
77
script = ExtResource("1_ilhdq")
88
selected = 0
9-
items = ["Pause", "Continue"]
9+
items = ["pause", "continue"]
1010

1111
[resource]
1212
script = ExtResource("1_q04gm")
@@ -16,9 +16,9 @@ description = "Pause/Continue the audio stream"
1616
category = "Sounds"
1717
type = 2
1818
variant_type = 0
19-
display_template = "{pause: OPTION} the sound {name: STRING}"
19+
display_template = "{pause: NIL} the sound {name: STRING}"
2020
code_template = "var __sound_node = get_node({name})
21-
if \"{pause}\" == \"pause\":
21+
if {pause} == \"pause\":
2222
__sound_node.stream_paused = true
2323
else:
2424
__sound_node.stream_paused = false

addons/block_code/code_generation/block_ast.gd

+33-39
Original file line numberDiff line numberDiff line change
@@ -44,31 +44,15 @@ class ASTNode:
4444
children = []
4545
arguments = {}
4646

47-
func get_code_block() -> String:
48-
var code_block: String = data.code_template # get multiline code_template from block definition
49-
50-
# insert args
51-
52-
# check if args match an overload in the resource
53-
54-
for arg_name in arguments:
55-
# Use parentheses to be safe
56-
var argument = arguments[arg_name]
57-
var code_string: String
58-
if argument is ASTValueNode:
59-
code_string = argument.get_code()
60-
else:
61-
code_string = BlockAST.raw_input_to_code_string(argument)
62-
63-
code_block = code_block.replace("{%s}" % arg_name, code_string)
64-
47+
func _get_code_block() -> String:
48+
var code_block: String = BlockAST.format_code_template(data.code_template, arguments)
6549
return IDHandler.make_unique(code_block)
6650

6751
func get_code(depth: int) -> String:
6852
var code: String = ""
6953

7054
# append code block
71-
var code_block := get_code_block()
55+
var code_block := _get_code_block()
7256
code_block = code_block.indent("\t".repeat(depth))
7357

7458
code += code_block + "\n"
@@ -91,21 +75,7 @@ class ASTValueNode:
9175
arguments = {}
9276

9377
func get_code() -> String:
94-
var code: String = data.code_template # get code_template from block definition
95-
96-
# check if args match an overload in the resource
97-
98-
for arg_name in arguments:
99-
# Use parentheses to be safe
100-
var argument = arguments[arg_name]
101-
var code_string: String
102-
if argument is ASTValueNode:
103-
code_string = argument.get_code()
104-
else:
105-
code_string = BlockAST.raw_input_to_code_string(argument)
106-
107-
code = code.replace("{%s}" % arg_name, code_string)
108-
78+
var code: String = BlockAST.format_code_template(data.code_template, arguments)
10979
return IDHandler.make_unique("(%s)" % code)
11080

11181

@@ -127,18 +97,42 @@ func to_string_recursive(node: ASTNode, depth: int) -> String:
12797
return string
12898

12999

100+
static func format_code_template(code_template: String, arguments: Dictionary) -> String:
101+
for argument_name in arguments:
102+
# Use parentheses to be safe
103+
var argument_value: Variant = arguments[argument_name]
104+
var code_string: String
105+
var raw_string: String
106+
107+
if argument_value is OptionData:
108+
# Temporary hack: previously, the value was stored as an OptionData
109+
# object with a list of items and a "selected" property. If we are
110+
# using an older block script where that is the case, convert the
111+
# value to the value of its selected item.
112+
# See also, ParameterInput._update_option_input.
113+
argument_value = argument_value.items[argument_value.selected]
114+
115+
if argument_value is ASTValueNode:
116+
code_string = argument_value.get_code()
117+
raw_string = code_string
118+
else:
119+
code_string = BlockAST.raw_input_to_code_string(argument_value)
120+
raw_string = str(argument_value)
121+
122+
code_template = code_template.replace("{{%s}}" % argument_name, raw_string)
123+
code_template = code_template.replace("{%s}" % argument_name, code_string)
124+
125+
return code_template
126+
127+
130128
static func raw_input_to_code_string(input) -> String:
131129
match typeof(input):
132130
TYPE_STRING:
133-
return "'%s'" % input.replace("\\", "\\\\").replace("'", "\\'")
131+
return "'%s'" % input.c_escape()
134132
TYPE_VECTOR2:
135133
return "Vector2%s" % str(input)
136134
TYPE_COLOR:
137135
return "Color%s" % str(input)
138-
TYPE_OBJECT:
139-
if input is OptionData:
140-
var option_data := input as OptionData
141-
return option_data.items[option_data.selected]
142136
_:
143137
return "%s" % input
144138

addons/block_code/code_generation/block_definition.gd

+73
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ extends Resource
44

55
const Types = preload("res://addons/block_code/types/types.gd")
66

7+
const FORMAT_STRING_PATTERN = "\\[(?<out_parameter>[^\\]]+)\\]|\\{(?<in_parameter>[^}]+)\\}|(?<label>[^\\{\\[]+)"
8+
79
@export var name: StringName
810

911
## The target node. Leaving this empty the block is considered a general block
@@ -27,6 +29,16 @@ const Types = preload("res://addons/block_code/types/types.gd")
2729
## Empty except for blocks that have a defined scope
2830
@export var scope: String
2931

32+
@export var extension_script: GDScript
33+
34+
static var _display_template_regex := RegEx.create_from_string(FORMAT_STRING_PATTERN)
35+
36+
var _extension: BlockExtension:
37+
get:
38+
if _extension == null and extension_script and extension_script.can_instantiate():
39+
_extension = extension_script.new()
40+
return _extension as BlockExtension
41+
3042

3143
func _init(
3244
p_name: StringName = &"",
@@ -40,6 +52,7 @@ func _init(
4052
p_defaults = {},
4153
p_signal_name: String = "",
4254
p_scope: String = "",
55+
p_extension_script: GDScript = null,
4356
):
4457
name = p_name
4558
target_node_class = p_target_node_class
@@ -52,7 +65,67 @@ func _init(
5265
defaults = p_defaults
5366
signal_name = p_signal_name
5467
scope = p_scope
68+
extension_script = p_extension_script
69+
70+
71+
func get_defaults_for_node(parent_node: Node) -> Dictionary:
72+
if not _extension:
73+
return defaults
74+
75+
# Use Dictionary.merge instead of Dictionary.merged for Godot 4.2 compatibility
76+
var new_defaults := _extension.get_defaults_for_node(parent_node)
77+
new_defaults.merge(defaults)
78+
return new_defaults
5579

5680

5781
func _to_string():
5882
return "%s - %s" % [name, target_node_class]
83+
84+
85+
func get_output_parameters() -> Dictionary:
86+
var result: Dictionary
87+
for item in parse_display_template(display_template):
88+
if item.has("out_parameter"):
89+
var parameter = item.get("out_parameter")
90+
result[parameter["name"]] = parameter["type"]
91+
return result
92+
93+
94+
static func parse_display_template(template_string: String):
95+
var items: Array[Dictionary]
96+
for regex_match in _display_template_regex.search_all(template_string):
97+
if regex_match.names.has("label"):
98+
var label_string := regex_match.get_string("label")
99+
items.append({"label": label_string})
100+
elif regex_match.names.has("in_parameter"):
101+
var parameter_string := regex_match.get_string("in_parameter")
102+
items.append({"in_parameter": _parse_parameter_format(parameter_string)})
103+
elif regex_match.names.has("out_parameter"):
104+
var parameter_string := regex_match.get_string("out_parameter")
105+
items.append({"out_parameter": _parse_parameter_format(parameter_string)})
106+
return items
107+
108+
109+
static func _parse_parameter_format(parameter_format: String) -> Dictionary:
110+
var parameter_name: String
111+
var parameter_type_str: String
112+
var parameter_type: Variant.Type
113+
var split := parameter_format.split(":", true, 1)
114+
115+
if len(split) == 0:
116+
return {}
117+
118+
if len(split) > 0:
119+
parameter_name = split[0].strip_edges()
120+
121+
if len(split) > 1:
122+
parameter_type_str = split[1].strip_edges()
123+
124+
if parameter_type_str:
125+
parameter_type = Types.STRING_TO_VARIANT_TYPE[parameter_type_str]
126+
127+
return {"name": parameter_name, "type": parameter_type}
128+
129+
130+
static func has_category(block_definition, category: String) -> bool:
131+
return block_definition.category == category
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
@tool
2+
class_name BlockExtension
3+
extends Object
4+
5+
6+
func get_defaults_for_node(context_node: Node) -> Dictionary:
7+
return {}

0 commit comments

Comments
 (0)