diff --git a/crates/ui-egui/src/panels/right.rs b/crates/ui-egui/src/panels/right.rs index d434a17b9..23b7adc2b 100644 --- a/crates/ui-egui/src/panels/right.rs +++ b/crates/ui-egui/src/panels/right.rs @@ -773,9 +773,13 @@ fn keywords(app: &mut LightcraftApp, ui: &mut egui::Ui, id: PhotoId) { padded(ui, |ui| { let kid = egui::Id::new("kw-input"); let mut text = ui.data_mut(|d| d.get_temp::(kid).unwrap_or_default()); - let r = ui.add(egui::TextEdit::singleline(&mut text).hint_text(crate::i18n::tr("Add keyword")).desired_width(f32::INFINITY)); - register(ui.ctx(), "field:keyword", r.rect); - if r.lost_focus() && ui.input(|i| i.key_pressed(egui::Key::Enter)) && !text.trim().is_empty() { + let r = + crate::text_field::TextField::singleline("field:keyword", &mut text).hint(crate::i18n::tr("Add keyword")).width(f32::INFINITY).show(ui); + // Return gives the keywords to the selected photos; Esc gives up what was typed + if r.cancelled() { + text.clear(); + } + if r.ending == Some(crate::text_field::Ending::Return) && !text.trim().is_empty() { // a new name goes inside the default parent (Put New Keywords Inside This Keyword) let kws: Vec = text.split(',').map(|s| app.session.catalog.typed_keyword(s)).filter(|s| !s.is_empty()).collect(); let _ = app.run("photo.setMeta", json!({"addKeywords": kws})); diff --git a/crates/ui-egui/src/tests_keywording.rs b/crates/ui-egui/src/tests_keywording.rs index 4f8b034f7..c5dd7ea37 100644 --- a/crates/ui-egui/src/tests_keywording.rs +++ b/crates/ui-egui/src/tests_keywording.rs @@ -86,3 +86,24 @@ fn a_chips_menu_acts_on_the_selection() { ask(&mut h, "ui.clickWidget", json!({"id": "keywordChipMenu:edit"})); assert!(matches!(&h.app.ui.dialog, Some(crate::state::Dialog::KeywordTag { editing: Some(k), .. }) if k == "Weddings"), "{:?}", h.app.ui.dialog); } + +/// The "Add keyword" box is a shared text field: right-click ▸ Paste fills it, Return gives the +/// keywords to every selected photo, and Esc gives up what was typed. +#[test] +fn the_add_keyword_box_pastes_adds_and_gives_up() { + let (mut h, ids) = two_selected(); + h.view.clipboard = "travel, sunset".into(); + ask(&mut h, "ui.clickWidget", json!({"id": "field:keyword", "button": "right"})); + ask(&mut h, "ui.clickWidget", json!({"id": "field:keyword:paste"})); + ask(&mut h, "ui.key", json!({"key": "Enter"})); + for id in &ids { + let k = keywords_of(&h, *id); + assert!(k.contains(&"travel".to_string()) && k.contains(&"sunset".to_string()), "{k:?}"); + } + ask(&mut h, "ui.clickWidget", json!({"id": "field:keyword"})); + ask(&mut h, "ui.text", json!({"text": "harbour"})); + ask(&mut h, "ui.key", json!({"key": "Escape"})); + ask(&mut h, "ui.clickWidget", json!({"id": "field:keyword"})); + ask(&mut h, "ui.key", json!({"key": "Enter"})); + assert!(ids.iter().all(|id| !keywords_of(&h, *id).contains(&"harbour".to_string())), "Esc gave it up"); +}