Skip to content

Commit bc42230

Browse files
committed
Add TemplateEditor to generate UI based on a block format string
This new component replaces StatementBlock.format_string in favour of a more declarative API. https://phabricator.endlessm.com/T35564
1 parent 284099c commit bc42230

File tree

17 files changed

+354
-216
lines changed

17 files changed

+354
-216
lines changed

addons/block_code/code_generation/block_definition.gd

Lines changed: 53 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
@@ -29,6 +31,8 @@ const Types = preload("res://addons/block_code/types/types.gd")
2931

3032
@export var extension_script: GDScript
3133

34+
static var _display_template_regex := RegEx.create_from_string(FORMAT_STRING_PATTERN)
35+
3236
var _extension: BlockExtension:
3337
get:
3438
if _extension == null and extension_script and extension_script.can_instantiate():
@@ -76,3 +80,52 @@ func get_defaults_for_node(parent_node: Node) -> Dictionary:
7680

7781
func _to_string():
7882
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

addons/block_code/ui/block_canvas/block_canvas.gd

Lines changed: 26 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -153,13 +153,17 @@ func ui_tree_from_ast_node(ast_node: BlockAST.ASTNode) -> Block:
153153
var block: Block = _context.block_script.instantiate_block(ast_node.data)
154154

155155
# Args
156+
var parameter_values: Dictionary
157+
156158
for arg_name in ast_node.arguments:
157159
var argument = ast_node.arguments[arg_name]
158160
if argument is BlockAST.ASTValueNode:
159161
var value_block = ui_tree_from_ast_value_node(argument)
160-
block.args_to_add_after_format[arg_name] = value_block
162+
parameter_values[arg_name] = value_block
161163
else: # Argument is not a node, but a user input value
162-
block.args_to_add_after_format[arg_name] = argument
164+
parameter_values[arg_name] = argument
165+
166+
block.set_parameter_values_on_ready(parameter_values)
163167

164168
# Children
165169
var current_block: Block = block
@@ -184,13 +188,17 @@ func ui_tree_from_ast_value_node(ast_value_node: BlockAST.ASTValueNode) -> Block
184188
var block: Block = _context.block_script.instantiate_block(ast_value_node.data)
185189

186190
# Args
191+
var parameter_values: Dictionary
192+
187193
for arg_name in ast_value_node.arguments:
188194
var argument = ast_value_node.arguments[arg_name]
189195
if argument is BlockAST.ASTValueNode:
190196
var value_block = ui_tree_from_ast_value_node(argument)
191-
block.args_to_add_after_format[arg_name] = value_block
197+
parameter_values[arg_name] = value_block
192198
else: # Argument is not a node, but a user input value
193-
block.args_to_add_after_format[arg_name] = argument
199+
parameter_values[arg_name] = argument
200+
201+
block.set_parameter_values_on_ready(parameter_values)
194202

195203
reconnect_block.emit(block)
196204
return block
@@ -217,14 +225,14 @@ func build_ast(block: Block) -> BlockAST.ASTNode:
217225
var ast_node := BlockAST.ASTNode.new()
218226
ast_node.data = block.definition
219227

220-
for arg_name in block.arg_name_to_param_input_dict:
221-
var param_input = block.arg_name_to_param_input_dict[arg_name]
222-
var snap_point = param_input.snap_point
223-
var snapped_block = snap_point.get_snapped_block()
224-
if snapped_block:
225-
ast_node.arguments[arg_name] = build_value_ast(snapped_block)
228+
var parameter_values := block.get_parameter_values()
229+
230+
for arg_name in parameter_values:
231+
var arg_value = parameter_values[arg_name]
232+
if arg_value is Block:
233+
ast_node.arguments[arg_name] = build_value_ast(arg_value)
226234
else:
227-
ast_node.arguments[arg_name] = param_input.get_raw_input()
235+
ast_node.arguments[arg_name] = arg_value
228236

229237
var children: Array[BlockAST.ASTNode] = []
230238

@@ -250,14 +258,14 @@ func build_value_ast(block: ParameterBlock) -> BlockAST.ASTValueNode:
250258
var ast_node := BlockAST.ASTValueNode.new()
251259
ast_node.data = block.definition
252260

