Reference · v2 · A3 Landscape

Dropdown Types in SpaceMusic

Five distinct dropdown configurations compared head-to-head — what the user sees, and the engine that feeds it under the hood.
date  2026-04-24
branch  ProjectNova
scope  C# UI · SMCodeGen · Channels
01
UI Dropdown · Static Engine

Static Dropdown

Fixed list of choices compiled into a strongly-typed enum. The workhorse — used whenever the options are known at build time and never change.
On Off On COMPILE-TIME READONLY
csv type
Dropdown or RadioButton
param
ParamOptions<TEnum>
value
TEnum (typed)
entries
Static Values[] in generated enum
base
DynamicEnumDefinitionBase<T>
observable
Observable.Empty
update
Regenerate code
scope
Global
// Dropdowns.csv OffOn | Dropdown | "Off;On" // Generated public static readonly string[] Values = { "Off", "On" };
RadioButton is currently a pure alias — same engine, same UiFactory call, rendered as a ComboBox. A dedicated radio control is not yet implemented.
02
UI Dropdown · Dynamic Engine

Dynamic Dropdown

Same ComboBox UI, but the entry list is globally updatable at runtime from a vvvv patch — useful for data discovered after start-up (themes, loaded files, scanned devices).
VVVV UpdateDropdownEnum REGISTRY TryUpdateEnum() Theme A RUNTIME · GLOBAL
csv flag
Values col = "dynamic" or empty
param
ParamOptions<TEnum>
value
TEnum (typed)
entries
Singleton Instance — no static array
base
ManualDynamicEnumDefinitionBase<T>
observable
Instance.OnChange
update
vvvv UpdateDropdownEnum node
scope
Global · one list for all UI
// Patch side UpdateDropdownEnum( enumName: ThemeNames, entries: ["Dark","Light","Neon"]); // Registry mutates singleton; // all subscribers see new list.
03
UI Dropdown · Inst.-Update Engine

Instance-Update Dropdown

Entry list is derived from template instance counts via a pattern (e.g. "Light %Lights%"). Rebuilt automatically whenever the user adds or removes instances in the scene.
COUNTS Lights = 3 REBUILDER.G.CS template expansion Light 2 Light 1 · Light 2 · Light 3
csv flag
InstanceUpdate = 1
param
ParamOptions<TEnum>
value
TEnum (typed)
entries
Pattern expanded against instance counts
base
ManualDynamicEnumDefinitionBase<T>
observable
Instance.OnChange
update
DynamicInstanceCoordinator on count change
scope
Global · reflects scene counts
// Pattern in Values column "Light %Lights%; All Lights" // Generated rebuilder static void RebuildFocusEnum( int lightsCount) { … }
04
UI MultiLevel · Static (dot-path)

MultiLevel Dropdown

N stacked ComboBoxes with cascading filtering. Entries are dot-separated paths in a static enum (e.g. "Texture 1.L.Front"); each level filters the next by prefix.
TEX Texture 1 CH L FACE Front CASCADE
csv type
MultiLevelDropdown
param
ParamOptions<TEnum>
value
string (dot-joined path)
entries
Static enum · uniform dot-depth
base
DynamicEnumDefinitionBase<T>
observable
Observable.Empty
wiring
Only last level writes to channel
labels
"Cat.Plugin" → level names
// Generated UiFactory.MultiLevelDropdownRow( "Category.Plugin", provider, "Project.Env…LightSelection", "LightSelection");
05
UI UpdateDropdown · Channel-backed · TypeID 22

Update Dropdown

Per-channel dropdown whose entry list lives inside the channel itself (ParamUpdateOptions.Options). No enum involved — truly independent per instance.
VVVV PATCH writes Options CHANNEL · PER-INSTANCE Options = "A:Alpha;B:Beta" Value = "A" A (Alpha) SPLIT ON ';' · COLON = DISPLAY
csv type
UpdateDropdown · ID = -1
param
ParamUpdateOptions
value
string (bare key)
entries
The channel itself · Options, ';'-split
base
none — no enum
observable
Channel value observable
update
Any write to Options
scope
Per instance · each channel its own list
extras
key:display syntax · Options persisted
// On the channel Options = "Tex1:Liquish;Tex2:Glass"; Value = "Tex1"; // UI renders "Tex1 (Liquish)"; // stores the bare key "Tex1".
Data flow

From CSV declaration to UI binding

① CSV input
② Code generation
③ Runtime state
④ UI binding
01 Static
Type Dropdown, Values filled: "Off;On"
Values[] const array + TEnumDefinition : DynamicEnumDefinitionBase
Array is readonly, never mutated. No singleton needed.
DropdownRow reads Values once → ComboBox populated at first render.
02 Dynamic
Type Dropdown, Values = "dynamic" or empty
ManualDynamicEnumDefinitionBase, empty Initialize()
UpdateDropdownEnumEnumRegistry.TryUpdateEnum (BeginUpdate / AddEntry / EndUpdate)
Subscribes to Instance.OnChange, rebuilds items with writingBack guard.
03 Inst.-Update
Type Dropdown, InstanceUpdate=1, pattern "Light %Lights%"
Rebuild{Name}Enum(int counts…) emitted in InstanceUpdateEnumRebuilder.g.cs
DynamicInstanceCoordinator calls rebuilder on count changes; entries re-expanded.
Same path as Dynamic — subscribes to Instance.OnChange.
04 MultiLevel
Type MultiLevelDropdown, dot-paths of equal depth
Same as Static — Values[] baked in; paths round-trip as one string.
Readonly. Selection stored as joined path; upper levels are UI-only.
MultiLevelDropdownRow splits label + entries on '.', builds N cascading ComboBoxes.
05 Update · T22
Type UpdateDropdown, DropdownID=-1, no Values needed
No enum. Parameter typed as ParamUpdateOptions in ParameterHierarchy.g.cs
Channel holds the full list; per-instance state persisted in scene JSON.
DirectChannelProvider reflects on Options + Value, re-splits on change.
At a glance

Comparison matrix

Aspect Static Dynamic Inst.-Upd. MultiLvl TypeID 22
Runtime-updatable list global via counts per instance
Selected value type TEnum TEnum TEnum string path string
Param record ParamOptions<TEnum> ParamUpdateOptions
Cascading UI N levels
key:display syntax yes
Scene persists selected value only Options + Value
Entries change event Observable.Empty Definition.OnChange Definition.OnChange Observable.Empty Channel value obs.
Thread-safety guard not needed writingBack writingBack not needed writingBack
Typical use case modes, flags, quality themes, scanned files per-section refs category → plugin per-instance lists