323 lines
12 KiB
GDScript
323 lines
12 KiB
GDScript
class_name AssetSelector
|
|
extends PopupPanel
|
|
|
|
signal item_selected(entry: Dictionary)
|
|
signal cancelled()
|
|
signal browse_requested()
|
|
signal refresh_requested()
|
|
signal make_artwork_requested()
|
|
signal edit_scenery_requested()
|
|
|
|
const COLUMNS := 4
|
|
const ROWS := 3
|
|
const PAGE_SIZE := COLUMNS * ROWS
|
|
|
|
const CELL_MIN_SIZE := Vector2(190.0, 220.0)
|
|
const THUMB_SIZE := Vector2(156.0, 156.0)
|
|
const ASSET_CARD := preload("res://scripts/shared/ui/asset_card.gd")
|
|
const BACKGROUND_CARD_PREVIEW := preload("res://scripts/stage/scenery/stage_background_card_preview.gd")
|
|
const STARTER_CARD_PREVIEW := preload("res://scripts/stage/scenery/stage_starter_card_preview.gd")
|
|
|
|
var kind: String = ""
|
|
|
|
var _entries: Array[Dictionary] = []
|
|
var _page: int = 0
|
|
var _thumbnails: Dictionary = {}
|
|
var _cell_texrects: Dictionary = {}
|
|
var _placeholder_tex: ImageTexture = null
|
|
var _ui_font: Font = null
|
|
var _emoji_font: Font = null
|
|
var _sandbox_theme: Variant = null
|
|
var _gallery_tab: String = "backgrounds"
|
|
var _tag_id: String = "all"
|
|
var _current_sky_id: String = ""
|
|
var _sky_card_group: ButtonGroup
|
|
|
|
@onready var _title_label: Label = %TitleLabel
|
|
@onready var _grid: GridContainer = %GridContainer
|
|
@onready var _empty_label: Label = %EmptyLabel
|
|
@onready var _page_label: Label = %PageLabel
|
|
@onready var _prev_button: Button = %PrevButton
|
|
@onready var _next_button: Button = %NextButton
|
|
@onready var _browse_button: Button = %BrowseButton
|
|
@onready var _refresh_button: Button = %RefreshButton
|
|
@onready var _close_button: Button = %CloseButton
|
|
@onready var _tabs: HBoxContainer = %GalleryTabs
|
|
@onready var _backgrounds_tab: Button = %BackgroundsTab
|
|
@onready var _artwork_tab: Button = %ArtworkTab
|
|
@onready var _tag_filter: OptionButton = %TagFilter
|
|
@onready var _make_artwork_button: Button = %MakeArtworkButton
|
|
@onready var _edit_scenery_button: Button = %EditSceneryButton
|
|
|
|
|
|
func _ready() -> void:
|
|
exclusive = true
|
|
if _tag_filter.item_count == 0:
|
|
for label: String in ["All artwork", "Nature", "Buildings", "Walls", "Roads", "Objects"]:
|
|
_tag_filter.add_item(label)
|
|
_prev_button.pressed.connect(_on_prev)
|
|
_next_button.pressed.connect(_on_next)
|
|
_browse_button.pressed.connect(func() -> void: browse_requested.emit())
|
|
_refresh_button.pressed.connect(func() -> void: refresh_requested.emit())
|
|
_close_button.pressed.connect(func() -> void: cancelled.emit())
|
|
_backgrounds_tab.pressed.connect(func() -> void: _set_gallery_tab("backgrounds"))
|
|
_artwork_tab.pressed.connect(func() -> void: _set_gallery_tab("artwork"))
|
|
_tag_filter.item_selected.connect(_on_tag_filter_selected)
|
|
_make_artwork_button.pressed.connect(func() -> void: make_artwork_requested.emit())
|
|
_edit_scenery_button.pressed.connect(func() -> void: edit_scenery_requested.emit())
|
|
get_tree().root.size_changed.connect(_on_root_size_changed)
|
|
|
|
|
|
func _on_root_size_changed() -> void:
|
|
if visible:
|
|
popup_centered()
|
|
|
|
|
|
func open(p_kind: String, entries: Array[Dictionary], current_sky_id: String = "") -> void:
|
|
kind = p_kind
|
|
_current_sky_id = current_sky_id if kind == "scenery" else ""
|
|
_title_label.text = "Choose Your Stickman" if kind == "stickman" else ("Choose Scenery" if kind == "scenery" else ("Choose a starter scene" if kind == "starters" else "Choose a Prop"))
|
|
_tabs.visible = kind == "scenery"
|
|
_tag_filter.visible = false
|
|
_make_artwork_button.visible = false
|
|
_edit_scenery_button.visible = false
|
|
_browse_button.visible = kind == "stickman" or kind == "prop" or kind == "scenery"
|
|
_refresh_button.visible = kind == "stickman" or kind == "prop" or kind == "scenery"
|
|
_empty_label.text = "No scenery found. Choose Make artwork to draw a new piece." if kind == "scenery" else ("No props found. Browse for a .prop file." if kind == "prop" else ("No starter scenes are available." if kind == "starters" else "No stickmen found."))
|
|
_gallery_tab = "backgrounds"
|
|
_tag_id = "all"
|
|
_backgrounds_tab.set_pressed_no_signal(true)
|
|
_artwork_tab.set_pressed_no_signal(false)
|
|
_tag_filter.select(0)
|
|
set_entries(entries)
|
|
if kind == "scenery":
|
|
_set_gallery_tab("backgrounds")
|
|
popup_centered()
|
|
|
|
|
|
func set_entries(entries: Array[Dictionary]) -> void:
|
|
_entries = entries
|
|
_thumbnails.clear()
|
|
_cell_texrects.clear()
|
|
_page = 0
|
|
_rebuild()
|
|
|
|
|
|
func set_thumbnail(entry: Dictionary, tex: Texture2D) -> void:
|
|
var key := _entry_key(entry)
|
|
_thumbnails[key] = tex
|
|
if _cell_texrects.has(key):
|
|
(_cell_texrects[key] as TextureRect).texture = tex
|
|
|
|
|
|
func set_current_sky(sky_id: StringName) -> void:
|
|
var next_id: String = String(sky_id)
|
|
if kind != "scenery" or _current_sky_id == next_id:
|
|
return
|
|
_current_sky_id = next_id
|
|
if _gallery_tab == "backgrounds":
|
|
_rebuild()
|
|
|
|
|
|
func close() -> void:
|
|
hide()
|
|
|
|
|
|
func apply_font(ui_font: Font, emoji_font: Font) -> void:
|
|
_ui_font = ui_font
|
|
_emoji_font = emoji_font
|
|
var controls: Array = [
|
|
_title_label, _empty_label, _page_label,
|
|
_prev_button, _next_button, _browse_button, _refresh_button, _close_button,
|
|
]
|
|
for c: Control in controls:
|
|
_apply_font_to(c)
|
|
|
|
func apply_theme(theme: Variant) -> void:
|
|
_sandbox_theme = theme
|
|
if theme != null:
|
|
self.theme = theme.control_theme()
|
|
add_theme_stylebox_override("panel", theme.stylebox("popups.asset_selector"))
|
|
var cell_size: Variant = theme.value("popups.asset_selector.cell_size", CELL_MIN_SIZE)
|
|
if cell_size is Array and (cell_size as Array).size() == 2:
|
|
_cell_min_size = Vector2(float(cell_size[0]), float(cell_size[1]))
|
|
var thumbnail_size: Variant = theme.value("popups.asset_selector.thumbnail_size", THUMB_SIZE)
|
|
if thumbnail_size is Array and (thumbnail_size as Array).size() == 2:
|
|
_thumbnail_size = Vector2(float(thumbnail_size[0]), float(thumbnail_size[1]))
|
|
|
|
var _cell_min_size: Vector2 = CELL_MIN_SIZE
|
|
var _thumbnail_size: Vector2 = THUMB_SIZE
|
|
|
|
|
|
func _unhandled_input(event: InputEvent) -> void:
|
|
if visible and event is InputEventKey:
|
|
var key := event as InputEventKey
|
|
if key.pressed and not key.echo and key.keycode == KEY_ESCAPE:
|
|
get_viewport().set_input_as_handled()
|
|
cancelled.emit()
|
|
|
|
|
|
static func page_bounds(total: int, page: int, page_size: int = PAGE_SIZE) -> Dictionary:
|
|
var page_count := maxi(1, int(ceil(float(total) / float(page_size))))
|
|
var start := page * page_size
|
|
if start >= total:
|
|
start = 0
|
|
var end := mini(start + page_size, total)
|
|
return { "start": start, "end": end, "total": total, "page_count": page_count }
|
|
|
|
|
|
func _entry_key(entry: Dictionary) -> String:
|
|
if kind == "stickman":
|
|
return String(entry.get("path", ""))
|
|
return String(entry.get("id", ""))
|
|
|
|
|
|
func _on_tag_filter_selected(index: int) -> void:
|
|
var ids: Array[String] = ["all", "nature", "buildings", "walls", "roads", "objects"]
|
|
_tag_id = ids[clampi(index, 0, ids.size() - 1)]
|
|
_page = 0
|
|
_rebuild()
|
|
|
|
|
|
func _set_gallery_tab(tab: String) -> void:
|
|
_gallery_tab = tab
|
|
_backgrounds_tab.set_pressed_no_signal(tab == "backgrounds")
|
|
_artwork_tab.set_pressed_no_signal(tab == "artwork")
|
|
_tag_filter.visible = kind == "scenery" and tab == "artwork"
|
|
_make_artwork_button.visible = kind == "scenery" and tab == "artwork"
|
|
_edit_scenery_button.visible = kind == "scenery"
|
|
_browse_button.visible = kind == "stickman" or kind == "prop" or (kind == "scenery" and tab == "artwork")
|
|
_refresh_button.visible = kind == "stickman" or kind == "prop" or (kind == "scenery" and tab == "artwork")
|
|
_empty_label.text = "No artwork in this group yet." if tab == "artwork" else "Choose a Sky look or wall finish."
|
|
_page = 0
|
|
_rebuild()
|
|
|
|
|
|
func _filtered_entries() -> Array[Dictionary]:
|
|
if kind != "scenery":
|
|
return _entries
|
|
var filtered: Array[Dictionary] = []
|
|
for entry: Dictionary in _entries:
|
|
if String(entry.get("gallery_tab", "artwork")) != _gallery_tab:
|
|
continue
|
|
if _gallery_tab == "artwork" and _tag_id != "all":
|
|
var tags: Array = entry.get("tags", []) as Array
|
|
if not tags.has(_tag_id):
|
|
continue
|
|
filtered.append(entry)
|
|
return filtered
|
|
|
|
|
|
func _on_prev() -> void:
|
|
if _page > 0:
|
|
_page -= 1
|
|
_rebuild()
|
|
|
|
|
|
func _on_next() -> void:
|
|
if (_page + 1) * PAGE_SIZE < _filtered_entries().size():
|
|
_page += 1
|
|
_rebuild()
|
|
|
|
|
|
func _page_count() -> int:
|
|
var entries: Array[Dictionary] = _filtered_entries()
|
|
if entries.is_empty():
|
|
return 1
|
|
return int(ceil(float(entries.size()) / float(PAGE_SIZE)))
|
|
|
|
|
|
func _rebuild() -> void:
|
|
_sky_card_group = ButtonGroup.new()
|
|
for c: Node in _grid.get_children():
|
|
c.queue_free()
|
|
_cell_texrects.clear()
|
|
var filtered: Array[Dictionary] = _filtered_entries()
|
|
_empty_label.visible = filtered.is_empty()
|
|
_grid.visible = not filtered.is_empty()
|
|
var total := _page_count()
|
|
var single := total <= 1
|
|
_prev_button.visible = not single
|
|
_next_button.visible = not single
|
|
_page_label.visible = not single
|
|
_prev_button.disabled = _page <= 0
|
|
_next_button.disabled = (_page + 1) * PAGE_SIZE >= filtered.size()
|
|
_page_label.text = "Page %d/%d" % [_page + 1, total]
|
|
if filtered.is_empty():
|
|
return
|
|
var bounds := page_bounds(filtered.size(), _page)
|
|
for i: int in range(int(bounds["start"]), int(bounds["end"])):
|
|
_grid.add_child(_build_cell(filtered[i]))
|
|
|
|
|
|
func _build_cell(entry: Dictionary) -> Control:
|
|
var key := _entry_key(entry)
|
|
var cell: Variant = ASSET_CARD.new()
|
|
# Selector cells inherit the popup theme. Avoid a per-card state override so
|
|
# opening a selector never implies that one asset is pre-selected.
|
|
var categories: Array = entry.get("tags", []) as Array
|
|
var category: String = ", ".join(PackedStringArray(categories)) if not categories.is_empty() else String(entry.get("category", entry.get("material_label", "")))
|
|
var card_size: Vector2 = _cell_min_size
|
|
var thumb_size: Vector2 = _thumbnail_size
|
|
if kind == "starters":
|
|
card_size.y = maxf(card_size.y, 248.0)
|
|
thumb_size = Vector2(128.0, 128.0)
|
|
var card_payload: Dictionary = {"placement_id": _entry_key(entry), "label": String(entry.get("name", entry.get("id", ""))), "icon": String(entry.get("icon", "")), "category": category, "description": String(entry.get("description", "")), "thumbnail_size": thumb_size}
|
|
if entry.has("background_kind"):
|
|
card_payload["background_kind"] = entry["background_kind"]
|
|
card_payload["finish_id"] = entry.get("finish_id", "")
|
|
card_payload["id"] = entry.get("id", "")
|
|
var is_sky_choice: bool = String(entry.get("background_kind", "")) == "sky"
|
|
var is_current_sky: bool = is_sky_choice and String(entry.get("id", "")) == _current_sky_id
|
|
if is_current_sky:
|
|
card_payload["status"] = "Current Sky"
|
|
card_payload["status_color"] = _theme_color("mode_colors.edit", Color("#22C6FF"))
|
|
cell.configure(card_payload, _thumbnails.get(key, null), null)
|
|
cell.custom_minimum_size = card_size
|
|
cell.focus_mode = Control.FOCUS_ALL
|
|
cell.activated.connect(func(_payload: Dictionary) -> void: _on_cell_pressed(entry))
|
|
if is_sky_choice:
|
|
cell.button_group = _sky_card_group
|
|
cell.set_selected(is_current_sky, _theme_color("stage_panels.choice.selected_surface", Color("#12394A")), _theme_color("mode_colors.edit", Color("#22C6FF")))
|
|
var tex: Texture2D = _thumbnails.get(key, null)
|
|
if kind == "starters":
|
|
var preview: Control = STARTER_CARD_PREVIEW.new() as Control
|
|
preview.call("configure", entry)
|
|
cell.set_preview_control(preview)
|
|
elif entry.has("background_kind"):
|
|
var preview: StageBackgroundCardPreview = BACKGROUND_CARD_PREVIEW.new() as StageBackgroundCardPreview
|
|
preview.configure(entry)
|
|
cell.set_preview_control(preview)
|
|
else:
|
|
cell.set_thumbnail(tex if tex != null else _get_placeholder())
|
|
_cell_texrects[key] = cell.thumbnail_rect()
|
|
|
|
return cell
|
|
|
|
|
|
func _theme_color(path: String, fallback: Color) -> Color:
|
|
if _sandbox_theme != null:
|
|
return _sandbox_theme.color(path, fallback)
|
|
return fallback
|
|
|
|
|
|
func _on_cell_pressed(entry: Dictionary) -> void:
|
|
item_selected.emit(entry)
|
|
|
|
|
|
func _apply_font_to(c: Control) -> void:
|
|
if c == null:
|
|
return
|
|
if _ui_font != null:
|
|
c.add_theme_font_override("font", _ui_font)
|
|
elif _emoji_font != null:
|
|
c.add_theme_font_override("font", _emoji_font)
|
|
|
|
|
|
func _get_placeholder() -> ImageTexture:
|
|
if _placeholder_tex == null:
|
|
var img := Image.create(int(_thumbnail_size.x), int(_thumbnail_size.y), false, Image.FORMAT_RGBA8)
|
|
img.fill(Color(0.16, 0.16, 0.16, 1.0))
|
|
_placeholder_tex = ImageTexture.create_from_image(img)
|
|
return _placeholder_tex
|