Fix #4803: Enter or Escape in a floating panel's field stays in the field

This commit is contained in:
bflatastic
2026-10-31 19:43:17 +07:00
parent 13bac047f3
commit 437280315f
2 changed files with 45 additions and 1 deletions
+14 -1
View File
@@ -1187,6 +1187,14 @@ fn forward_keys(app: &mut VectorcraftApp, ctx: &egui::Context, events: Vec<egui:
ctx.request_repaint();
}
/// A field of a floating panel's window that ends its editing on Enter or Escape lets go of the
/// keyboard in that frame, so craft-ui would forward the key to the main window's shortcuts: Enter
/// in the Transparency panel's Opacity field also opened the Move dialog (#4803). The key was the
/// field's, so it stays in the window.
fn keep_field_ending_keys(ui: &egui::Ui) {
ui.input_mut(|i| i.events.retain(|e| !matches!(e, egui::Event::Key { key: egui::Key::Enter | egui::Key::Escape, pressed: true, .. })));
}
/// VectorCraft's look and wording of craft-ui's docking.
fn dock_style(ui: &egui::Ui) -> craft_ui::docking::DockStyle {
let t = crate::theme::Tokens::get(ui.ctx());
@@ -1244,9 +1252,11 @@ impl craft_ui::docking::DockContent<String> for PanelContent<'_> {
self.app.canvas_area(ui);
return;
}
if ui.ctx().viewport_id() != egui::ViewportId::ROOT {
let floating = ui.ctx().viewport_id() != egui::ViewportId::ROOT;
if floating {
crate::canvas::release_art_in_window(self.app, ui.ctx());
}
let field_had_keyboard = floating && ui.memory(|m| m.focused().is_some());
let t = crate::theme::Tokens::get(ui.ctx());
let back = ui.max_rect();
if back.is_positive() {
@@ -1263,6 +1273,9 @@ impl craft_ui::docking::DockContent<String> for PanelContent<'_> {
crate::dock::panel_body(self.app, ui, id, width, height)
});
}
if field_had_keyboard {
keep_field_ending_keys(ui);
}
}
fn logical_label(&self, id: &String) -> String {
@@ -240,6 +240,37 @@ fn keys_typed_in_a_native_window_run_the_main_windows_shortcuts() {
assert!(h.app.session.active().unwrap().selection.objects.is_empty());
}
/// Enter that commits a field of a floating panel's window, and Escape that cancels one, are the
/// field's: with an object selected and the Selection tool, Enter didn't also open the Move dialog
/// (#4803).
#[test]
fn enter_and_escape_in_a_native_windows_field_stay_in_the_field() {
let mut h = Harness::new();
h.app.run("shape.rectangle", json!({"x": 10, "y": 10, "width": 50, "height": 40})).unwrap();
h.app.run("select.all", json!({})).unwrap();
let viewport = h.float("transparency");
// The window's first text field (Opacity), by the state its text box keeps.
let ctx = h.ctx.clone();
let field = ctx
.viewport_for(viewport, |vp| vp.prev_pass.widgets.layers().flat_map(|(_, w)| w.iter()).map(|w| (w.id, w.rect)).collect::<Vec<_>>())
.into_iter()
.find(|(id, _)| egui::TextEdit::load_state(&ctx, *id).is_some())
.map(|(_, rect)| rect.center())
.expect("the panel has a text field");
for key in [Key::Enter, Key::Escape] {
if let Some(window) = h.host.borrow_mut().windows.get_mut(&viewport) {
window.events.extend([button(field, true), button(field, false)]);
}
h.frames(2);
h.key_in(viewport, key);
assert!(h.app.ui.dialog.is_none(), "{key:?} in the field opens no dialog");
assert_eq!(h.app.session.active().unwrap().selection.objects.len(), 1, "{key:?} in the field keeps the selection");
}
// Keys typed with no field editing still reach the main window.
h.key_in(viewport, Key::Enter);
assert!(h.app.ui.dialog.is_some(), "Enter outside a field opens the Move dialog");
}
#[test]
fn a_modal_blocks_native_windows_and_takes_the_keyboard_from_them() {
let mut h = Harness::new();