253-
for arg_name in block.arg_name_to_param_input_dict:
254-
var param_input = block.arg_name_to_param_input_dict[arg_name]
255-
var snap_point = param_input.snap_point
256-
var snapped_block = snap_point.get_snapped_block()
257-
if snapped_block:
258-
ast_node.arguments[arg_name] = build_value_ast(snapped_block)
261+
var parameter_values := block.get_parameter_values()
262+
263+
for arg_name in parameter_values:
264+
var arg_value = parameter_values[arg_name]
265+
if arg_value is Block:
266+
ast_node.arguments[arg_name] = build_value_ast(arg_value)
259267
else:
260-
ast_node.arguments[arg_name] = param_input.get_raw_input()
268+
ast_node.arguments[arg_name] = arg_value
261269

262270
return ast_node
263271

addons/block_code/ui/blocks/block/block.gd

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@ signal modified
2020
## Snap point that holds blocks that should be nested under this block
2121
@export var child_snap: SnapPoint = null
2222

23+
## The resource containing the definition of the block
24+
@export var template_editor: TemplateEditor = null
25+
2326
## The scope of the block (statement of matching entry block)
2427
@export var scope: String = ""
2528

@@ -29,10 +32,34 @@ signal modified
2932
## Whether the block can be deleted by the Delete key.
3033
var can_delete: bool = true
3134

35+
@onready var _context := BlockEditorContext.get_default()
36+
3237

3338
func _ready():
3439
focus_mode = FocusMode.FOCUS_ALL
3540
mouse_filter = Control.MOUSE_FILTER_IGNORE
41+
_update_template_editor()
42+
43+
44+
func set_parameter_values_on_ready(raw_values: Dictionary):
45+
await ready
46+
set_parameter_values(raw_values)
47+
48+
49+
func set_parameter_values(raw_values: Dictionary):
50+
template_editor.set_parameter_values(raw_values)
51+
52+
53+
func get_parameter_values() -> Dictionary:
54+
return template_editor.get_parameter_values()
55+
56+
57+
func _update_template_editor():
58+
if template_editor == null:
59+
return
60+
61+
template_editor.format_string = definition.display_template if definition else ""
62+
template_editor.parameter_defaults = definition.get_defaults_for_node(_context.parent_node) if definition else {}
3663

3764

3865
func _gui_input(event):
@@ -57,6 +84,10 @@ func _gui_input(event):
5784
EditorInterface.popup_dialog_centered(dialog)
5885

5986

87+
func _modified():
88+
modified.emit()
89+
90+
6091
func remove_from_tree():
6192
var parent = get_parent()
6293
if parent:
@@ -84,7 +115,7 @@ func disconnect_signals():
84115

85116

86117
func _to_string():
87-
return "<{block_class}:{block_name}#{rid}>".format({"block_name": definition.name, "block_class": get_block_class(), "rid": get_instance_id()})
118+
return "<{block_class}:{block_name}#{rid}>".format({"block_name": definition.name if definition else "", "block_class": get_block_class(), "rid": get_instance_id()})
88119

89120

90121
func _make_custom_tooltip(for_text) -> Control:

addons/block_code/ui/blocks/control_block/control_block.gd

Lines changed: 3 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,9 @@ class_name ControlBlock
33
extends Block
44

55
const Constants = preload("res://addons/block_code/ui/constants.gd")
6-
7-
var arg_name_to_param_input_dict: Dictionary
8-
var args_to_add_after_format: Dictionary # Only used when loading
6+
const DragDropArea = preload("res://addons/block_code/ui/blocks/utilities/drag_drop_area/drag_drop_area.gd")
7+
const DragDropAreaScene = preload("res://addons/block_code/ui/blocks/utilities/drag_drop_area/drag_drop_area.tscn")
8+
const Gutter = preload("res://addons/block_code/ui/blocks/utilities/background/gutter.gd")
99

1010

1111
func _ready():
@@ -19,16 +19,6 @@ func _ready():
1919
%SnapGutter.color = color
2020
%SnapGutter.custom_minimum_size.x = Constants.CONTROL_MARGIN
2121

22-
format()
23-
24-
for arg_name in arg_name_to_param_input_dict:
25-
if arg_name in args_to_add_after_format:
26-
var argument = args_to_add_after_format[arg_name]
27-
if argument is Block:
28-
arg_name_to_param_input_dict[arg_name].snap_point.add_child(argument)
29-
else:
30-
arg_name_to_param_input_dict[arg_name].set_raw_input(argument)
31-
3222

