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 variantsicon: Texture2D (optional)– Representative icon if you want a static image (falls back to first variant's icon)
Utility methods:
count() -> intget_variant(index: int) -> Resourcevariant_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_textreflects 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¶
- Replace existing ItemList usage in
PlaceableSelectionUIwithPlaceableListnode. - Wrap all single placeables into sequences of length 1 (helper factory recommended).
- 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