Combo box (external piece)
Authoring
use day_piece_combobox::combo_box;
let flavors = Signal::new(vec!["vanilla".into(), "chocolate".into()]);
let flavor = Signal::new(String::new());
combo_box(flavors, flavor).placeholder("Type or pick a flavor").id("flavor")
combo_box(items, text) binds text two-way: typing writes the signal per keystroke, picking a
dropdown item writes the item’s string, and setting the signal patches the control (echo-guarded,
§4.4: the guard remembers the last value that arrived from the native control so bind_seeded
does not patch it straight back). items is reactive: a change patches the native dropdown live,
and the typed text survives the swap. .placeholder(impl IntoText) sets the empty-state prompt
(read once at build). ComboBox implements Piece, so .id()/.a11y()/.frame() chain via
Decorate. Like text_field it is a width-growing leaf (grow_w = true, natural
single-line height).
Every backend reports both change paths (typing and picking) through the one
Event::TextChanged(String), so dayscript’s input: step drives the entry on every backend.
The front-end also answers a synthetic Event::SelectionChanged(i) by mapping i through the
current items, so dayscript’s select: step is the scripted menu path (native backends never
emit it: a native pick already arrives as text).
Per-backend native realization
| AppKit | GTK | Qt | Android | XAML | UIKit | ArkUI |
|---|---|---|---|---|---|---|
NSComboBox | GtkComboBoxText with entry | editable QComboBox | AutoCompleteTextView | editable ComboBox (1809+) | — placeholder | — placeholder |
iOS and HarmonyOS have no native combo-box control, so the piece carries no renderer
there: day renders its placeholder leaf, and the showcase adds a footnote saying why.
Use picker or text_field on those platforms. The change plumbing per backend:
- AppKit: one per-node delegate serves both halves,
NSControlTextEditingDelegate::controlTextDidChange:for keystrokes andNSComboBoxDelegate::comboBoxSelectionDidChange:for picks. The selection notification fires before the control writes the pick into its ownstringValue, so the handler reads the selected item’s string instead.setCompletes(true)gives inline autocompletion. ProgrammaticsetStringValuefires neither (no suppression needed). - GTK:
GtkComboBoxText::with_entry(); the internalGtkEntry’schangedsignal is the single change path (a pick writes the entry). It also fires on programmaticset_text, so a per-nodesuppresscell guards the sync. GTK 4.10 deprecatedGtkComboBoxTextwithout an editable replacement (GtkDropDownhas no entry), so the renderer keeps it under a commentedallow(deprecated). - Qt / XAML: each carries its own C++ shim inside this crate (
src/lib-qt-shim.cpp,src/lib-xaml-shim.cpp), compiled by the crate’sbuild.rs. The Qt shim is aQComboBoxwithsetEditable(true)+NoInsert;editTextChangedis the single change path, and programmatic setters sit inblockSignals. The XAML shim boxes an editableComboBoxthrough theday_xaml_box/day_xaml_unboxfunctions. Documented divergence: XAML’sComboBoxhas no per-keystroke text event, so free-form text commits on Enter or focus loss (TextSubmitted/LostFocus) while picks report immediately (SelectionChanged). - Android: carries its own Java factory
(
src/DayCombo.java), folded into the app’s Gradle build via[package.metadata.day.android]with no edits to day-android. Android’s combo box isAutoCompleteTextView: suggestions prefix-filter while typing, and a tap or focus pops the dropdown open so the list is reachable without typing. OneTextWatcherreports both paths asDayBridge.K_TEXT_CHANGED; the programmatic setters guard on equality.
Verification
The showcase Controls page (controls.rs flavor_block) binds a combo_box to a flavor
signal with a localized three-item list, an Add button that pushes the typed text into the
items, and a readout mirroring the signal. The walkthrough drives all three behaviors: select
index 2 (menu path, asserted by localized key), input a literal that is in no list (free-form
path), then Add + select index 3 (an index that exists only after the add), which shows the
reactive item list round-trips. Runs on macOS-AppKit, GTK, Qt, iOS-sim (placeholder + synthetic
steps), and the Android emulator. Rust is clippy-clean (-D warnings) and cargo fmt-clean for
every backend feature.
Follow-ups
- Reactive placeholder (currently fixed at build).
Event::Submittedon Return for “commit” semantics distinct from per-keystroke changes.- Disabled/enabled state; a max-visible-items hint for the dropdown.
XAML is CI-only (built under cfg(windows) + the Windows SDK); it isn’t buildable on the
macOS/Linux dev hosts.