Skip to content

Commit

Permalink
Merge pull request #206 from endlessm/T35564-add-block-builder
Browse files Browse the repository at this point in the history
Add dynamic options lists for blocks
  • Loading branch information
dylanmccall authored Sep 19, 2024
2 parents 4d2f4e9 + e812651 commit 760a579
Show file tree
Hide file tree
Showing 39 changed files with 904 additions and 652 deletions.
7 changes: 1 addition & 6 deletions addons/block_code/block_code_plugin.gd
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
@tool
extends EditorPlugin

const MainPanelScene := preload("res://addons/block_code/ui/main_panel.tscn")
const MainPanel = preload("res://addons/block_code/ui/main_panel.gd")
const Types = preload("res://addons/block_code/types/types.gd")
Expand Down Expand Up @@ -93,16 +94,10 @@ func _exit_tree():


func _ready():
connect("scene_changed", _on_scene_changed)
editor_inspector.connect("edited_object_changed", _on_editor_inspector_edited_object_changed)
_on_scene_changed(EditorInterface.get_edited_scene_root())
_on_editor_inspector_edited_object_changed()


func _on_scene_changed(scene_root: Node):
main_panel.switch_scene(scene_root)


func _on_editor_inspector_edited_object_changed():
var edited_object = editor_inspector.get_edited_object()
#var edited_node = edited_object as Node
Expand Down
15 changes: 15 additions & 0 deletions addons/block_code/blocks/graphics/animationplayer_play.gd
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
@tool
extends BlockExtension

const OptionData = preload("res://addons/block_code/code_generation/option_data.gd")


func get_defaults_for_node(context_node: Node) -> Dictionary:
var animation_player = context_node as AnimationPlayer

if not animation_player:
return {}

var animation_list = animation_player.get_animation_list()

return {"animation": OptionData.new(animation_list)}
14 changes: 11 additions & 3 deletions addons/block_code/blocks/graphics/animationplayer_play.tres
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
[gd_resource type="Resource" load_steps=4 format=3 uid="uid://c5e1byehtxwc0"]
[gd_resource type="Resource" load_steps=6 format=3 uid="uid://c5e1byehtxwc0"]

[ext_resource type="Script" path="res://addons/block_code/code_generation/block_definition.gd" id="1_emeuv"]
[ext_resource type="Script" path="res://addons/block_code/code_generation/option_data.gd" id="1_xu43h"]
[ext_resource type="Script" path="res://addons/block_code/blocks/graphics/animationplayer_play.gd" id="2_7ymgi"]

[sub_resource type="Resource" id="Resource_qpxn2"]
script = ExtResource("1_xu43h")
selected = 0
items = []

