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.

Goals

  • 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

Components Overview

PlaceableSequence (Resource)

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

Fields:

  • display_name: String – Label 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

PlaceableListEntry (PanelContainer)

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)

PlaceableList (Control)

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()

Demo Scene

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.

Usage Example

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
)

Design Notes

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.

Keyboard & Accessibility

  • 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)

Extensibility Ideas

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.

Integration Steps

  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

Validation & Edge Cases

  • 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.

Migration Checklist

  • [ ] 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