Skip to content

UI Placeable Sequence & List Components

Status: Experimental (subject to API refinements)

This document describes the new UI components that replace the basic ItemList-driven placeable selection with a richer, extensible system supporting variant cycling via left/right arrows.

  • Provide a flexible per-entry layout (icon, name, variant index, navigation arrows)
  • Support variant sequences (e.g. different visual styles or upgrade tiers) under a single selectable slot
  • Clean keyboard & mouse navigation
  • Theming consistent with the cyan/magenta dark UI palette

Groups an ordered list of placeable-like resources (each expected to expose display_name and optional icon).

Fields:

  • display_name: StringLabel for the whole sequence (used when variant elements lack a name)
  • placeables: Array[Resource] – Ordered variants
  • icon: Texture2D (optional) – Representative icon if you want a static image (falls back to first variant’s icon)

Utility methods:

  • count() -> int
  • get_variant(index: int) -> Resource
  • variant_display_name(index: int) -> String

Single UI row representing a PlaceableSequence.

Features:

  • Displays active variant icon & name
  • Variant index label (e.g. 2/3) when more than one
  • Left/Right arrow buttons (hidden if count() <= 1)
  • Keyboard: Left / Right to cycle variants; Enter / Space or mouse click selects
  • Emits:
    • selected(entry)
    • variant_changed(entry, variant_index)

Scrollable container managing multiple PlaceableListEntry instances.

Features:

  • Vertical navigation with Up / Down keys
  • Maintains selection state & highlight
  • Emits selection_changed(entry) when selection or active variant (of selected) changes
  • API:
    • add_sequence(sequence: PlaceableSequence)
    • clear()
    • get_selected_entry()

res://templates/grid_building_templates/ui/placement_selection/placeable_list_demo.tscn populates several sequences (some with a single variant, others with three) to demonstrate behavior.

var seq := PlaceableSequence.new()
seq.display_name = "Wall Segments"
for i in 4:
var variant := load("res://placeables/wall_variant_%d.tres" % i)
seq.placeables.append(variant)
placeable_list.add_sequence(seq)

React to selection:

placeable_list.selection_changed.connect(func(entry):
var active_variant = entry._active_object() # internal helper; expose wrapper if needed
# Use active_variant to drive preview/build
)

Why not extend Placeable? Keeping PlaceableSequence separate avoids changing existing placement validation paths (each variant is still a normal placeable resource). The sequence groups them only for UI selection convenience.

Why not ItemList? We need per-row controls (arrows) and custom layout + theming per entry which ItemList does not support without hacks.

  • Initial selection auto-focuses first entry
  • Up/Down traverse entries
  • Left/Right only act on currently focused entry to change its variant
  • Entry tooltip_text reflects variant status: Name (Variant 2/3)

Future enhancements (not implemented yet):

  • Drag & drop reordering within a sequence
  • Favorite pinning & filtering
  • Persist last chosen variant per sequence in player profile
  • Search box above the list

\n## Theming Each entry uses dynamic StyleBoxFlat overrides for normal/selected states. Integrate with a global theme by replacing these overrides or injecting a custom style via a factory method later.

  1. Replace existing ItemList usage in PlaceableSelectionUI with PlaceableList node.
  2. Wrap all single placeables into sequences of length 1 (helper factory recommended).
  3. On selection change, resolve active variant to feed existing placement preview flow (no rule changes required).

\n## Helper Factory (Suggested) (Not yet implemented) A small utility that takes an Array of Placeable or Array[PlaceableSequence|Placeable] and returns only sequences (wrapping singles). This reduces UI wiring code.

static func normalize_sequences(items: Array) -> Array[PlaceableSequence]:
var out: Array[PlaceableSequence]
for it in items:
if it is PlaceableSequence:
out.append(it)
elif it is Placeable:
var seq := PlaceableSequence.new()
seq.display_name = it.display_name
seq.placeables.append(it)
out.append(seq)
return out
  • Empty sequence: arrows hidden; entry still selectable (might represent a placeholder). Consider preventing empty sequences in production builds.
  • Null icons: entry reserves icon space for alignment consistency.
  • Rapid variant cycling: stateless; no debounce needed unless variant load is heavy.
  • Add sequences wrapping for all existing placeables
  • Replace old selection signal binding with new selection_changed
  • Update any tests referencing the UI list to accept sequences
  • Add factory utility if repetition appears

Last Updated: 2025-08-09