[sub_resource type="Resource" id="Resource_vnp2w"]
script = ExtResource("1_xu43h")
Expand All @@ -16,14 +22,16 @@ description = "Play the animation."
category = "Graphics | Animation"
type = 2
variant_type = 0
display_template = "Play {animation: STRING} {direction: OPTION}"
code_template = "if \"{direction}\" == \"ahead\":
display_template = "Play {animation: STRING} {direction: NIL}"
code_template = "if {direction} == \"ahead\":
play({animation})
else:
play_backwards({animation})
"
defaults = {
"animation": SubResource("Resource_qpxn2"),
"direction": SubResource("Resource_vnp2w")
}
signal_name = ""
scope = ""
extension_script = ExtResource("2_7ymgi")
4 changes: 2 additions & 2 deletions addons/block_code/blocks/logic/compare.tres
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ description = ""
category = "Logic | Comparison"
type = 3
variant_type = 1
display_template = "{float1: FLOAT} {op: OPTION} {float2: FLOAT}"
code_template = "{float1} {op} {float2}"
display_template = "{float1: FLOAT} {op: NIL} {float2: FLOAT}"
code_template = "{float1} {{op}} {float2}"
defaults = {
"float1": 1.0,
"float2": 1.0,
Expand Down
6 changes: 3 additions & 3 deletions addons/block_code/blocks/sounds/pause_continue_sound.tres
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
[sub_resource type="Resource" id="Resource_lalgp"]
script = ExtResource("1_ilhdq")
selected = 0
items = ["Pause", "Continue"]
items = ["pause", "continue"]

[resource]
script = ExtResource("1_q04gm")
Expand All @@ -16,9 +16,9 @@ description = "Pause/Continue the audio stream"
category = "Sounds"
type = 2
variant_type = 0
display_template = "{pause: OPTION} the sound {name: STRING}"
display_template = "{pause: NIL} the sound {name: STRING}"
code_template = "var __sound_node = get_node({name})
if \"{pause}\" == \"pause\":
if {pause} == \"pause\":
__sound_node.stream_paused = true
else:
__sound_node.stream_paused = false
Expand Down
72 changes: 33 additions & 39 deletions addons/block_code/code_generation/block_ast.gd
Original file line number Diff line number Diff line change
Expand Up @@ -44,31 +44,15 @@ class ASTNode:
children = []
arguments = {}

func get_code_block() -> String:
var code_block: String = data.code_template # get multiline code_template from block definition

# insert args

# check if args match an overload in the resource

for arg_name in arguments:
# Use parentheses to be safe
var argument = arguments[arg_name]
var code_string: String
if argument is ASTValueNode:
code_string = argument.get_code()
else:
code_string = BlockAST.raw_input_to_code_string(argument)

code_block = code_block.replace("{%s}" % arg_name, code_string)

func _get_code_block() -> String:
var code_block: String = BlockAST.format_code_template(data.code_template, arguments)
return IDHandler.make_unique(code_block)

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

# append code block
var code_block := get_code_block()
var code_block := _get_code_block()
code_block = code_block.indent("\t".repeat(depth))

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

func get_code() -> String:
var code: String = data.code_template # get code_template from block definition

# check if args match an overload in the resource

for arg_name in arguments:
# Use parentheses to be safe
var argument = arguments[arg_name]
var code_string: String
if argument is ASTValueNode:
code_string = argument.get_code()
else:
code_string = BlockAST.raw_input_to_code_string(argument)

code = code.replace("{%s}" % arg_name, code_string)

var code: String = BlockAST.format_code_template(data.code_template, arguments)
return IDHandler.make_unique("(%s)" % code)


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


static func format_code_template(code_template: String, arguments: Dictionary) -> String:
for argument_name in arguments:
# Use parentheses to be safe
var argument_value: Variant = arguments[argument_name]
var code_string: String
var raw_string: String

if argument_value is OptionData:
# Temporary hack: previously, the value was stored as an OptionData
# object with a list of items and a "selected" property. If we are
# using an older block script where that is the case, convert the
# value to the value of its selected item.
# See also, ParameterInput._update_option_input.
argument_value = argument_value.items[argument_value.selected]

if argument_value is ASTValueNode:
code_string = argument_value.get_code()
raw_string = code_string
else:
code_string = BlockAST.raw_input_to_code_string(argument_value)
raw_string = str(argument_value)

code_template = code_template.replace("{{%s}}" % argument_name, raw_string)
code_template = code_template.replace("{%s}" % argument_name, code_string)

return code_template


static func raw_input_to_code_string(input) -> String:
match typeof(input):
TYPE_STRING:
return "'%s'" % input.replace("\\", "\\\\").replace("'", "\\'")
return "'%s'" % input.c_escape()
TYPE_VECTOR2:
return "Vector2%s" % str(input)
TYPE_COLOR:
return "Color%s" % str(input)
TYPE_OBJECT:
if input is OptionData:
var option_data := input as OptionData
return option_data.items[option_data.selected]
_:
return "%s" % input

Expand Down
73 changes: 73 additions & 0 deletions addons/block_code/code_generation/block_definition.gd
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ extends Resource

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

const FORMAT_STRING_PATTERN = "\\[(?<out_parameter>[^\\]]+)\\]|\\{(?<in_parameter>[^}]+)\\}|(?<label>[^\\{\\[]+)"

@export var name: StringName

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

@export var extension_script: GDScript

static var _display_template_regex := RegEx.create_from_string(FORMAT_STRING_PATTERN)

var _extension: BlockExtension:
get:
if _extension == null and extension_script and extension_script.can_instantiate():
_extension = extension_script.new()
return _extension as BlockExtension


func _init(
p_name: StringName = &"",
Expand All @@ -40,6 +52,7 @@ func _init(
p_defaults = {},
p_signal_name: String = "",
p_scope: String = "",
p_extension_script: GDScript = null,
):
name = p_name
target_node_class = p_target_node_class
Expand All @@ -52,7 +65,67 @@ func _init(
defaults = p_defaults
signal_name = p_signal_name
scope = p_scope
extension_script = p_extension_script


func get_defaults_for_node(parent_node: Node) -> Dictionary:
if not _extension:
return defaults

# Use Dictionary.merge instead of Dictionary.merged for Godot 4.2 compatibility
var new_defaults := _extension.get_defaults_for_node(parent_node)
new_defaults.merge(defaults)
return new_defaults


func _to_string():
return "%s - %s" % [name, target_node_class]


func get_output_parameters() -> Dictionary:
var result: Dictionary
for item in parse_display_template(display_template):
if item.has("out_parameter"):
var parameter = item.get("out_parameter")
result[parameter["name"]] = parameter["type"]
return result


static func parse_display_template(template_string: String):
var items: Array[Dictionary]
for regex_match in _display_template_regex.search_all(template_string):
if regex_match.names.has("label"):
var label_string := regex_match.get_string("label")
items.append({"label": label_string})
elif regex_match.names.has("in_parameter"):
var parameter_string := regex_match.get_string("in_parameter")
items.append({"in_parameter": _parse_parameter_format(parameter_string)})
elif regex_match.names.has("out_parameter"):
var parameter_string := regex_match.get_string("out_parameter")
items.append({"out_parameter": _parse_parameter_format(parameter_string)})
return items


static func _parse_parameter_format(parameter_format: String) -> Dictionary:
var parameter_name: String
var parameter_type_str: String
var parameter_type: Variant.Type
var split := parameter_format.split(":", true, 1)

if len(split) == 0:
return {}

if len(split) > 0:
parameter_name = split[0].strip_edges()

if len(split) > 1:
parameter_type_str = split[1].strip_edges()

if parameter_type_str:
parameter_type = Types.STRING_TO_VARIANT_TYPE[parameter_type_str]

return {"name": parameter_name, "type": parameter_type}


static func has_category(block_definition, category: String) -> bool:
return block_definition.category == category
7 changes: 7 additions & 0 deletions addons/block_code/code_generation/block_extension.gd
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
@tool
class_name BlockExtension
extends Object


func get_defaults_for_node(context_node: Node) -> Dictionary:
return {}
Loading

0 comments on commit 760a579

Please sign in to comment.