Skip to content

Add dynamic options lists for blocks #206

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 13 commits into from
Sep 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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]
Comment on lines +107 to +113
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think that we need to maintain backwards compatibility, there has been wild refactors since the last release. It doesn't harm to have this anyways.


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