diff --git a/addons/block_code/examples/pong_game/paddle.gd b/addons/block_code/examples/pong_game/paddle.gd index 7d073e89..033c26c5 100644 --- a/addons/block_code/examples/pong_game/paddle.gd +++ b/addons/block_code/examples/pong_game/paddle.gd @@ -11,23 +11,23 @@ static func get_exposed_properties() -> Array[String]: return ["position"] -static func get_custom_blocks() -> Array[BlockCategory]: +static func get_custom_blocks() -> Array[Block]: var b: Block + var block_list: Array[Block] = [] # Movement - var movement_list: Array[Block] = [] b = CategoryFactory.BLOCKS["statement_block"].instantiate() b.block_type = Types.BlockType.EXECUTE b.block_format = "Move with player 1 buttons, speed {speed: VECTOR2}" b.statement = 'velocity = Input.get_vector("ui_left", "ui_right", "ui_up", "ui_down")*{speed}\n' + "move_and_slide()" - movement_list.append(b) + b.category = "Movement" + block_list.append(b) b = CategoryFactory.BLOCKS["statement_block"].instantiate() b.block_type = Types.BlockType.EXECUTE b.block_format = "Move with player 2 buttons, speed {speed: VECTOR2}" b.statement = 'velocity = Input.get_vector("player_2_left", "player_2_right", "player_2_up", "player_2_down")*{speed}\n' + "move_and_slide()" - movement_list.append(b) + b.category = "Movement" + block_list.append(b) - var movement_category: BlockCategory = BlockCategory.new("Movement", movement_list, Color("4a86d5")) - - return [movement_category] + return block_list diff --git a/addons/block_code/examples/pong_game/pong_game.gd b/addons/block_code/examples/pong_game/pong_game.gd index 0f85adb0..2ff2cafb 100644 --- a/addons/block_code/examples/pong_game/pong_game.gd +++ b/addons/block_code/examples/pong_game/pong_game.gd @@ -7,23 +7,27 @@ func get_custom_class(): return "Pong" -static func get_custom_blocks() -> Array[BlockCategory]: +static func get_custom_categories() -> Array[BlockCategory]: + return [BlockCategory.new("Scoring", Color("4a86d5"))] + + +static func get_custom_blocks() -> Array[Block]: var b: Block + var block_list: Array[Block] = [] # TODO: Only for testing. Move these blocks where they belong. - var score_list: Array[Block] = [] b = CategoryFactory.BLOCKS["statement_block"].instantiate() b.block_type = Types.BlockType.EXECUTE b.block_format = "Set player 1 score to {score: INT}" b.statement = 'get_tree().call_group("hud", "set_player_score", "right", {score})' - score_list.append(b) + b.category = "Scoring" + block_list.append(b) b = CategoryFactory.BLOCKS["statement_block"].instantiate() b.block_type = Types.BlockType.EXECUTE b.block_format = "Set player 2 score to {score: INT}" b.statement = 'get_tree().call_group("hud", "set_player_score", "left", {score})' - score_list.append(b) - - var score_category: BlockCategory = BlockCategory.new("Scoring", score_list, Color("4a86d5")) + b.category = "Scoring" + block_list.append(b) - return [score_category] + return block_list diff --git a/addons/block_code/simple_nodes/simple_character/simple_character.gd b/addons/block_code/simple_nodes/simple_character/simple_character.gd index 5b4de358..4f989e13 100644 --- a/addons/block_code/simple_nodes/simple_character/simple_character.gd +++ b/addons/block_code/simple_nodes/simple_character/simple_character.gd @@ -17,23 +17,23 @@ static func get_exposed_properties() -> Array[String]: return ["position"] -static func get_custom_blocks() -> Array[BlockCategory]: +static func get_custom_blocks() -> Array[Block]: var b: Block + var block_list: Array[Block] = [] # Movement - var movement_list: Array[Block] = [] b = CategoryFactory.BLOCKS["statement_block"].instantiate() b.block_type = Types.BlockType.EXECUTE b.block_format = "Move with player 1 buttons, speed {speed: INT}" b.statement = 'velocity = Input.get_vector("ui_left", "ui_right", "ui_up", "ui_down")*{speed}\n' + "move_and_slide()" - movement_list.append(b) + b.category = "Input" + block_list.append(b) b = CategoryFactory.BLOCKS["statement_block"].instantiate() b.block_type = Types.BlockType.EXECUTE b.block_format = "Move with player 2 buttons, speed {speed: INT}" b.statement = 'velocity = Input.get_vector("player_2_left", "player_2_right", "player_2_up", "player_2_down")*{speed}\n' + "move_and_slide()" - movement_list.append(b) + b.category = "Input" + block_list.append(b) - var movement_cat: BlockCategory = BlockCategory.new("Movement", movement_list, Color("4a86d5")) - - return [movement_cat] + return block_list diff --git a/addons/block_code/ui/blocks/block/block.gd b/addons/block_code/ui/blocks/block/block.gd index d7232b32..79ec85b4 100644 --- a/addons/block_code/ui/blocks/block/block.gd +++ b/addons/block_code/ui/blocks/block/block.gd @@ -17,6 +17,9 @@ signal modified ## Type of block to check if can be attached to snap point @export var block_type: Types.BlockType = Types.BlockType.EXECUTE +## Category to add the block to +@export var category: String + ## The next block in the line of execution (can be null if end) @export var bottom_snap_path: NodePath diff --git a/addons/block_code/ui/picker/categories/block_category.gd b/addons/block_code/ui/picker/categories/block_category.gd index c77c0732..428d79c5 100644 --- a/addons/block_code/ui/picker/categories/block_category.gd +++ b/addons/block_code/ui/picker/categories/block_category.gd @@ -4,9 +4,11 @@ extends Object var name: String var block_list: Array[Block] var color: Color +var order: int -func _init(p_name: String = "", p_block_list: Array[Block] = [], p_color: Color = Color.WHITE): +func _init(p_name: String = "", p_color: Color = Color.WHITE, p_order: int = 0, p_block_list: Array[Block] = []): name = p_name block_list = p_block_list color = p_color + order = p_order diff --git a/addons/block_code/ui/picker/categories/category_factory.gd b/addons/block_code/ui/picker/categories/category_factory.gd index 16226cd6..8f1fded9 100644 --- a/addons/block_code/ui/picker/categories/category_factory.gd +++ b/addons/block_code/ui/picker/categories/category_factory.gd @@ -9,274 +9,349 @@ const BLOCKS: Dictionary = { "entry_block": preload("res://addons/block_code/ui/blocks/entry_block/entry_block.tscn"), } +## Properties for builtin categories. Order starts at 10 for the first +## category and then are separated by 10 to allow custom categories to +## be easily placed between builtin categories. +const BUILTIN_PROPS: Dictionary = { + "Control": + { + "color": Color("ffad76"), + "order": 30, + }, + "Graphics": + { + "color": Color("9be371"), + "order": 110, + }, + "Input": + { + "color": Color.SLATE_GRAY, + "order": 70, + }, + "Lifecycle": + { + "color": Color("fa5956"), + "order": 10, + }, + "Logic": + { + "color": Color("42b8e3"), + "order": 60, + }, + "Math": + { + "color": Color("3042c5"), + "order": 50, + }, + "Movement": + { + "color": Color("e2e72b"), + "order": 90, + }, + "Signal": + { + "color": Color("f0c300"), + "order": 20, + }, + "Sound": + { + "color": Color("e30fc0"), + "order": 80, + }, + "Size": + { + "color": Color("f79511"), + "order": 100, + }, + "Test": + { + "color": Color("9989df"), + "order": 40, + }, + "Variables": + { + "color": Color("4f975d"), + "order": 60, + }, +} + + +## Compare block categories for sorting. Compare by order then name. +static func _category_cmp(a: BlockCategory, b: BlockCategory) -> bool: + if a.order != b.order: + return a.order < b.order + return a.name.naturalcasecmp_to(b.name) < 0 + + +static func get_categories(blocks: Array[Block], extra_categories: Array[BlockCategory] = []) -> Array[BlockCategory]: + var cat_map: Dictionary = {} + var extra_cat_map: Dictionary = {} + + for cat in extra_categories: + extra_cat_map[cat.name] = cat + + for block in blocks: + var cat: BlockCategory = cat_map.get(block.category) + if cat == null: + cat = extra_cat_map.get(block.category) + if cat == null: + var props: Dictionary = BUILTIN_PROPS.get(block.category, {}) + var color: Color = props.get("color", Color.SLATE_GRAY) + var order: int = props.get("order", 0) + cat = BlockCategory.new(block.category, color, order) + cat_map[block.category] = cat + cat.block_list.append(block) + + # Dictionary.values() returns an untyped Array and there's no way to + # convert an array type besides Array.assign(). + var cats: Array[BlockCategory] = [] + cats.assign(cat_map.values()) + cats.sort_custom(_category_cmp) + return cats + -static func get_general_categories() -> Array[BlockCategory]: +static func get_general_blocks() -> Array[Block]: var b: Block + var block_list: Array[Block] = [] # Lifecycle - var lifecycle_list: Array[Block] = [] b = BLOCKS["entry_block"].instantiate() b.block_name = "ready_block" b.block_format = "On Ready" b.statement = "func _ready():" - lifecycle_list.append(b) + b.category = "Lifecycle" + block_list.append(b) b = BLOCKS["entry_block"].instantiate() b.block_name = "process_block" b.block_format = "On Process" b.statement = "func _process(delta):" - lifecycle_list.append(b) + b.category = "Lifecycle" + block_list.append(b) b = BLOCKS["entry_block"].instantiate() b.block_name = "physics_process_block" b.block_format = "On Physics Process" b.statement = "func _physics_process(delta):" - lifecycle_list.append(b) + b.category = "Lifecycle" + block_list.append(b) b = BLOCKS["statement_block"].instantiate() b.block_format = "Queue Free" b.statement = "queue_free()" - lifecycle_list.append(b) - - var lifecycle_category: BlockCategory = BlockCategory.new("Lifecycle", lifecycle_list, Color("fa5956")) + b.category = "Lifecycle" + block_list.append(b) # Control - var control_list: Array[Block] = [] - b = BLOCKS["control_block"].instantiate() b.block_formats = ["if {condition: BOOL}"] b.statements = ["if {condition}:"] - control_list.append(b) + b.category = "Control" + block_list.append(b) b = BLOCKS["control_block"].instantiate() b.block_formats = ["if {condition: BOOL}", "else"] b.statements = ["if {condition}:", "else:"] - control_list.append(b) + b.category = "Control" + block_list.append(b) b = BLOCKS["control_block"].instantiate() b.block_formats = ["repeat {number: INT}"] b.statements = ["for i in {number}:"] - control_list.append(b) + b.category = "Control" + block_list.append(b) b = BLOCKS["control_block"].instantiate() b.block_formats = ["while {condition: BOOL}"] b.statements = ["while {condition}:"] - control_list.append(b) + b.category = "Control" + block_list.append(b) b = BLOCKS["statement_block"].instantiate() b.block_format = "Break" b.statement = "break" - control_list.append(b) + b.category = "Control" + block_list.append(b) b = BLOCKS["statement_block"].instantiate() b.block_format = "Continue" b.statement = "continue" - control_list.append(b) - - var control_category: BlockCategory = BlockCategory.new("Control", control_list, Color("ffad76")) + b.category = "Control" + block_list.append(b) # Test - var test_list: Array[Block] = [] - b = BLOCKS["statement_block"].instantiate() b.block_format = "print {text: STRING}" b.statement = "print({text})" b.defaults = {"text": "Hello"} - test_list.append(b) - - var test_category: BlockCategory = BlockCategory.new("Test", test_list, Color("9989df")) + b.category = "Test" + block_list.append(b) # Signal - var signal_list: Array[Block] = [] - b = BLOCKS["entry_block"].instantiate() # HACK: make signals work with new entry nodes. NIL instead of STRING type allows # plain text input for function name. Should revamp signals later b.block_format = "On signal {signal: NIL}" b.statement = "func signal_{signal}():" - signal_list.append(b) + b.category = "Signal" + block_list.append(b) b = BLOCKS["statement_block"].instantiate() b.block_format = "Send signal {signal: STRING} to group {group: STRING}" b.statement = 'var signal_manager = get_tree().root.get_node_or_null("SignalManager")\n' + "if signal_manager:\n" + "\tsignal_manager.broadcast_signal({group}, {signal})" - signal_list.append(b) + b.category = "Signal" + block_list.append(b) b = BLOCKS["statement_block"].instantiate() b.block_format = "Add to group {group: STRING}" b.statement = "add_to_group({group})" - signal_list.append(b) + b.category = "Signal" + block_list.append(b) b = BLOCKS["statement_block"].instantiate() b.block_format = "Add {node: NODE_PATH} to group {group: STRING}" b.statement = "get_node({node}).add_to_group({group})" - signal_list.append(b) + b.category = "Signal" + block_list.append(b) b = BLOCKS["statement_block"].instantiate() b.block_format = "Remove from group {group: STRING}" b.statement = "remove_from_group({group})" - signal_list.append(b) + b.category = "Signal" + block_list.append(b) b = BLOCKS["statement_block"].instantiate() b.block_format = "Remove {node: NODE_PATH} from group {group: STRING}" b.statement = "get_node({node}).remove_from_group({group})" - signal_list.append(b) + b.category = "Signal" + block_list.append(b) b = BLOCKS["parameter_block"].instantiate() b.variant_type = TYPE_BOOL b.block_format = "Is in group {group: STRING}" b.statement = "is_in_group({group})" - signal_list.append(b) + b.category = "Signal" + block_list.append(b) b = BLOCKS["parameter_block"].instantiate() b.variant_type = TYPE_BOOL b.block_format = "Is {node: NODE_PATH} in group {group: STRING}" b.statement = "get_node({node}).is_in_group({group})" - signal_list.append(b) - - var signal_category: BlockCategory = BlockCategory.new("Signal", signal_list, Color("f0c300")) - - # Variable - var variable_list: Array[Block] = [] + b.category = "Signal" + block_list.append(b) + # Variables b = BLOCKS["statement_block"].instantiate() b.block_format = "Set String {var: STRING} {value: STRING}" b.statement = "VAR_DICT[{var}] = {value}" - variable_list.append(b) + b.category = "Variables" + block_list.append(b) b = BLOCKS["parameter_block"].instantiate() b.block_format = "Get String {var: STRING}" b.statement = "VAR_DICT[{var}]" - variable_list.append(b) + b.category = "Variables" + block_list.append(b) b = BLOCKS["statement_block"].instantiate() b.block_format = "Set Int {var: STRING} {value: INT}" b.statement = "VAR_DICT[{var}] = {value}" - variable_list.append(b) + b.category = "Variables" + block_list.append(b) b = BLOCKS["parameter_block"].instantiate() b.variant_type = TYPE_INT b.block_format = "Get Int {var: STRING}" b.statement = "VAR_DICT[{var}]" - variable_list.append(b) + b.category = "Variables" + block_list.append(b) b = BLOCKS["parameter_block"].instantiate() b.block_format = "To String {int: INT}" b.statement = "str({int})" - variable_list.append(b) - - var variable_category: BlockCategory = BlockCategory.new("Variables", variable_list, Color("4f975d")) + b.category = "Variables" + block_list.append(b) # Math - var math_list: Array[Block] = [] - b = BLOCKS["parameter_block"].instantiate() b.variant_type = TYPE_INT b.block_format = "{a: INT} + {b: INT}" b.statement = "({a} + {b})" - math_list.append(b) + b.category = "Math" + block_list.append(b) b = BLOCKS["parameter_block"].instantiate() b.variant_type = TYPE_INT b.block_format = "{a: INT} - {b: INT}" b.statement = "({a} - {b})" - math_list.append(b) + b.category = "Math" + block_list.append(b) b = BLOCKS["parameter_block"].instantiate() b.variant_type = TYPE_INT b.block_format = "{a: INT} * {b: INT}" b.statement = "({a} * {b})" - math_list.append(b) + b.category = "Math" + block_list.append(b) b = BLOCKS["parameter_block"].instantiate() b.variant_type = TYPE_INT b.block_format = "{a: INT} / {b: INT}" b.statement = "({a} / {b})" - math_list.append(b) + b.category = "Math" + block_list.append(b) b = BLOCKS["parameter_block"].instantiate() b.variant_type = TYPE_INT b.block_format = "{base: INT} ^ {exp: INT}" b.statement = "(pow({base}, {exp}))" - math_list.append(b) - - var math_category: BlockCategory = BlockCategory.new("Math", math_list, Color("3042c5")) + b.category = "Math" + block_list.append(b) # Logic - - var logic_list: Array[Block] = [] - b = BLOCKS["parameter_block"].instantiate() b.variant_type = TYPE_BOOL b.block_format = "{int1: INT} {op: OPTION} {int2: INT}" b.statement = "({int1} {op} {int2})" b.defaults = {"op": OptionData.new(["==", ">", "<", ">=", "<=", "!="])} - logic_list.append(b) + b.category = "Logic" + block_list.append(b) for op in ["and", "or"]: b = BLOCKS["parameter_block"].instantiate() b.variant_type = TYPE_BOOL b.block_format = "{bool1: BOOL} %s {bool2: BOOL}" % op b.statement = "({bool1} %s {bool2})" % op - logic_list.append(b) + b.category = "Logic" + block_list.append(b) b = BLOCKS["parameter_block"].instantiate() b.variant_type = TYPE_BOOL b.block_format = "Not {bool: BOOL}" b.statement = "(not {bool})" - logic_list.append(b) - - var logic_category: BlockCategory = BlockCategory.new("Logic", logic_list, Color("42b8e3")) + b.category = "Logic" + block_list.append(b) # Input - var input_list: Array[Block] = _get_input_blocks() - var input_category: BlockCategory = BlockCategory.new("Input", input_list, Color.SLATE_GRAY) + block_list.append_array(_get_input_blocks()) # Sound - var sound_list: Array[Block] = [] - b = BLOCKS["statement_block"].instantiate() b.block_type = Types.BlockType.EXECUTE b.block_format = "Load file {file_path: STRING} as sound {name: STRING}" b.statement = "VAR_DICT[{name}] = AudioStreamPlayer.new()\nVAR_DICT[{name}].name = {name}\nVAR_DICT[{name}].set_stream(load({file_path}))\nadd_child(VAR_DICT[{name}])" - sound_list.append(b) + b.category = "Sound" + block_list.append(b) b = BLOCKS["statement_block"].instantiate() b.block_type = Types.BlockType.EXECUTE b.block_format = "Play the sound {name: STRING} with Volume dB {db: FLOAT} and Pitch Scale {pitch: FLOAT}" b.statement = "VAR_DICT[{name}].volume_db = {db}\nVAR_DICT[{name}].pitch_scale = {pitch}\nVAR_DICT[{name}].play()" b.defaults = {"db": "0.0", "pitch": "1.0"} - sound_list.append(b) - - var sound_category: BlockCategory = BlockCategory.new("Sound", sound_list, Color("e30fc0")) - - return [ - lifecycle_category, - signal_category, - control_category, - test_category, - math_category, - logic_category, - variable_category, - input_category, - sound_category, - ] - - -static func add_to_categories(main: Array[BlockCategory], addition: Array[BlockCategory]) -> Array[BlockCategory]: - var matching := [] - - for category in main: - for add_category in addition: - if category.name == add_category.name: - matching.append(category.name) - for block in add_category.block_list: - category.block_list.append(block) + b.category = "Sound" + block_list.append(b) - for add_category in addition: - if !matching.has(add_category.name): - main.append(add_category) - - return main + return block_list static func property_to_blocklist(property: Dictionary) -> Array[Block]: @@ -290,23 +365,26 @@ static func property_to_blocklist(property: Dictionary) -> Array[Block]: var b = BLOCKS["statement_block"].instantiate() b.block_format = "Set %s to {value: %s}" % [property.name.capitalize(), type_string] b.statement = "%s = {value}" % property.name + b.category = property.category block_list.append(b) b = BLOCKS["statement_block"].instantiate() b.block_format = "Change %s by {value: %s}" % [property.name.capitalize(), type_string] b.statement = "%s += {value}" % property.name + b.category = property.category block_list.append(b) b = BLOCKS["parameter_block"].instantiate() b.block_type = block_type b.block_format = "%s" % property.name.capitalize() b.statement = "%s" % property.name + b.category = property.category block_list.append(b) return block_list -static func category_from_property_list(property_list: Array, selected_props: Array, p_name: String, p_color: Color) -> BlockCategory: +static func blocks_from_property_list(property_list: Array, selected_props: Dictionary) -> Array[Block]: var block_list: Array[Block] for selected_property in selected_props: @@ -314,31 +392,30 @@ static func category_from_property_list(property_list: Array, selected_props: Ar for prop in property_list: if selected_property == prop.name: found_prop = prop + found_prop.category = selected_props[selected_property] break if found_prop: block_list.append_array(property_to_blocklist(found_prop)) else: push_warning("No property matching %s found in %s" % [selected_property, property_list]) - return BlockCategory.new(p_name, block_list, p_color) + return block_list -static func get_inherited_categories(_class_name: String) -> Array[BlockCategory]: - var cats: Array[BlockCategory] = [] +static func get_inherited_blocks(_class_name: String) -> Array[Block]: + var blocks: Array[Block] = [] var current: String = _class_name while current != "": - add_to_categories(cats, get_built_in_categories(current)) + blocks.append_array(get_built_in_blocks(current)) current = ClassDB.get_parent_class(current) - return cats - + return blocks -static func get_built_in_categories(_class_name: String) -> Array[BlockCategory]: - var cats: Array[BlockCategory] = [] - var props: Array = [] +static func get_built_in_blocks(_class_name: String) -> Array[Block]: + var props: Dictionary = {} var block_list: Array[Block] = [] match _class_name: @@ -346,12 +423,20 @@ static func get_built_in_categories(_class_name: String) -> Array[BlockCategory] var b = BLOCKS["statement_block"].instantiate() b.block_format = "Set Rotation Degrees {angle: FLOAT}" b.statement = "rotation_degrees = {angle}" + b.category = "Movement" block_list.append(b) - props = ["position", "rotation", "scale"] + props = { + "position": "Movement", + "rotation": "Movement", + "scale": "Size", + } "CanvasItem": - props = ["modulate", "visible"] + props = { + "modulate": "Graphics", + "visible": "Graphics", + } "RigidBody2D": for verb in ["entered", "exited"]: @@ -361,14 +446,20 @@ static func get_built_in_categories(_class_name: String) -> Array[BlockCategory] # convert to path b.statement = "func _on_body_%s(_body: Node):\n\tvar body: NodePath = _body.get_path()" % [verb] b.signal_name = "body_%s" % [verb] + b.category = "Signal" block_list.append(b) var b = BLOCKS["statement_block"].instantiate() b.block_format = "Set Physics Position {position: VECTOR2}" b.statement = "PhysicsServer2D.body_set_state(get_rid(),PhysicsServer2D.BODY_STATE_TRANSFORM,Transform2D.IDENTITY.translated({position}))" + b.category = "Movement" block_list.append(b) - props = ["mass", "linear_velocity", "angular_velocity"] + props = { + "mass": "Size", + "linear_velocity": "Movement", + "angular_velocity": "Movement", + } "Area2D": for verb in ["entered", "exited"]: @@ -378,17 +469,13 @@ static func get_built_in_categories(_class_name: String) -> Array[BlockCategory] # convert to path b.statement = "func _on_body_%s(_body: Node2D):\n\tvar body: NodePath = _body.get_path()" % [verb] b.signal_name = "body_%s" % [verb] + b.category = "Signal" block_list.append(b) var prop_list = ClassDB.class_get_property_list(_class_name, true) + block_list.append_array(blocks_from_property_list(prop_list, props)) - var class_cat: BlockCategory = category_from_property_list(prop_list, props, _class_name, Color.SLATE_GRAY) - block_list.append_array(class_cat.block_list) - class_cat.block_list = block_list - if block_list: - cats.append(class_cat) - - return cats + return block_list static func _get_input_blocks() -> Array[Block]: @@ -401,6 +488,7 @@ static func _get_input_blocks() -> Array[Block]: block.block_format = "Is action {action_name: OPTION} {action: OPTION}" block.statement = 'Input.is_action_{action}("{action_name}")' block.defaults = {"action_name": OptionData.new(InputMap.get_actions()), "action": OptionData.new(["pressed", "just_pressed", "just_released"])} + block.category = "Input" block_list.append(block) return block_list diff --git a/addons/block_code/ui/picker/picker.gd b/addons/block_code/ui/picker/picker.gd index 803d6698..4599b0d6 100644 --- a/addons/block_code/ui/picker/picker.gd +++ b/addons/block_code/ui/picker/picker.gd @@ -12,26 +12,24 @@ func bsd_selected(bsd: BlockScriptData): reset_picker() return + var blocks_to_add: Array[Block] = [] var categories_to_add: Array[BlockCategory] = [] - var found_simple_class_script = null + # By default, assume the class is built-in. + var parent_class: String = bsd.script_inherits for class_dict in ProjectSettings.get_global_class_list(): if class_dict.class == bsd.script_inherits: var script = load(class_dict.path) + if script.has_method("get_custom_categories"): + categories_to_add = script.get_custom_categories() if script.has_method("get_custom_blocks"): - categories_to_add = script.get_custom_blocks() - found_simple_class_script = script - break + blocks_to_add = script.get_custom_blocks() + parent_class = str(script.get_instance_base_type()) + break - var parent_class: String - if found_simple_class_script: - parent_class = str(found_simple_class_script.get_instance_base_type()) - else: # Built in - parent_class = bsd.script_inherits + blocks_to_add.append_array(CategoryFactory.get_inherited_blocks(parent_class)) - categories_to_add.append_array(CategoryFactory.get_inherited_categories(parent_class)) - - init_picker(categories_to_add) + init_picker(blocks_to_add, categories_to_add) func reset_picker(): @@ -39,13 +37,11 @@ func reset_picker(): c.queue_free() -func init_picker(extra_blocks: Array[BlockCategory] = []): +func init_picker(extra_blocks: Array[Block] = [], extra_categories: Array[BlockCategory] = []): reset_picker() - var block_categories := CategoryFactory.get_general_categories() - - if extra_blocks.size() > 0: - CategoryFactory.add_to_categories(block_categories, extra_blocks) + var blocks := CategoryFactory.get_general_blocks() + extra_blocks + var block_categories := CategoryFactory.get_categories(blocks, extra_categories) for _category in block_categories: var category: BlockCategory = _category as BlockCategory diff --git a/docs/OVERVIEW.md b/docs/OVERVIEW.md index a55ea97b..e9d26852 100644 --- a/docs/OVERVIEW.md +++ b/docs/OVERVIEW.md @@ -38,9 +38,9 @@ Blocks are created from a template such as `StatementBlock`, `ParameterBlock`, ` * `ControlBlock`: Dictates the flow of instruction, such as a loop or if/else statement. Can have multiple slots for calling different statements based on a conditional input. You can define a block: -1. Globally in `CategoryFactory.get_general_categories()` -2. For a specific built-in class in `CategoryFactory.get_built_in_categories()` -3. In a custom class by defining a method `static func get_custom_blocks() -> Array[BlockCategory]:`. See `SimpleCharacter` for an example. +1. Globally in `CategoryFactory.get_general_blocks()` +2. For a specific built-in class in `CategoryFactory.get_built_in_blocks()` +3. In a custom class by defining a method `static func get_custom_blocks() -> Array[Block]:`. See `SimpleCharacter` for an example. Here is an example of generating a `STATEMENT` block: ``` @@ -53,15 +53,17 @@ b.block_format = "Set Int {var: STRING} {value: INT}" # Define the statement that will be generated from the inputs. Use the parameter name to specify which goes where. b.statement = "VAR_DICT[{var}] = {value}" -``` -You can then add this new block to a `BlockCategory` like so: -``` -var variable_list: Array[Block] = [] -variable_list.append(b) -var variable_category: BlockCategory = BlockCategory.new("Variables", variable_list, Color("4f975d")) + +# Set the category too add the block to. +b.category = "Variables" ``` Look in `CategoryFactory` for more examples! +Each block gets added to a category in the Block Code editor by setting +the block's `category` property. If you have a custom class, you can add +a new category by defining a method `static func get_custom_categories() +-> Array[BlockCategory]:`. See `Pong` for an example. + Note: For `EntryBlock`s, you can use the syntax `[param: TYPE]` with square brackets instead of curlies to generate a copyable `ParameterBlock` that can be used in the below method. For example, to expose the `delta` argument in `func _on_process(delta):`, you would use the following format string: ``` diff --git a/tests/test_category_factory.gd b/tests/test_category_factory.gd new file mode 100644 index 00000000..a30a769d --- /dev/null +++ b/tests/test_category_factory.gd @@ -0,0 +1,45 @@ +extends GutTest +## Tests for CategoryFactory + + +func get_category_names(categories: Array[BlockCategory]) -> Array[String]: + var names: Array[String] = [] + for category in categories: + names.append(category.name) + return names + + +func get_class_category_names(_class_name: String) -> Array[String]: + var blocks: Array[Block] = CategoryFactory.get_inherited_blocks(_class_name) + return get_category_names(CategoryFactory.get_categories(blocks)) + + +func test_general_category_names(): + var blocks: Array[Block] = CategoryFactory.get_general_blocks() + var names: Array[String] = get_category_names(CategoryFactory.get_categories(blocks)) + assert_eq( + names, + [ + "Lifecycle", + "Signal", + "Control", + "Test", + "Math", + "Logic", + "Variables", + "Input", + "Sound", + ] + ) + + +const class_category_names = [ + ["Node2D", ["Movement", "Size", "Graphics"]], + ["Sprite2D", ["Movement", "Size", "Graphics"]], + ["Node", []], + ["Object", []], +] + + +func test_inherited_category_names(params = use_parameters(class_category_names)): + assert_eq(get_class_category_names(params[0]), params[1])