3323
func _on_drag_drop_area_mouse_down():
3424
_drag_started()
@@ -40,7 +30,3 @@ static func get_block_class():
4030

4131
static func get_scene_path():
4232
return "res://addons/block_code/ui/blocks/control_block/control_block.tscn"
43-
44-
45-
func format():
46-
arg_name_to_param_input_dict = StatementBlock.format_string(self, %RowHBox, definition.display_template, definition.defaults)

addons/block_code/ui/blocks/control_block/control_block.tscn

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,20 @@
1-
[gd_scene load_steps=6 format=3 uid="uid://bk2argmujy0kk"]
1+
[gd_scene load_steps=7 format=3 uid="uid://bk2argmujy0kk"]
22

33
[ext_resource type="Script" path="res://addons/block_code/ui/blocks/control_block/control_block.gd" id="1_2hbir"]
44
[ext_resource type="Script" path="res://addons/block_code/ui/blocks/utilities/background/gutter.gd" id="2_6o8pf"]
55
[ext_resource type="PackedScene" uid="uid://c7puyxpqcq6xo" path="res://addons/block_code/ui/blocks/utilities/drag_drop_area/drag_drop_area.tscn" id="2_lpu3c"]
66
[ext_resource type="Script" path="res://addons/block_code/ui/blocks/utilities/background/background.gd" id="2_tx0qr"]
77
[ext_resource type="PackedScene" uid="uid://b1oge52xhjqnu" path="res://addons/block_code/ui/blocks/utilities/snap_point/snap_point.tscn" id="3_nhryi"]
8+
[ext_resource type="PackedScene" uid="uid://b1xvp3u11h41s" path="res://addons/block_code/ui/blocks/utilities/template_editor/template_editor.tscn" id="4_6uktl"]
89

9-
[node name="ControlBlock" type="MarginContainer" node_paths=PackedStringArray("bottom_snap", "child_snap")]
10+
[node name="ControlBlock" type="MarginContainer" node_paths=PackedStringArray("bottom_snap", "child_snap", "template_editor")]
1011
size_flags_horizontal = 0
1112
focus_mode = 2
1213
mouse_filter = 2
1314
script = ExtResource("1_2hbir")
1415
bottom_snap = NodePath("VBoxContainer/SnapPoint")
1516
child_snap = NodePath("VBoxContainer/MarginContainer/Rows/SnapContainer/SnapPoint")
17+
template_editor = NodePath("VBoxContainer/MarginContainer/Rows/Row/RowHBoxContainer/TemplateEditor")
1618

1719
[node name="VBoxContainer" type="VBoxContainer" parent="."]
1820
layout_mode = 2
@@ -57,11 +59,9 @@ theme_override_constants/margin_top = 6
5759
theme_override_constants/margin_right = 12
5860
theme_override_constants/margin_bottom = 6
5961

60-
[node name="RowHBox" type="HBoxContainer" parent="VBoxContainer/MarginContainer/Rows/Row/RowHBoxContainer"]
62+
[node name="TemplateEditor" parent="VBoxContainer/MarginContainer/Rows/Row/RowHBoxContainer" instance=ExtResource("4_6uktl")]
6163
unique_name_in_owner = true
6264
layout_mode = 2
63-
mouse_filter = 2
64-
theme_override_constants/separation = 0
6565

6666
[node name="SnapContainer" type="MarginContainer" parent="VBoxContainer/MarginContainer/Rows"]
6767
unique_name_in_owner = true
@@ -95,4 +95,5 @@ shift_top = 20.0
9595
[node name="SnapPoint" parent="VBoxContainer" instance=ExtResource("3_nhryi")]
9696
layout_mode = 2
9797

98+
[connection signal="modified" from="." to="." method="_modified"]
9899
[connection signal="mouse_down" from="VBoxContainer/MarginContainer/Rows/Row/DragDropArea" to="." method="_on_drag_drop_area_mouse_down"]

addons/block_code/ui/blocks/entry_block/entry_block.tscn

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,18 @@
1-
[gd_scene load_steps=5 format=3 uid="uid://d2fibflv3ojys"]
1+
[gd_scene load_steps=6 format=3 uid="uid://d2fibflv3ojys"]
22

