diff --git a/assets/locales/pending/configuration-rule-targets-note.json b/assets/locales/pending/configuration-rule-targets-note.json new file mode 100644 index 0000000000..aa37372bc0 --- /dev/null +++ b/assets/locales/pending/configuration-rule-targets-note.json @@ -0,0 +1,30 @@ +{ + "en": { + "config.rule_parameter_columns": "Only parameters with a column in the table are listed. Add one with Add Column (+) to use it here.", + "config.rule_suppression_columns": "Only features with a Suppress column in the table are listed. Add one with Add Column (+) to drive it here." + }, + "ar": { + "config.rule_parameter_columns": "لا تظهر هنا إلا المعاملات التي لها عمود في الجدول. أضف عمودًا عبر «إضافة عمود» (+) لاستخدام المعامل هنا.", + "config.rule_suppression_columns": "لا تظهر هنا إلا الميزات التي لها عمود «تعطيل» في الجدول. أضف عمودًا عبر «إضافة عمود» (+) للتحكم بالميزة هنا." + }, + "de": { + "config.rule_parameter_columns": "Hier stehen nur Parameter mit einer Spalte in der Tabelle. Fügen Sie eine mit „Spalte hinzufügen“ (+) hinzu, um den Parameter hier zu verwenden.", + "config.rule_suppression_columns": "Hier stehen nur Funktionen mit einer Spalte „Unterdrücken“ in der Tabelle. Fügen Sie eine mit „Spalte hinzufügen“ (+) hinzu, um die Funktion hier zu steuern." + }, + "he": { + "config.rule_parameter_columns": "מוצגים כאן רק פרמטרים שיש להם עמודה בטבלה. הוסיפו עמודה דרך \"הוספת עמודה\" (+) כדי להשתמש בפרמטר כאן.", + "config.rule_suppression_columns": "מוצגים כאן רק פיצ'רים שיש להם עמודת \"השבתה\" בטבלה. הוסיפו עמודה דרך \"הוספת עמודה\" (+) כדי לשלוט בפיצ'ר כאן." + }, + "ja": { + "config.rule_parameter_columns": "表に列があるパラメータのみが表示されます。ここで使うには「列を追加」(+) で列を追加してください。", + "config.rule_suppression_columns": "表に「抑制」列があるフィーチャーのみが表示されます。ここで制御するには「列を追加」(+) で列を追加してください。" + }, + "vi": { + "config.rule_parameter_columns": "Chỉ những tham số có cột trong bảng mới được liệt kê. Thêm cột bằng “Thêm cột” (+) để dùng tham số đó ở đây.", + "config.rule_suppression_columns": "Chỉ những tính năng có cột “Bỏ kích hoạt (Suppress)” trong bảng mới được liệt kê. Thêm cột bằng “Thêm cột” (+) để điều khiển tính năng đó ở đây." + }, + "zh": { + "config.rule_parameter_columns": "此处仅列出在表中有列的参数。使用“添加列”(+) 添加一列,即可在此使用该参数。", + "config.rule_suppression_columns": "此处仅列出在表中有“抑制”列的特征。使用“添加列”(+) 添加一列,即可在此控制该特征。" + } +} diff --git a/crates/ui-egui/src/configuration_rules_ui.rs b/crates/ui-egui/src/configuration_rules_ui.rs index 1fe0f88f85..627fa4de88 100644 --- a/crates/ui-egui/src/configuration_rules_ui.rs +++ b/crates/ui-egui/src/configuration_rules_ui.rs @@ -86,6 +86,10 @@ pub(super) fn show(ui: &mut Ui, app: &mut SolveApp, error: &mut Option, let features: Vec<(u64, String)> = app.session.doc.features.iter().map(|f| (f.id, f.name.clone())).collect(); let suppression_features: Vec<(u64, String)> = features.iter().filter(|(id, _)| app.session.doc.configs.columns.contains(&Column::Suppress(*id))).cloned().collect(); + // A rule's target must be a column of the table (the engine refuses any other), so the Then + // lists leave out what has none; a note then says so, where When lists everything (#10532). + let unlisted_features = suppression_features.len() < features.len(); + let unlisted_parameters = all_parameters.iter().any(|p| !parameters.contains(p)); ui_kit::divider(ui, "configuration_rules"); ui_kit::text(ui, crate::i18n::tr_label("Configuration rules"), ui_kit::Text::Heading("rules_heading")); ui_kit::text( @@ -179,17 +183,20 @@ pub(super) fn show(ui: &mut Ui, app: &mut SolveApp, error: &mut Option, RuleEffect::DriveParameter { parameter, expression: value } => { ui.vertical(|ui| { choose(ui, "effect_parameter", parameter, ¶meters); + columns_note(ui, unlisted_parameters, "config.rule_parameter_columns"); expression(ui, "Value", value); }); } RuleEffect::DriveSuppression { feature, value } => { ui.vertical(|ui| { feature_choice(ui, "effect_feature", feature, &suppression_features); + columns_note(ui, unlisted_features, "config.rule_suppression_columns"); ui_kit::checkbox(ui, crate::i18n::tr_label("Suppressed"), value); }); } RuleEffect::ParameterRange { parameter, minimum, maximum } => { choose(ui, "range_parameter", parameter, ¶meters); + columns_note(ui, unlisted_parameters, "config.rule_parameter_columns"); ui.vertical(|ui| { expression(ui, "Minimum", minimum); }); @@ -199,6 +206,7 @@ pub(super) fn show(ui: &mut Ui, app: &mut SolveApp, error: &mut Option, } RuleEffect::AllowParameterValues { parameter, expressions } => { choose(ui, "allowed_parameter", parameter, ¶meters); + columns_note(ui, unlisted_parameters, "config.rule_parameter_columns"); let mut text = expressions.join("; "); if ui_kit::row(ui, "Allowed expressions, separated by semicolons", |ui| ui_kit::text_field(ui, &mut text, "").1) { *expressions = text.split(';').map(|s| s.trim().to_string()).collect(); @@ -313,6 +321,13 @@ pub(super) fn show(ui: &mut Ui, app: &mut SolveApp, error: &mut Option, } }); } +/// Under a Then list that leaves some out (`unlisted`): only the table's columns are listed, and +/// how to add one. +fn columns_note(ui: &mut Ui, unlisted: bool, message: &'static str) { + if unlisted { + ui_kit::text(ui, crate::i18n::tr(message), ui_kit::Text::Note(message)); + } +} fn report(ui: &mut Ui, text: impl AsRef) { ui_kit::text(ui, crate::i18n::tr_label(text.as_ref()), ui_kit::Text::Report("rule_report", false)); } diff --git a/crates/ui-egui/src/dialogs_motion.rs b/crates/ui-egui/src/dialogs_motion.rs index e4af799a72..9cde6e0b91 100644 --- a/crates/ui-egui/src/dialogs_motion.rs +++ b/crates/ui-egui/src/dialogs_motion.rs @@ -895,11 +895,19 @@ mod tests { // Kit rows publish identities independently of translated text. Long forms scroll through // the same wheel events as a person; assertions still verify the saved rules and geometry. + // A table row scrolled out of view is not built, so its handle is missing until the form + // scrolls back up to the table above the rules. fn reveal_config_handle(h: &mut crate::scenario::Harness, handle: &str) { for _ in 0..24 { - let at = crate::scenario::handle_at(handle).unwrap_or_else(|| panic!("missing {handle}: {:?}", h.app.dialog)); let header = crate::scenario::rect_of("dialog:header").unwrap(); let footer = crate::scenario::rect_of("dialog:footer").unwrap(); + let at = crate::scenario::handle_at(handle); + if handle.starts_with("config:activate:") && at.is_none() { + h.call("ui.scroll", json!({"x":header.left()+Tokens::get().space_6,"y":(header.bottom()+footer.top())*0.5,"delta":160.0})); + h.frames(6); + continue; + } + let at = at.unwrap_or_else(|| panic!("missing {handle}: {:?}", h.app.dialog)); if at.y > header.bottom() + 8.0 && at.y < footer.top() - 8.0 { return; } diff --git a/crates/ui-egui/src/manage_dialogs_tests.rs b/crates/ui-egui/src/manage_dialogs_tests.rs index d07b141bb5..448c8f9dfe 100644 --- a/crates/ui-egui/src/manage_dialogs_tests.rs +++ b/crates/ui-egui/src/manage_dialogs_tests.rs @@ -542,6 +542,32 @@ fn configuration_add_column_menu_filters_as_typed_and_resets_on_close() { assert!(h.app.dialog.is_some(), "menu Escape leaves the card open"); } +#[test] +fn a_rule_says_why_then_lists_only_the_tables_columns() { + // A rule drives or restricts a column of the table only, so Then leaves out a parameter or a + // feature without one, where When lists them all: a note says so and how to add one (#10532). + let parameters = "Only parameters with a column in the table are listed. Add one with Add Column (+) to use it here."; + let features = "Only features with a Suppress column in the table are listed. Add one with Add Column (+) to drive it here."; + let mut h = fixture(); + h.app.run("parameters.add", json!({"name":"depth","expression":"5 mm"})).unwrap(); + h.app.run("config.configure", json!({"params":["width"]})).unwrap(); + h.app.start("config.table"); + h.frames(4); + h.step(&json!({"click":{"handle":"dialog:section:Add or edit rule"}})).unwrap(); + h.frames(3); + assert!(h.widget(parameters).is_some(), "depth has no column, so Drive parameter says why it is not listed"); + h.step(&json!({"click":{"handle":"dialog:Then"}})).unwrap(); + h.step(&json!({"click":{"handle":"dialog:Then:Drive suppression"}})).unwrap(); + h.frames(3); + assert!(h.widget(features).is_some(), "no feature has a Suppress column, so the empty list says why"); + assert!(h.widget(parameters).is_none(), "the parameter note belongs to the parameter effects"); + for feature in h.app.session.doc.features.iter().map(|f| f.name.clone()).collect::>() { + h.app.run("config.column", json!({"feature": feature})).unwrap(); + } + h.frames(3); + assert!(h.widget(features).is_none(), "with a column for every feature, all are listed and the note goes"); +} + #[test] fn draft_bins_do_not_delete_saved_rows_named_new() { for command in ["parameters.change", "config.table"] {