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
20.79k stars 1.5k forks source link

Allow user to impl its own type checking directly in TextEdit #1348

Open jean-airoldie opened 2 years ago

jean-airoldie commented 2 years ago

Problem

User input in a TextEdit has to be manually validated each time because it allows basically any characters. This adds a lot of additional unnecessary validation steps when you expect anything more restricting than a String, for instance a u64.

Solution

I would like something akin to imgui::InputInt which is like a more restrictive version of a TextEdit but that only allow numeric characters and prevents the user from entering an invalid integer. I think ideally however, you would want something more generic so the user can implement its own type checking etc.

SquareMan commented 1 year ago

I see that the original PR for this was scrapped. Is there still interest in working on a solution for this? On the project I'm working on text validation has been a consistent problem so I wrote this string wrapper that takes a validator function and implements TextBuffer, allowing you to use it directly in a textbox like this:

let num_less_than_ten = ValText::with_validator(|text| {
  text.parse().ok().filter(|&n| n < 10)
});

ui.text_edit_singleline(&mut num_less_than_ten);

though being able to prevent certain characters from being entered entirely seems pretty useful as well. If we can reopen discussion on what would be a good fit for egui I might be able to work on a PR myself.

jean-airoldie commented 1 year ago

I'm no longer using egui, so feel free to reopen the discussion.

bayou-brogrammer commented 1 year ago

@SquareMan

Do you have more implementation details? I am currently needed a validator

SquareMan commented 1 year ago

@lecoqjacob Are you referring to implementation details of my workaround or for an actual solution here within egui itself?

For the former I notice that my link is now broken after some project refactors, here's a new one. This isn't meant as a general solution to the problem but mostly fits my usecase. ValText is a struct that is given a validator function and enforces that the validator is run any time the underlying string is mutated. It implements TextBuffer which allows it to be passed directly to a TextEdit. Also have a custom OptionEditor widget which simplifies the process of adding the TextEdit and handling the surrounding styling for valid/invalid inputs. here is an example of how it all fits together.

For a permanent solution I'd like to see @emilk involved in the discussion if possible as I'm not super familiar with the plan from before my original reply. My ideal solution would probably be to have a widget that you can pass a mut reference of the underlying data to with an optional validator function that overrides a default for that type. I'm not sure exactly how that API might look like atm and I also don't know the actual string buffer would be managed here.

Additionally I believe that the original issue here was more focused on restricting the set of input characters all together, not even accepting invalid characters into the buffer. I think that a proper solution needs to support both. For instance consider a u8. You probably want the input field to only accept characters [0-9] but also you need to validate afterwards that the entered value is 0..=255

simonrw commented 11 months ago

I have a different workaround for this, though it's conceptually similar to the workaround posted above:

fn integer_edit_field(ui: &mut egui::Ui, value: &mut u16) -> egui::Response {
    let mut tmp_value = format!("{}", value);
    let res = ui.text_edit_singleline(&mut tmp_value);
    if let Ok(result) = tmp_value.parse() {
        *value = result;
    }
    res
}
eievui5 commented 11 months ago

That solution has issues with not accepting empty text, meaning you can't replace a field's existing contents. It also means that every character you type needs to be valid, or else the change will be rejected. I worked around the former by representing 0 as an empty string. This works, but isn't great UX.

On the topic of UX, the Slider widget would work great for this, except for some reason it doesn't allow you to press Tab to go to the next element, instead clearing the text you entered. That makes it a bad choice for filling out forms with lots of numeric fields.

soucosmo commented 2 months ago

Try this, it works for me

use regex::Regex;

pub fn only_numbers_repository(s: &mut String) {
    let re = Regex::new(r"[^0-9]+").unwrap();
    *s = re.replace_all(s, "").to_string();
}
if ui.text_edit_singleline(&mut app.actions_create.total_seconds)
      .on_hover_text_at_pointer("Informe a quantidade total de segundos que você pretende gastar")
      .changed() {
      only_numbers_repository(&mut app.actions_create.total_seconds);
  };