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
21.72k stars 1.57k forks source link

Checkboxes require a mutable reference to an underlying boolean, making display of read-only values awkward #4226

Open zeta0134 opened 6 months ago

zeta0134 commented 6 months ago

I'm working with an application with the UI on a separate thread, which means egui needs to draw its state based on an immutable cache of the current (validated) settings. To actually change a setting, the UI thread passes an event to the application thread, which responds by updating the cache.

For radio controls, I can achieve this by setting selected to the cached display state, like so:

if ui.radio(self.settings_cache.get_integer("radio_group_example") == 2, "Option 2").clicked() {
    self.runtime_tx.send(Event::StoreIntegerSetting("radio_group_example", 2));
    ui.close_menu();
}

For checkboxes the API needs a mutable reference to the boolean value. As a workaround, I can create a temporary boolean copy whose changed value is ignored, while sending the real interaction off to my application thread:

let mut temporary_boolean = self.settings_cache.get_boolean("checkbox_example");
if ui.checkbox(&mut temporary_boolean, "NTSC Filter").clicked() {
    self.runtime_tx.send(Event::ToggleBooleanSetting("checkbox_example"));
    ui.close_menu();
}

This workaround feels a bit awkward. Shouldn't there be a way to immutably specify the current visual state for any widget?

emilk commented 6 months ago

We could add helper constructors for this: impl Checkbox { pub bn fixed(checked: bool, text: impl Into<WidgetText>) -> Self { …, though you should still use ui.add_enabled to indicate to the user that the checkbox is disabled

YgorSouza commented 6 months ago

though you should still use ui.add_enabled to indicate to the user that the checkbox is disabled

In that case you wouldn't want to disable it, since you are still using its response. You just want to pass in an immutable bool because actually changing the bool's value requires a function call. Though if your API was "set" instead of "toggle", you would need to keep track of the modified bool anyway.

Is it really that awkward, though? You just have to prepend & mut and then you can put whatever expression you want.

ui.checkbox(&mut true, "Always true");
ui.checkbox(&mut cfg!(target_os = "windows"), "Built for Windows");
let immutable_bool = true;
ui.checkbox(&mut { immutable_bool }, "Immutable variable");
ui.checkbox(
    &mut (SystemTime::now()
        .duration_since(SystemTime::UNIX_EPOCH)
        .unwrap_or_default()
        .as_secs()
        % 2
        == 1),
    "Timestamp is odd",
);

Though it is true that if the bool is false, the Checkbox is going to flicker on when you click on it, even if it is still false in the next frame. Is this something we want to be able to avoid? Would only be a matter of using the previous state to paint rather than the modified state in the current frame. But there's also the accessibility information that would have to be dealt with somehow.