diff --git a/ControlTree.gd b/ControlTree.gd new file mode 100644 index 0000000..bc69ae5 --- /dev/null +++ b/ControlTree.gd @@ -0,0 +1,29 @@ +extends Control +class_name ControlTree + +export(PackedScene) var item_scene: PackedScene + +export(bool) var allow_multi_select: bool = false + +onready var items_container: VBoxContainer = $"%Items" + +var items: Array = [] + +var item_select_btn_group: ButtonGroup = ButtonGroup.new() + + +func add_item(text: String, parent_item: ControlTreeItem = null) -> ControlTreeItem: + var new_item: ControlTreeItem = item_scene.instance() + + if !parent_item: + items_container.add_child(new_item) + items.append(new_item) + else: + parent_item.add_subitem(new_item) + + new_item.set_text(text) + + if !allow_multi_select: + new_item.set_button_group(item_select_btn_group) + + return new_item diff --git a/ControlTree.tscn b/ControlTree.tscn new file mode 100644 index 0000000..c90ba6b --- /dev/null +++ b/ControlTree.tscn @@ -0,0 +1,31 @@ +[gd_scene load_steps=3 format=2] + +[ext_resource path="res://ControlTree.gd" type="Script" id=1] +[ext_resource path="res://ControlTreeItem.tscn" type="PackedScene" id=2] + +[node name="ControlTree" type="Control"] +anchor_right = 0.492844 +anchor_bottom = 1.0 +margin_right = 0.327972 +script = ExtResource( 1 ) +__meta__ = { +"_edit_use_anchors_": true, +"_editor_description_": "An item tree with functionality similar to that of the built-in Tree control, but utilizing Control nodes instead of an internal state. +It is not recommended to create trees using scenes, the intended way is to create them through code." +} +item_scene = ExtResource( 2 ) + +[node name="ItemsList" type="PanelContainer" parent="."] +anchor_right = 1.0 +anchor_bottom = 1.0 + +[node name="ItemsScroller" type="ScrollContainer" parent="ItemsList"] +margin_left = 7.0 +margin_top = 7.0 +margin_right = 498.0 +margin_bottom = 593.0 + +[node name="Items" type="VBoxContainer" parent="ItemsList/ItemsScroller"] +unique_name_in_owner = true +margin_right = 491.0 +size_flags_horizontal = 3 diff --git a/ControlTreeItem.gd b/ControlTreeItem.gd new file mode 100644 index 0000000..d9f5a4e --- /dev/null +++ b/ControlTreeItem.gd @@ -0,0 +1,94 @@ +extends VBoxContainer +class_name ControlTreeItem + +# the margin of the offset on the subitem (if this item is a subitem) +const SUBITEM_MARGIN_SIZE := 32 + +# if this property is true, the item can be interacted with +# e.g, it can be renamed, its checkmark can be ticked +var editable: bool = true + +# proxy property for the CheckBox's `pressed` property +var checked: bool = false setget set_checked, is_checked + +# if true, this item can be reordered in the tree +var draggable: bool = true + +# an array of this item's subitems +var subitems: Array = [] + +# if true, this item's subitems will not be visible +var collapsed: bool = false + +onready var subitem_margin_container: MarginContainer = $"%SubitemMargin" +onready var collapse_button: Button = $"%CollapseButton" +onready var drag_handle: Panel = $"%DragHandle" +onready var check_box: CheckBox = $"%CheckBox" +onready var item_text_button: Button = $"%ItemTextButton" + +onready var subitem_container: VBoxContainer = $"%Subitems" + + +func _ready() -> void: +# warning-ignore:return_value_discarded +# warning-ignore:return_value_discarded + check_box.connect("pressed", self, "set_enabled", [true]) + collapse_button.connect("pressed", self, "toggle_collapsed") + + +func set_checked(v: bool, from_checkbox: bool = false) -> void: + checked = v + if from_checkbox: return + check_box.pressed = v + + +func is_checked() -> bool: + return checked + + +func set_text(t: String) -> void: + item_text_button.text = t + + +func get_text() -> String: + return item_text_button.text + + +func add_subitem(item: ControlTreeItem) -> void: + subitem_container.add_child(item) + subitems.append(item) + item.subitem_margin_container.visible = true + print('d: ', get_depth(item)) + item.subitem_margin_container.rect_min_size.x = SUBITEM_MARGIN_SIZE * get_depth(item)# - (clamp(collapse_button.rect_size.x * subitems.size(), 0, 1)) + collapse_button.visible = true + + +func set_button_group(bg: ButtonGroup) -> void: + item_text_button.group = bg + + +func get_depth(item: ControlTreeItem) -> int: + var depth := 0 + # this is the stupidest hack ever to get around cyclic deps + # basically, you can't check if an object is of the same class + # as self, ie `Object is ControlTreeItem` will error out + # so instead we're making a stupid, idiotic method + # that only this class has and checking for its presence + while item.get_parent().get_parent().has_method("i_am_item"): + depth += 1 + item = item.get_parent().get_parent() + + return depth + + +func i_am_item() -> void: + pass + + +func toggle_collapsed() -> void: + collapsed = !collapsed + + collapse_button.text = ">" if collapsed else "v" + + for i in subitems: + i.visible = !collapsed diff --git a/ControlTreeItem.tscn b/ControlTreeItem.tscn new file mode 100644 index 0000000..76fc6e6 --- /dev/null +++ b/ControlTreeItem.tscn @@ -0,0 +1,67 @@ +[gd_scene load_steps=2 format=2] + +[ext_resource path="res://ControlTreeItem.gd" type="Script" id=1] + +[node name="ControlTreeItem" type="VBoxContainer"] +anchor_right = 0.479 +anchor_bottom = 0.06 +margin_right = 0.503967 +script = ExtResource( 1 ) +__meta__ = { +"_editor_description_": "An item singlet, i.e. the quintessential item container type for ControlTree. Has a \"main\" item and potentially a number of sub-items." +} + +[node name="ItemAtom" type="HBoxContainer" parent="."] +margin_right = 490.0 +margin_bottom = 32.0 +rect_min_size = Vector2( 0, 32 ) +size_flags_vertical = 5 +__meta__ = { +"_editor_description_": "Atomic ControlTree item subtype, i.e. the smallest uncontained tree item that is only aware of itself and not any of its' sub-items." +} + +[node name="SubitemMargin" type="MarginContainer" parent="ItemAtom"] +unique_name_in_owner = true +visible = false +margin_bottom = 32.0 + +[node name="CollapseButton" type="Button" parent="ItemAtom"] +unique_name_in_owner = true +visible = false +margin_right = 19.0 +margin_bottom = 32.0 +toggle_mode = true +text = "v" +flat = true + +[node name="DragHandle" type="Panel" parent="ItemAtom"] +unique_name_in_owner = true +margin_right = 32.0 +margin_bottom = 32.0 +rect_min_size = Vector2( 32, 0 ) + +[node name="CheckBox" type="CheckBox" parent="ItemAtom"] +unique_name_in_owner = true +margin_left = 36.0 +margin_right = 60.0 +margin_bottom = 32.0 + +[node name="ItemTextButton" type="Button" parent="ItemAtom"] +unique_name_in_owner = true +margin_left = 64.0 +margin_right = 490.0 +margin_bottom = 32.0 +size_flags_horizontal = 3 +toggle_mode = true +text = "Item text" +flat = true +align = 0 + +[node name="Subitems" type="VBoxContainer" parent="."] +unique_name_in_owner = true +margin_top = 36.0 +margin_right = 490.0 +margin_bottom = 36.0 +__meta__ = { +"_editor_description_": "A container for a ControlTreeItem's sub-items, which are themselves ControlTreeItems." +} diff --git a/TestArea.tscn b/TestArea.tscn new file mode 100644 index 0000000..51b4106 --- /dev/null +++ b/TestArea.tscn @@ -0,0 +1,57 @@ +[gd_scene load_steps=3 format=2] + +[ext_resource path="res://ControlTree.tscn" type="PackedScene" id=1] + +[sub_resource type="GDScript" id=1] +script/source = "extends Control + +var i +var j + +func _ready() -> void: + pass + + +func _on_Button_pressed() -> void: + i = $ControlTree.add_item('test') + + +func _on_Button2_pressed() -> void: + j = $ControlTree.add_item('test2', i) + + +func _on_Button3_pressed() -> void: + $ControlTree.add_item('test3', j) +" + +[node name="TestArea" type="Control"] +anchor_right = 1.0 +anchor_bottom = 1.0 +script = SubResource( 1 ) + +[node name="ControlTree" parent="." instance=ExtResource( 1 )] + +[node name="Button" type="Button" parent="."] +margin_left = 634.0 +margin_top = 48.0 +margin_right = 765.0 +margin_bottom = 97.0 +text = "Add item" + +[node name="Button2" type="Button" parent="."] +margin_left = 634.0 +margin_top = 117.0 +margin_right = 765.0 +margin_bottom = 166.0 +text = "Add subitem" + +[node name="Button3" type="Button" parent="."] +margin_left = 634.0 +margin_top = 194.0 +margin_right = 765.0 +margin_bottom = 243.0 +text = "Add subsubitem" + +[connection signal="pressed" from="Button" to="." method="_on_Button_pressed"] +[connection signal="pressed" from="Button2" to="." method="_on_Button2_pressed"] +[connection signal="pressed" from="Button3" to="." method="_on_Button3_pressed"] diff --git a/default_env.tres b/default_env.tres new file mode 100644 index 0000000..20207a4 --- /dev/null +++ b/default_env.tres @@ -0,0 +1,7 @@ +[gd_resource type="Environment" load_steps=2 format=2] + +[sub_resource type="ProceduralSky" id=1] + +[resource] +background_mode = 2 +background_sky = SubResource( 1 ) diff --git a/icon.png b/icon.png new file mode 100644 index 0000000..c98fbb6 Binary files /dev/null and b/icon.png differ diff --git a/icon.png.import b/icon.png.import new file mode 100644 index 0000000..a4c02e6 --- /dev/null +++ b/icon.png.import @@ -0,0 +1,35 @@ +[remap] + +importer="texture" +type="StreamTexture" +path="res://.import/icon.png-487276ed1e3a0c39cad0279d744ee560.stex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://icon.png" +dest_files=[ "res://.import/icon.png-487276ed1e3a0c39cad0279d744ee560.stex" ] + +[params] + +compress/mode=0 +compress/lossy_quality=0.7 +compress/hdr_mode=0 +compress/bptc_ldr=0 +compress/normal_map=0 +flags/repeat=0 +flags/filter=true +flags/mipmaps=false +flags/anisotropic=false +flags/srgb=2 +process/fix_alpha_border=true +process/premult_alpha=false +process/HDR_as_SRGB=false +process/invert_color=false +process/normal_map_invert_y=false +stream=false +size_limit=0 +detect_3d=true +svg/scale=1.0 diff --git a/project.godot b/project.godot new file mode 100644 index 0000000..e5fd574 --- /dev/null +++ b/project.godot @@ -0,0 +1,42 @@ +; Engine configuration file. +; It's best edited using the editor UI and not directly, +; since the parameters that go here are not all obvious. +; +; Format: +; [section] ; section goes between [] +; param=value ; assign values to parameters + +config_version=4 + +_global_script_classes=[ { +"base": "Control", +"class": "ControlTree", +"language": "GDScript", +"path": "res://ControlTree.gd" +}, { +"base": "VBoxContainer", +"class": "ControlTreeItem", +"language": "GDScript", +"path": "res://ControlTreeItem.gd" +} ] +_global_script_class_icons={ +"ControlTree": "", +"ControlTreeItem": "" +} + +[application] + +config/name="customtree" +config/icon="res://icon.png" + +[gui] + +common/drop_mouse_on_gui_input_disabled=true + +[physics] + +common/enable_pause_aware_picking=true + +[rendering] + +environment/default_environment="res://default_env.tres"