8000 Merge pull request #206 from endlessm/T35564-add-block-builder · endlessm/godot-block-coding@760a579 · GitHub
[go: up one dir, main page]

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

File tree

39 files changed

+904
-652
lines changed
  • entry_block
  • parameter_block
  • statement_block
  • utilities
  • picker
  • title_bar
  • tests
  • 39 files changed

    +904
    -652
    lines changed

    addons/block_code/block_code_plugin.gd

    Lines changed: 1 addition & 6 deletions
    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
    Lines changed: 15 additions & 0 deletions
    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

    Lines changed: 11 additions & 3 deletions
    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

    Lines changed: 2 additions & 2 deletions
    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

    Lines changed: 3 additions & 3 deletions
    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

    Lines changed: 33 additions & 39 deletions
    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 93C6 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< F438 code class="diff-text syntax-highlighted-line addition">+
    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

    Lines changed: 73 additions & 0 deletions
    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
    Lines changed: 7 additions & 0 deletions
    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)
    0