Files
modbison/scripts/studio/studio_collections_dock.gd
T

269 lines
8.8 KiB
GDScript

class_name StudioCollectionsDock
extends VBoxContainer
## Model Collections Browser Dock for ModBison Studio.
## Provides collection browsing, theme filtering, item previews,
## and drag-and-drop model placement into the 3D viewport.
signal model_selected(model_data: Dictionary)
signal model_drag_started(model_data: Dictionary)
signal collection_selected(collection_id: int)
const AssetCacheManagerClass = preload("res://scripts/studio/asset_cache_manager.gd")
var _search_edit: LineEdit
var _theme_option: OptionButton
var _collections_scroll: ScrollContainer
var _collections_vbox: VBoxContainer
var _models_grid: GridContainer
var _status_label: Label
var _collections_data: Array[Dictionary] = []
var _selected_collection_id: int = -1
var _cache_mgr = null
var _is_built: bool = false
func _init() -> void:
name = "StudioCollectionsDock"
custom_minimum_size = Vector2(280, 400)
size_flags_vertical = Control.SIZE_EXPAND_FILL
_cache_mgr = AssetCacheManagerClass.get_instance()
if not _is_built:
_build_ui()
_load_default_preset_collections()
func _ready() -> void:
if not _is_built:
_build_ui()
_load_default_preset_collections()
func _build_ui() -> void:
if _is_built:
return
_is_built = true
var header := Label.new()
header.text = "Poly Model Collections"
header.add_theme_font_size_override("font_size", 14)
header.add_theme_color_override("font_color", Color(1.0, 0.9, 0.35))
add_child(header)
var sub := Label.new()
sub.text = "Curated themed 3D model packs. Drag onto viewport to place."
sub.add_theme_font_size_override("font_size", 10)
sub.add_theme_color_override("font_color", Color(0.7, 0.7, 0.7))
sub.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
add_child(sub)
# Search Bar
_search_edit = LineEdit.new()
_search_edit.placeholder_text = "🔍 Search collections & models..."
_search_edit.text_changed.connect(_on_search_changed)
add_child(_search_edit)
# Theme Filter
var filter_row := HBoxContainer.new()
var theme_lbl := Label.new()
theme_lbl.text = "Theme:"
theme_lbl.add_theme_font_size_override("font_size", 11)
filter_row.add_child(theme_lbl)
_theme_option = OptionButton.new()
_theme_option.size_flags_horizontal = Control.SIZE_EXPAND_FILL
_theme_option.add_item("All Themes", 0)
_theme_option.set_item_metadata(0, "")
var themes := ["fantasy", "medieval", "nature", "sci-fi", "urban", "industrial", "props"]
for i in range(themes.size()):
var t: String = themes[i]
_theme_option.add_item(t.capitalize(), i + 1)
_theme_option.set_item_metadata(i + 1, t)
_theme_option.item_selected.connect(_on_theme_selected)
filter_row.add_child(_theme_option)
add_child(filter_row)
# Scroll area for collections
_collections_scroll = ScrollContainer.new()
_collections_scroll.size_flags_vertical = Control.SIZE_EXPAND_FILL
_collections_scroll.horizontal_scroll_mode = ScrollContainer.SCROLL_MODE_DISABLED
add_child(_collections_scroll)
_collections_vbox = VBoxContainer.new()
_collections_vbox.size_flags_horizontal = Control.SIZE_EXPAND_FILL
_collections_scroll.add_child(_collections_vbox)
_status_label = Label.new()
_status_label.text = "Ready"
_status_label.add_theme_font_size_override("font_size", 10)
_status_label.add_theme_color_override("font_color", Color(0.5, 0.5, 0.5))
add_child(_status_label)
func _load_default_preset_collections() -> void:
# Built-in collections for offline / studio design
var fantasy_pack := {
"id": 101,
"title": "Fantasy Ruins & Treasure",
"theme": "fantasy",
"description": "Stone pillars, treasure chests, and fantasy dungeon props",
"items": [
{"id": 1, "name": "Treasure Chest", "catalog_id": "chest_01", "asset_id": "props/chest.glb", "theme": "fantasy"},
{"id": 2, "name": "Buffalo Coin", "catalog_id": "coin_01", "asset_id": "props/buffalo_chip_coin.glb", "theme": "fantasy"},
{"id": 3, "name": "Pine Tree", "catalog_id": "tree_pine", "asset_id": "props/tree_pine_01.glb", "theme": "nature"},
]
}
var props_pack := {
"id": 102,
"title": "Town & Village Props",
"theme": "medieval",
"description": "Street lamps, wooden crates, and boundary rocks",
"items": [
{"id": 4, "name": "Wooden Crate", "catalog_id": "crate_small", "asset_id": "props/crate_small.glb", "theme": "props"},
{"id": 5, "name": "Street Lamp", "catalog_id": "lamp_post", "asset_id": "props/lamp_post_01.glb", "theme": "urban"},
{"id": 6, "name": "Garden Rock", "catalog_id": "rock_01", "asset_id": "props/rock_01.glb", "theme": "nature"},
]
}
var bison_pack := {
"id": 103,
"title": "Wildlife & Creatures",
"theme": "nature",
"description": "Wild Bison and terrain fauna",
"items": [
{"id": 7, "name": "ModBison", "catalog_id": "bison", "asset_id": "props/bison.glb", "theme": "nature"},
]
}
_collections_data = [fantasy_pack, props_pack, bison_pack]
_refresh_collections_display()
func add_collection(col_dict: Dictionary) -> void:
_collections_data.append(col_dict)
_refresh_collections_display()
func set_collections(cols: Array[Dictionary]) -> void:
_collections_data = cols
_refresh_collections_display()
func _on_search_changed(_new_text: String) -> void:
_refresh_collections_display()
func _on_theme_selected(_idx: int) -> void:
_refresh_collections_display()
func _refresh_collections_display() -> void:
for c in _collections_vbox.get_children():
c.queue_free()
var search_text := _search_edit.text.strip_edges().to_lower()
var selected_theme := ""
if _theme_option != null:
var meta = _theme_option.get_item_metadata(_theme_option.selected)
if meta != null:
selected_theme = str(meta).to_lower()
var matched_count := 0
for col in _collections_data:
var col_theme: String = str(col.get("theme", "")).to_lower()
var col_title: String = str(col.get("title", ""))
var col_desc: String = str(col.get("description", ""))
if selected_theme != "" and col_theme != selected_theme:
continue
if search_text != "":
var matches_title := col_title.to_lower().contains(search_text)
var matches_desc := col_desc.to_lower().contains(search_text)
var matches_item := false
for item in col.get("items", []):
if str(item.get("name", "")).to_lower().contains(search_text):
matches_item = true
break
if not matches_title and not matches_desc and not matches_item:
continue
var card := _create_collection_card(col)
_collections_vbox.add_child(card)
matched_count += 1
if matched_count == 0:
var empty_lbl := Label.new()
empty_lbl.text = "No collections found matching filter."
empty_lbl.add_theme_font_size_override("font_size", 11)
empty_lbl.add_theme_color_override("font_color", Color(0.6, 0.6, 0.6))
_collections_vbox.add_child(empty_lbl)
_status_label.text = "Showing %d collections" % matched_count
func _create_collection_card(col: Dictionary) -> Control:
var panel := PanelContainer.new()
var vbox := VBoxContainer.new()
panel.add_child(vbox)
var title_lbl := Label.new()
title_lbl.text = "📦 " + str(col.get("title", "Collection"))
title_lbl.add_theme_font_size_override("font_size", 12)
title_lbl.add_theme_color_override("font_color", Color(0.3, 0.85, 1.0))
vbox.add_child(title_lbl)
var theme_lbl := Label.new()
theme_lbl.text = "Theme: %s | %d models" % [str(col.get("theme", "general")).capitalize(), (col.get("items", []) as Array).size()]
theme_lbl.add_theme_font_size_override("font_size", 10)
theme_lbl.add_theme_color_override("font_color", Color(0.7, 0.7, 0.7))
vbox.add_child(theme_lbl)
# Grid of models in this collection
var items: Array = col.get("items", [])
var grid := GridContainer.new()
grid.columns = 2
grid.size_flags_horizontal = Control.SIZE_EXPAND_FILL
for item in items:
if item is Dictionary:
var btn := _create_model_item_button(item)
grid.add_child(btn)
vbox.add_child(grid)
return panel
func _create_model_item_button(item: Dictionary) -> Button:
var btn := ModelDragButton.new()
btn.model_data = item
btn.text = "🔹 " + str(item.get("name", "Model"))
btn.custom_minimum_size = Vector2(120, 28)
btn.size_flags_horizontal = Control.SIZE_EXPAND_FILL
btn.tooltip_text = "Click to select or drag directly into 3D viewport"
btn.pressed.connect(func():
model_selected.emit(item)
_status_label.text = "Selected: %s" % item.get("name")
)
btn.drag_started.connect(func(data):
model_drag_started.emit(data)
)
return btn
class ModelDragButton extends Button:
signal drag_started(data: Dictionary)
var model_data: Dictionary = {}
func _get_drag_data(_at_position: Vector2) -> Variant:
var preview := Label.new()
preview.text = "📦 " + str(model_data.get("name", "Model"))
preview.add_theme_color_override("font_color", Color(1.0, 0.95, 0.2))
set_drag_preview(preview)
drag_started.emit(model_data)
return {
"type": "model_placement",
"catalog_id": model_data.get("catalog_id", ""),
"asset_id": model_data.get("asset_id", ""),
"model_name": model_data.get("name", ""),
"model_data": model_data
}