emilk / egui

egui: an easy-to-use immediate mode GUI in Rust that runs on both web and native
https://www.egui.rs/
Apache License 2.0
22.51k stars 1.61k forks source link

consume_key and consume_shortcut with input field #5338

Open nacl42 opened 3 weeks ago

nacl42 commented 3 weeks ago

Describe the bug When a keyboard shortcut is consumed, the key will be inserted into an active edit field anyway.

In the below example, there is an edit field to type and independent of this, the keyboard shortcut ALT-B will be intercepted. In the example, the keyboard shortcut is processed before the edit field is displayed. Pressing ALT-B will trigger the action but will also insert the letter 'b' into the edit field.

To Reproduce Steps to reproduce the behavior:

use eframe::egui;

fn main() -> eframe::Result {
    eframe::run_native(
        "xyz",
        eframe::NativeOptions::default(),
        Box::new(|_cc| Ok(Box::<MyApp>::default())),
    )
}

#[derive(Default)]
pub struct MyApp {
    content: String,
    show_label: bool,
}

impl eframe::App for MyApp {
    fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
        egui::CentralPanel::default().show(ctx, |ui| {
            if ctx.input_mut(|i| i.consume_key(egui::Modifiers::ALT, egui::Key::B)) {
                self.show_label = true;
            };
            ui.label("Type a few chars and then press ALT-B:");
            ui.text_edit_singleline(&mut self.content);
            if self.show_label {
                ui.label("You pressed ALT-B. Note that the char 'b' is inserted into the edit field, even though consume_key has been called.");
            }
        });
    }
}

Expected behavior

I would expect that if the key is consumed it will not be interpreted by the text edit. This is also what the function description indicates: "Check for a key press. If found, true is returned and the key pressed is consumed, so that this will only return true once.".

Screenshots

Bildschirmfoto_2024-11-01_16-57-03

Bildschirmfoto_2024-11-01_16-57-32

Desktop (please complete the following information):

nacl42 commented 3 weeks ago

The issue might be in the following function in input_state/mod.rs:

pub fn count_and_consume_key(&mut self, modifiers: Modifiers, logical_key: Key) -> usize {
        let mut count = 0usize;

        self.events.retain(|event| {
            let is_match = matches!(
                event,
                Event::Key {
                    key: ev_key,
                    modifiers: ev_mods,
                    pressed: true,
                    ..
                } if *ev_key == logical_key && ev_mods.matches_logically(modifiers)
            );

            count += is_match as usize;

            !is_match
        });

        count
    }

It removes the actual event for the modifier (in this case ALT-B), but there seems to be a second event (B without any modifier). This event is then triggered for the input field.

The question is: What is the expected behavior?

nacl42 commented 2 weeks ago

I think I found the cause of the problem:

See crate/egui-winit/src/lib.rs:

 786         if let Some(text) = &text {
 787             // Make sure there is text, and that it is not control characters
 788             // (e.g. delete is sent as "\u{f728}" on macOS).
 789             if !text.is_empty() && text.chars().all(is_printable_char) {
 790                 // On some platforms we get here when the user presses Cmd-C (copy), ctrl-W, etc.
 791                 // We need to ignore these characters that are side-effects of commands.
 792                 // Also make sure the key is pressed (not released). On Linux, text might
 793                 // contain some data even when the key is released.
 794                 let is_cmd = self.egui_input.modifiers.ctrl
 795                     || self.egui_input.modifiers.command
 796                     || self.egui_input.modifiers.mac_cmd;
 797                 if pressed && !is_cmd {
 798                     self.egui_input
 799                         .events
 800                         .push(egui::Event::Text(text.to_string()));
 801                 }
 802             }
 803         }
 804     }

IMHO the alt modifier should be handled similar to ctrl, command and mac_cmd.