From eab4f1fa0888275d576014380b0e06cc57f34526 Mon Sep 17 00:00:00 2001 From: Patrick Wuttke Date: Sat, 21 Aug 2021 17:14:52 +0200 Subject: [PATCH] Added spoilers and entity/entity-array properties to property grid. --- scenes/property_list/entity_spoiler.tscn | 7 ++ scenes/property_list/entity_tree_spoiler.tscn | 7 ++ .../property_list/scripts/entity_spoiler.gd | 22 ++++ .../scripts/entity_tree_spoiler.gd | 26 ++++ scenes/scripts/spoiler.gd | 115 ++++++++++++++++++ scenes/spoiler.tscn | 31 +++++ .../types/controls/dynamic_grid_container.gd | 17 ++- scripts/types/controls/entity_tree.gd | 25 +++- scripts/types/controls/property_list.gd | 50 +++++++- .../types/controls/simple_property_edit.gd | 2 +- .../constraints/dynamic_grid_constraints.gd | 41 ++++++- 11 files changed, 327 insertions(+), 16 deletions(-) create mode 100644 scenes/property_list/entity_spoiler.tscn create mode 100644 scenes/property_list/entity_tree_spoiler.tscn create mode 100644 scenes/property_list/scripts/entity_spoiler.gd create mode 100644 scenes/property_list/scripts/entity_tree_spoiler.gd create mode 100644 scenes/scripts/spoiler.gd create mode 100644 scenes/spoiler.tscn diff --git a/scenes/property_list/entity_spoiler.tscn b/scenes/property_list/entity_spoiler.tscn new file mode 100644 index 0000000..37381e5 --- /dev/null +++ b/scenes/property_list/entity_spoiler.tscn @@ -0,0 +1,7 @@ +[gd_scene load_steps=3 format=2] + +[ext_resource path="res://addons/de.mewin.gduibasics/scenes/spoiler.tscn" type="PackedScene" id=1] +[ext_resource path="res://addons/de.mewin.gduibasics/scenes/property_list/scripts/entity_spoiler.gd" type="Script" id=2] + +[node name="spoiler" instance=ExtResource( 1 )] +script = ExtResource( 2 ) diff --git a/scenes/property_list/entity_tree_spoiler.tscn b/scenes/property_list/entity_tree_spoiler.tscn new file mode 100644 index 0000000..e139ae3 --- /dev/null +++ b/scenes/property_list/entity_tree_spoiler.tscn @@ -0,0 +1,7 @@ +[gd_scene load_steps=3 format=2] + +[ext_resource path="res://addons/de.mewin.gduibasics/scenes/spoiler.tscn" type="PackedScene" id=1] +[ext_resource path="res://addons/de.mewin.gduibasics/scenes/property_list/scripts/entity_tree_spoiler.gd" type="Script" id=2] + +[node name="spoiler" instance=ExtResource( 1 )] +script = ExtResource( 2 ) diff --git a/scenes/property_list/scripts/entity_spoiler.gd b/scenes/property_list/scripts/entity_spoiler.gd new file mode 100644 index 0000000..31cd214 --- /dev/null +++ b/scenes/property_list/scripts/entity_spoiler.gd @@ -0,0 +1,22 @@ +extends "res://addons/de.mewin.gduibasics/scenes/scripts/spoiler.gd" + +var entity : Object setget _set_entity + +############# +# overrides # +############# +func _lazy_load_child() -> Control: + var prop_list : Control = load("res://addons/de.mewin.gduibasics/scripts/types/controls/property_list.gd").new() + prop_list.size_flags_horizontal |= SIZE_EXPAND + prop_list.entity = entity + return prop_list + +########### +# setters # +########### +func _set_entity(val : Object) -> void: + if val != entity: + entity = val + var prop_list := _get_child() + if prop_list: + prop_list.entity = entity diff --git a/scenes/property_list/scripts/entity_tree_spoiler.gd b/scenes/property_list/scripts/entity_tree_spoiler.gd new file mode 100644 index 0000000..4fa17b2 --- /dev/null +++ b/scenes/property_list/scripts/entity_tree_spoiler.gd @@ -0,0 +1,26 @@ +extends "res://addons/de.mewin.gduibasics/scenes/scripts/spoiler.gd" + +# only used to store the entities before the tree is created +var __entities := [].duplicate() + +############# +# overrides # +############# +func _lazy_load_child() -> Control: + var tree : Control = load("res://addons/de.mewin.gduibasics/scripts/types/controls/entity_tree.gd").new() + tree.size_flags_horizontal |= SIZE_EXPAND + tree.rect_min_size.y = 300 # ? + for entity in __entities: + tree.add_entity(entity) + __entities.clear() + return tree + +################ +# public stuff # +################ +func add_entity(entity : Object) -> void: + var tree := _get_child() + if tree: + tree.add_entity(entity) + else: + __entities.append(entity) diff --git a/scenes/scripts/spoiler.gd b/scenes/scripts/spoiler.gd new file mode 100644 index 0000000..bb1ec5c --- /dev/null +++ b/scenes/scripts/spoiler.gd @@ -0,0 +1,115 @@ +extends VBoxContainer + +export(Texture) var icon_shown : Texture = preload("res://addons/de.mewin.gduibasics/images/arrow_down.svg") +export(Texture) var icon_hidden : Texture = preload("res://addons/de.mewin.gduibasics/images/arrow_right.svg") +var child_visible : bool setget _set_child_visible, _get_child_visible +var label := "" setget _set_label + +onready var _header : Control = GDBUtility.find_node_by_name(self, "header") +onready var _tex_arrow : TextureRect = GDBUtility.find_node_by_name(self, "tex_arrow") + +var __last_child : Control = null + +############# +# overrides # +############# +func _ready() -> void: + __update_child() + _header.label = label + +func add_child(child : Node, legible_unique_name := false): + .add_child(child, legible_unique_name) + __update_child() + +################ +# overridables # +################ +func _lazy_load_child() -> Control: + return null + +################ +# public stuff # +################ +func toggle() -> void: + _set_child_visible(!_get_child_visible()) + +################# +# private stuff # +################# +func _get_child() -> Control: + if get_child_count() < 2: + return null + return get_child(1) as Control + +func __update_child() -> void: + var the_child := _get_child() + if the_child == __last_child: + return + + if __last_child: + GDBUtility.disconnect_all(__last_child, self) + if the_child: + the_child.connect("visibility_changed", self, "_on_child_visibility_changed") + the_child.connect("tree_exiting", self, "_on_child_tree_exiting") + if the_child.visible != _get_child_visible(): + emit_signal("child_visibility_changed") + __last_child = the_child + __update_icon() + +func __update_icon() -> void: + if _get_child_visible(): + _tex_arrow.texture = icon_shown + else: + _tex_arrow.texture = icon_hidden + +##################### +# setters & getters # +##################### +func _set_label(val : String) -> void: + label = val + if _header: + _header.label = val + +func _set_child_visible(val : bool) -> void: + var child := _get_child() + if val && !child: + child = yield(GDBCoroutine.await(_lazy_load_child()), "completed") + if child: + emit_signal("child_created", child) + child.visible = true + add_child(child) + return + + if child && child.visible != val: + child.visible = val + emit_signal("on_child_visibility_changed") + +func _get_child_visible() -> bool: + var child := _get_child() + if child: + return child.visible + return false + +############ +# handlers # +############ +func _on_header_gui_input(event : InputEvent) -> void: + if event is InputEventMouseButton && event.button_index == BUTTON_LEFT && event.pressed: + toggle() + +func _on_child_visibility_changed() -> void: + __update_icon() + emit_signal("child_visibility_changed") + +func _on_child_tree_exiting() -> void: + var tree := get_tree() + if !tree: + return + yield(tree, "idle_frame") + __update_child() + +########### +# signals # +########### +signal child_created(child) +signal child_visibility_changed() diff --git a/scenes/spoiler.tscn b/scenes/spoiler.tscn new file mode 100644 index 0000000..1cedf95 --- /dev/null +++ b/scenes/spoiler.tscn @@ -0,0 +1,31 @@ +[gd_scene load_steps=4 format=2] + +[ext_resource path="res://addons/de.mewin.gduibasics/scenes/scripts/spoiler.gd" type="Script" id=1] +[ext_resource path="res://addons/de.mewin.gduibasics/scenes/property_list/header.tscn" type="PackedScene" id=2] +[ext_resource path="res://addons/de.mewin.gduibasics/images/arrow_right.svg" type="Texture" id=3] + +[node name="spoiler" type="VBoxContainer"] +anchor_right = 1.0 +anchor_bottom = 1.0 +script = ExtResource( 1 ) +__meta__ = { +"_edit_use_anchors_": false +} + +[node name="header" parent="." instance=ExtResource( 2 )] +margin_right = 1024.0 + +[node name="margin_container" type="MarginContainer" parent="header"] +margin_right = 1024.0 +margin_bottom = 22.0 +mouse_filter = 2 +custom_constants/margin_left = 5 + +[node name="tex_arrow" type="TextureRect" parent="header/margin_container"] +margin_left = 5.0 +margin_right = 1024.0 +margin_bottom = 22.0 +mouse_filter = 2 +texture = ExtResource( 3 ) + +[connection signal="gui_input" from="header" to="." method="_on_header_gui_input"] diff --git a/scripts/types/controls/dynamic_grid_container.gd b/scripts/types/controls/dynamic_grid_container.gd index 6d53521..dbea89e 100644 --- a/scripts/types/controls/dynamic_grid_container.gd +++ b/scripts/types/controls/dynamic_grid_container.gd @@ -74,11 +74,14 @@ func __sort_children(): rows[rows.size() - 1].append(child) child.rect_size = child.get_combined_minimum_size() - column_widths[col] = max(column_widths[col], child.rect_size.x) + + var full_size : Vector2 = child.rect_size + Vector2(constraints.padding_left + constraints.padding_right, \ + constraints.padding_top + constraints.padding_bottom) + column_widths[col] = max(column_widths[col], full_size.x) if child.size_flags_horizontal & Control.SIZE_EXPAND: column_expand[col] = max(column_expand[col], child.size_flags_stretch_ratio) - row_width += child.rect_size.x + row_width += full_size.x col = col + constraints.colspan @@ -100,22 +103,24 @@ func __sort_children(): col = 0 for child in row: var constraints := _get_constraints(child) - child.rect_position = pos + child.rect_position = pos + Vector2(constraints.padding_left, constraints.padding_top) var width := 0.0 for i in range(min(constraints.colspan, columns - col)): width += column_widths[col + i] if child.size_flags_horizontal & Control.SIZE_FILL: - child.rect_size.x = width + child.rect_size.x = width - (constraints.padding_left + constraints.padding_right) pos.x += width - row_height = max(row_height, child.rect_size.y) + var full_height : float = child.rect_size.y + constraints.padding_top + constraints.padding_bottom + row_height = max(row_height, full_height) col = col + constraints.colspan for child in row: if child.size_flags_vertical & Control.SIZE_FILL: - child.rect_size.y = row_height + var constraints := _get_constraints(child) + child.rect_size.y = row_height - (constraints.padding_top + constraints.padding_bottom) pos.y += row_height pos.x = 0.0 diff --git a/scripts/types/controls/entity_tree.gd b/scripts/types/controls/entity_tree.gd index b56911e..f1ea16f 100644 --- a/scripts/types/controls/entity_tree.gd +++ b/scripts/types/controls/entity_tree.gd @@ -14,12 +14,10 @@ onready var __selected_entity_adapter := get_node_or_null(selected_entity_adapte func _ready(): create_item() # invisible root hide_root = true - columns = column_ids.size() select_mode = SELECT_ROW set_column_titles_visible(true) - for col in range(min(columns, column_titles.size())): - set_column_title(col, column_titles[col]) + __update_columns() connect("item_selected", self, "_on_item_selected") if __selected_entity_adapter != null: @@ -33,6 +31,9 @@ func add_entity(entity : Object) -> void: printerr("UIB_EntityTree: attempted to add invalid entity") return + if !column_ids: + __columns_from_entity(entity) + var itm := create_item() itm.set_metadata(0, entity) var col := 0 @@ -101,6 +102,24 @@ func __update_row(entity : Object, column := ""): itm.set_text(col_idx, GDB_Property.get_prop_value_as_string(prop)) +func __columns_from_entity(entity : Object) -> void: + column_ids = [] + column_titles = [] + + for prop_id in GDB_Entity.get_entity_property_ids(entity): + var prop : Object = entity.get_property_by_id(prop_id) + if prop && prop.get_type() in [TYPE_STRING, TYPE_INT, TYPE_REAL, TYPE_BOOL] \ + && !(GDB_Property.get_prop_flags(prop) & GDB_Property.FLAG_HIDDEN): + column_ids.append(prop_id) + column_titles.append(prop.get_name()) + + __update_columns() + +func __update_columns() -> void: + columns = max(1, column_ids.size()) + for col in range(min(columns, column_titles.size())): + set_column_title(col, column_titles[col]) + ############ # handlers # ############ diff --git a/scripts/types/controls/property_list.gd b/scripts/types/controls/property_list.gd index 5f9828d..9a346b6 100644 --- a/scripts/types/controls/property_list.gd +++ b/scripts/types/controls/property_list.gd @@ -2,6 +2,8 @@ extends UIB_DynamicGridContainer class_name UIB_PropertyList +const COLUMNS = 2 + export(NodePath) var entity_adapter := "" var entity : Object = null setget _set_entity @@ -13,7 +15,7 @@ onready var __entity_adapter := get_node_or_null(entity_adapter) as GDB_DataAdap # overrides # ############# func _ready() -> void: - self.columns = 2 + self.columns = COLUMNS if __entity_adapter != null: __entity_adapter.connect("data_changed", self, "_on_entity_adapter_data_changed") @@ -22,13 +24,20 @@ func _ready() -> void: # overridables # ################ func _make_controls(property : Object) -> Array: + if GDB_Property.get_prop_flags(property) & GDB_Property.FLAG_HIDDEN: + return [] + match property.get_type(): GDB_Property.TYPE_HEADER: return _make_header(property) + GDB_Property.TYPE_ENTITY: + return _make_controls_entity(property) TYPE_INT, TYPE_REAL, TYPE_STRING: return _make_controls_simple(property) TYPE_BOOL: return _make_controls_bool(property) + TYPE_ARRAY: + return _make_controls_array(property) _: return [] @@ -37,9 +46,17 @@ func _make_header(property : Object) -> Array: var header := preload("res://addons/de.mewin.gduibasics/scenes/property_list/header.tscn").instance() header.label = property.get_name() header.texture = GDB_Property.get_prop_icon(property) - get_constraints(header).colspan = self.columns + get_constraints(header).colspan = COLUMNS return [header] +func _make_controls_entity(property : Object) -> Array: + var spoiler := preload("res://addons/de.mewin.gduibasics/scenes/property_list/entity_spoiler.tscn").instance() + spoiler.label = property.get_name() + spoiler.entity = property.get_value() + get_constraints(spoiler).colspan = COLUMNS + get_constraints(spoiler).padding_left = 5 + return [spoiler] + func _make_controls_simple(property : Object) -> Array: var label := preload("res://addons/de.mewin.gduibasics/scenes/property_list/property_label.tscn").instance() label.label = property.get_name() @@ -57,11 +74,29 @@ func _make_controls_bool(property : Object) -> Array: var checkbox := UIB_PropertyCheckBox.new() checkbox.property = property checkbox.size_flags_horizontal |= SIZE_EXPAND + checkbox.disabled = !(GDB_Property.get_prop_flags(property) & GDB_Property.FLAG_READONLY) get_constraints(checkbox).colspan = self.columns return [ checkbox ] +func _make_controls_array(property : Object) -> Array: + match GDB_Property.get_prop_content_type(property): + GDB_Property.TYPE_ENTITY: + return _make_controls_entity_array(property) + _: + return [] + +func _make_controls_entity_array(property : Object) -> Array: + var spoiler := preload("res://addons/de.mewin.gduibasics/scenes/property_list/entity_tree_spoiler.tscn").instance() + spoiler.label = property.get_name() + for entity in property.get_value(): + spoiler.add_entity(entity) + spoiler.connect("child_created", self, "_on_entity_array_tree_created") + get_constraints(spoiler).colspan = COLUMNS + get_constraints(spoiler).padding_left = 5 + return [spoiler] + ################ # public stuff # ################ @@ -139,6 +174,9 @@ func __init_entity() -> void: if entity == null: return + var title_property := GDB_Entity.make_entity_title_property(entity) + if title_property: + pass for property in entity.get_properties(): add_property(property) @@ -162,6 +200,14 @@ func _set_entity(entity_ : Object) -> void: func _on_entity_adapter_data_changed() -> void: _set_entity(__entity_adapter.data) +func _on_entity_array_tree_created(tree : UIB_EntityTree) -> void: + tree.connect("item_activated", self, "_on_entity_array_tree_item_activated", [tree]) + +func _on_entity_array_tree_item_activated(tree : UIB_EntityTree) -> void: + var entity : Object = tree.get_selected_entity() + if entity: + _set_entity(entity) + ########### # signals # ########### diff --git a/scripts/types/controls/simple_property_edit.gd b/scripts/types/controls/simple_property_edit.gd index 3537604..cbaefa6 100644 --- a/scripts/types/controls/simple_property_edit.gd +++ b/scripts/types/controls/simple_property_edit.gd @@ -21,7 +21,7 @@ func _gui_input(event : InputEvent) -> void: ################# func __init_property() -> void: text = __text_from_property() - editable = (property != null) + editable = (property != null && !(GDB_Property.get_prop_flags(property) & GDB_Property.FLAG_READONLY)) __validate_text() if property != null: diff --git a/scripts/types/misc/constraints/dynamic_grid_constraints.gd b/scripts/types/misc/constraints/dynamic_grid_constraints.gd index d57a39b..4536cc9 100644 --- a/scripts/types/misc/constraints/dynamic_grid_constraints.gd +++ b/scripts/types/misc/constraints/dynamic_grid_constraints.gd @@ -5,21 +5,54 @@ class_name UIB_DynamicGridConstraints export(int, 1, 100) var colspan := 1 setget _set_colspan export(int, 1, 100) var rowspan := 1 setget _set_rowspan +export(int) var padding_top := 0 setget _set_padding_top +export(int) var padding_bottom := 0 setget _set_padding_bottom +export(int) var padding_left := 0 setget _set_padding_left +export(int) var padding_right := 0 setget _set_padding_right -func _ready(): +############# +# overrides # +############# +func _ready() -> void: __trigger_update() -func __trigger_update(): +################# +# private stuff # +################# +func __trigger_update() -> void: var container := get_node_or_null("../..") as Container # cyclic inclusion, cannot use UIB_DynamicGridContainer if container != null: container.notification(Container.NOTIFICATION_SORT_CHILDREN) -func _set_colspan(value : int): +########### +# setters # +########### +func _set_colspan(value : int) -> void: if value != colspan: colspan = value __trigger_update() -func _set_rowspan(value : int): +func _set_rowspan(value : int) -> void: if value != rowspan: rowspan = value __trigger_update() + +func _set_padding_top(value : int) -> void: + if value != padding_top: + padding_top = value + __trigger_update() + +func _set_padding_bottom(value : int) -> void: + if value != padding_bottom: + padding_bottom = value + __trigger_update() + +func _set_padding_left(value : int) -> void: + if value != padding_left: + padding_left = value + __trigger_update() + +func _set_padding_right(value : int) -> void: + if value != padding_right: + padding_right = value + __trigger_update()