From 73404a2ad2d549ca576b8e6cefd4dbd48807d58f Mon Sep 17 00:00:00 2001 From: vangel76 Date: Sun, 11 Oct 2026 18:41:17 +0200 Subject: [PATCH] #881: Tab in the Project panel's rename field commits and keeps the keyboard Problem: Tab in a Project panel rename (or comment) field committed the edit and egui moved keyboard focus to the next widget, the row's label swatch. While it held focus, Enter no longer renamed (it "clicked" the swatch), the arrow keys moved between widgets and app shortcuts paused. Fix: the field keeps Tab through its event filter (arrows as before); Tab surrenders focus and commits like Enter, so nothing else takes the keyboard and the next Enter renames again. Co-Authored-By: Claude Opus 5.5 --- crates/ui-egui/src/panels/project.rs | 11 +++++++++-- crates/ui-egui/tests/ui_inline_edit.rs | 24 ++++++++++++++++++++++++ 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/crates/ui-egui/src/panels/project.rs b/crates/ui-egui/src/panels/project.rs index 21f84297..0b158122 100644 --- a/crates/ui-egui/src/panels/project.rs +++ b/crates/ui-egui/src/panels/project.rs @@ -447,9 +447,16 @@ pub fn show(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) { let cell_edit = |app: &mut EffectcraftApp, ui: &mut egui::Ui, actions: &mut Vec<(String, serde_json::Value)>, field: &str, cell: Rect| -> bool { let Some((eid, ef, mut buf)) = editing.clone().filter(|(e, f, _)| *e == id.0 && f == field) else { return false }; let mut child = ui.new_child(egui::UiBuilder::new().max_rect(cell)); - let resp = child.add(egui::TextEdit::singleline(&mut buf).desired_width(cell.width()).font(Tokens::ui(12.0))); + // The field keeps Tab (egui would move the keyboard to the next widget, where Enter no + // longer renames, #881): Tab commits like Enter and gives the keyboard back to the panel. + let filter = egui::EventFilter { tab: true, horizontal_arrows: true, vertical_arrows: true, ..Default::default() }; + let resp = child.add(egui::TextEdit::singleline(&mut buf).desired_width(cell.width()).font(Tokens::ui(12.0)).event_filter(filter)); app.auto.add(&format!("project.item.{}.{field}Edit", id.0), cell, field); - if resp.lost_focus() { + let tab = resp.has_focus() && ctx.input(|i| i.key_pressed(egui::Key::Tab)); + if tab { + resp.surrender_focus(); + } + if resp.lost_focus() || tab { ctx.data_mut(|d| d.remove::(edit_id())); if !ctx.input(|i| i.key_pressed(egui::Key::Escape)) { let (cmd, params) = if ef == "name" { diff --git a/crates/ui-egui/tests/ui_inline_edit.rs b/crates/ui-egui/tests/ui_inline_edit.rs index 4119d79b..dd20e55b 100644 --- a/crates/ui-egui/tests/ui_inline_edit.rs +++ b/crates/ui-egui/tests/ui_inline_edit.rs @@ -72,6 +72,30 @@ fn project_rename_replaces_the_name() { assert!(!h.ctx.egui_wants_keyboard_input(), "Enter releases the keyboard"); } +/// #881: Tab in the Project panel's rename field commits the name like Enter and gives the keyboard +/// back to the panel; it doesn't leave keyboard focus on another widget (the row's label swatch), +/// where Enter would no longer rename and the arrow keys would move between widgets. +#[test] +fn tab_in_the_project_rename_field_commits_and_keeps_enter_renaming() { + let (mut h, comp) = harness(); + let name = |h: &Harness<'_, EffectcraftApp>| h.state().session.project.items.values().find(|i| i.id.0 == comp).unwrap().name.clone(); + let edit = format!("project.item.{comp}.nameEdit"); + let editing = |h: &Harness<'_, EffectcraftApp>| h.state().auto.elements.iter().any(|e| e.id == edit); + let p = center(&h, &format!("project.item.{comp}.name")); + click(&mut h, p, 1); + key(&mut h, Key::Enter); + type_text(&mut h, "Hero"); + key(&mut h, Key::Tab); + assert_eq!(name(&h), "Hero", "Tab commits the name"); + assert!(!editing(&h), "Tab closes the rename field"); + assert_eq!(h.ctx.memory(|m| m.focused()), None, "no other widget takes the keyboard"); + key(&mut h, Key::Enter); + assert!(editing(&h), "Enter renames again"); + type_text(&mut h, "Main"); + key(&mut h, Key::Enter); + assert_eq!(name(&h), "Main"); +} + #[test] fn timecode_field_replaces_commits_and_cancels() { let (mut h, _) = harness();