33
[ext_resource type="Script" path="res://addons/block_code/ui/blocks/entry_block/entry_block.gd" id="2_3ik8h"]
44
[ext_resource type="Script" path="res://addons/block_code/ui/blocks/utilities/background/background.gd" id="2_yrw8l"]
55
[ext_resource type="PackedScene" uid="uid://c7puyxpqcq6xo" path="res://addons/block_code/ui/blocks/utilities/drag_drop_area/drag_drop_area.tscn" id="3_v0qw8"]
6+
[ext_resource type="PackedScene" uid="uid://b1xvp3u11h41s" path="res://addons/block_code/ui/blocks/utilities/template_editor/template_editor.tscn" id="4_1gwsm"]
67
[ext_resource type="PackedScene" uid="uid://b1oge52xhjqnu" path="res://addons/block_code/ui/blocks/utilities/snap_point/snap_point.tscn" id="4_yj206"]
78

8-
[node name="EntryBlock" type="MarginContainer" node_paths=PackedStringArray("child_snap")]
9+
[node name="EntryBlock" type="MarginContainer" node_paths=PackedStringArray("child_snap", "template_editor")]
910
size_flags_horizontal = 0
1011
focus_mode = 2
1112
mouse_filter = 2
1213
script = ExtResource("2_3ik8h")
1314
child_snap = NodePath("VBoxContainer/SnapPoint")
15+
template_editor = NodePath("VBoxContainer/TopMarginContainer/MarginContainer/TemplateEditor")
1416

1517
[node name="VBoxContainer" type="VBoxContainer" parent="."]
1618
layout_mode = 2
@@ -46,14 +48,12 @@ theme_override_constants/margin_top = 6
4648
theme_override_constants/margin_right = 12
4749
theme_override_constants/margin_bottom = 6
4850

49-
[node name="HBoxContainer" type="HBoxContainer" parent="VBoxContainer/TopMarginContainer/MarginContainer"]
51+
[node name="TemplateEditor" parent="VBoxContainer/TopMarginContainer/MarginContainer" instance=ExtResource("4_1gwsm")]
5052
unique_name_in_owner = true
51-
custom_minimum_size = Vector2(50, 0)
5253
layout_mode = 2
53-
mouse_filter = 2
54-
theme_override_constants/separation = 0
5554

5655
[node name="SnapPoint" parent="VBoxContainer" instance=ExtResource("4_yj206")]
5756
layout_mode = 2
5857

5958
[connection signal="mouse_down" from="VBoxContainer/TopMarginContainer/DragDropArea" to="." method="_on_drag_drop_area_mouse_down"]
59+
[connection signal="modified" from="VBoxContainer/TopMarginContainer/MarginContainer/TemplateEditor" to="." method="_modified"]

addons/block_code/ui/blocks/parameter_block/parameter_block.gd

Lines changed: 1 addition & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,10 @@ extends Block
44

55
const Constants = preload("res://addons/block_code/ui/constants.gd")
66
const Util = preload("res://addons/block_code/ui/util.gd")
7+
const ParameterOutput = preload("res://addons/block_code/ui/blocks/utilities/parameter_output/parameter_output.gd")
78

89
@onready var _panel := $Panel
9-
@onready var _hbox := %HBoxContainer
1010

11-
var arg_name_to_param_input_dict: Dictionary
1211
var args_to_add_after_format: Dictionary # Only used when loading
1312
var spawned_by: ParameterOutput
1413

@@ -30,16 +29,6 @@ func _ready():
3029
if not Util.node_is_part_of_edited_scene(self):
3130
_panel.add_theme_stylebox_override("panel", _panel_normal)
3231

33-
format()
34-
35-
for arg_name in arg_name_to_param_input_dict:
36-
if arg_name in args_to_add_after_format:
37-
var argument = args_to_add_after_format[arg_name]
38-
if argument is Block:
39-
arg_name_to_param_input_dict[arg_name].snap_point.add_child(argument)
40-
else:
41-
arg_name_to_param_input_dict[arg_name].set_raw_input(argument)
42-
4332

4433
func _on_drag_drop_area_mouse_down():
4534
_drag_started()
@@ -53,10 +42,6 @@ static func get_scene_path():
5342
return "res://addons/block_code/ui/blocks/parameter_block/parameter_block.tscn"
5443

5544

56-
func format():
57-
arg_name_to_param_input_dict = StatementBlock.format_string(self, %HBoxContainer, definition.display_template, definition.defaults)
58-
59-
6045
func _on_focus_entered():
6146
_panel.add_theme_stylebox_override("panel", _panel_focus)
6247

0 commit comments

Comments
 (0)