You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
ComboBox and RadioGroup: writes to current-value are silently overridden
Bug Description
ComboBox.current-value and RadioGroup.current-value are declared in-out, suggesting host code can write them. In practice, writes from host code are silently overridden by a declarative binding inside the widget — the host's write is dropped without warning and the property re-derives from model[current-index].
In internal/compiler/widgets/common/combobox-base.slint:
RadioGroup (internal/compiler/widgets/common/radiogroup-base.slint) has the identical pattern (current-value: root.model[root.current-index].text).
When the host calls set_current_value("X") from Rust/C++/JS, the property briefly holds "X", then the next read re-derives via the binding back to model[current-index]. The host's intent is lost, and there is no diagnostic.
slint::slint! {
import {ComboBox} from "std-widgets.slint";
export component App inherits Window{
in-out property <int> idx <=> cb.current-index;
in-out property <string> val <=> cb.current-value;
cb := ComboBox{
model:["A","B","C","D"];}}}let app = App::new()?;
app.set_val(SharedString::from("C"));// expected: idx == 2, val == "C"// observed: idx == 0, val == "A"
What the fix should look like (Option A)
A host writing set_current_value("C") is expressing intent "select the row whose value is C". The widget should reverse-look-up the written value in the model and update current-index accordingly:
changedcurrent-value => {
ifroot.current-value != root.model[root.current-index] {
// Find the value in the model; set current-index to the first match,// or to -1 if not found. The subsequent changed current-index handler// will write current-value back to model[current-index] — which is// what the host wrote when it was found, and "" when it wasn't.root.current-index = find-index-for-value(root.current-value);
}
}
This aligns with the philosophy of the prior two fixes in this area:
Reverse lookup applies the same principle to value-based writes.
Why this can't ship today
Slint has no iteration in function bodies and forbids recursive functions (the compiler reports a binding loop on a recursive helper). There is no [T].index-of(value: T) -> int builtin paralleling [T].length. So find-index-for-value(...) cannot be written in pure Slint.
Once those land, the combobox-base.slint fix is the ~10 lines sketched above (in both ComboBox and RadioGroup).
Interim mitigation proposal
Until the language work lands, surface the silent override as a runtime warning in the affected widgets. Both combobox-base.slint and radiogroup-base.slint would gain:
changedcurrent-value => {
ifroot.current-value != root.model[root.current-index] {
debug("ComboBox: writes to `current-value` from host code are not supported "
+ "and will be silently overridden. Use `current-index` to change the "
+ "selection. Tracking: <this issue URL>.");
root.update-current-value(); // explicit revert
}
}
debug() is the existing Slint builtin (expression_tree.rs:354) which routes through log::debug! or stderr. It only fires when the host's write actually diverges from the model-derived value, so hosts following the documented contract (writing current-index instead) never see it. The block is small (~6 lines per widget) and trivially removed once Option A becomes implementable.
Documentation on the affected current-value properties should be updated in parallel to recommend current-index and warn that direct writes are unsupported.
ComboBox and RadioGroup: writes to
current-valueare silently overriddenBug Description
ComboBox.current-valueandRadioGroup.current-valueare declaredin-out, suggesting host code can write them. In practice, writes from host code are silently overridden by a declarative binding inside the widget — the host's write is dropped without warning and the property re-derives frommodel[current-index].In
internal/compiler/widgets/common/combobox-base.slint:RadioGroup(internal/compiler/widgets/common/radiogroup-base.slint) has the identical pattern (current-value: root.model[root.current-index].text).When the host calls
set_current_value("X")from Rust/C++/JS, the property briefly holds"X", then the next read re-derives via the binding back tomodel[current-index]. The host's intent is lost, and there is no diagnostic.Reproducer
Headless test, runs against current master (
c0a00d2):https://github.com/tilladam/slint-popup-authority-probe/blob/main/tests/binding_tests.rs#L182-L233 (test
claim_set_current_value_finds_index_in_model, currently marked#[ignore]pending a fix).Distilled:
What the fix should look like (Option A)
A host writing
set_current_value("C")is expressing intent "select the row whose value is C". The widget should reverse-look-up the written value in the model and updatecurrent-indexaccordingly:This aligns with the philosophy of the prior two fixes in this area:
310b01a2e(closing ComboBox reset current on model changed #5217): addedchanged model => reset-current()— keep widget state coherent across data changes.0b425c541(closing combo box auto resets current value #10805): changed reset → clamp — preserve host intent whenever the data permits.Reverse lookup applies the same principle to value-based writes.
Why this can't ship today
Slint has no iteration in function bodies and forbids recursive functions (the compiler reports a binding loop on a recursive helper). There is no
[T].index-of(value: T) -> intbuiltin paralleling[T].length. Sofind-index-for-value(...)cannot be written in pure Slint.The blockers for a pure-language fix:
good first issue, rfc; required for usable iteration.for-instatement loop in slint language #8561 ("for-instatement loop in slint language") —ogoffartpreviously commented that iteration limited to models is acceptable in principle, contingent on Add support for local variables #2752.Once those land, the combobox-base.slint fix is the ~10 lines sketched above (in both ComboBox and RadioGroup).
Interim mitigation proposal
Until the language work lands, surface the silent override as a runtime warning in the affected widgets. Both
combobox-base.slintandradiogroup-base.slintwould gain:debug()is the existing Slint builtin (expression_tree.rs:354) which routes throughlog::debug!or stderr. It only fires when the host's write actually diverges from the model-derived value, so hosts following the documented contract (writingcurrent-indexinstead) never see it. The block is small (~6 lines per widget) and trivially removed once Option A becomes implementable.Documentation on the affected
current-valueproperties should be updated in parallel to recommendcurrent-indexand warn that direct writes are unsupported.Related
for-instatement loop in slint language #8561 work..slintdirectly #1328 (Filter and map models in.slintdirectly) — open since 2022. Same enabler set.current-indexrace, not reproducible on master after0b425c541. Cited here only as context for the prior fix philosophy.Suggested next steps
for-instatement loop in slint language #8561.Happy to send a PR for the interim runtime warning (in both widgets) once a direction is chosen.
Environment
c0a00d2)