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.97k stars 1.59k forks source link

Custom light & dark themes on system theme change #4490

Closed kernelkind closed 3 weeks ago

kernelkind commented 4 months ago

Is your feature request related to a problem? Please describe. Currently, when the system theme changes, the Visuals theme is switched to the hard-coded egui::Visuals::dark() or egui::Visuals::light(). I would like for there to be an option to use custom Visuals for dark and light themes.

This seems to be where the system theme change is detected and the egui visuals are set: https://github.com/emilk/egui/blob/c3f386aa301f26106397c4e14434bd5a734ba6b6/crates/eframe/src/native/epi_integration.rs#L271

And this seems to be where the hard-coded Visuals are set: https://github.com/emilk/egui/blob/c3f386aa301f26106397c4e14434bd5a734ba6b6/crates/eframe/src/epi.rs#L501-L506

Describe the solution you'd like There should be some way to pass a Visuals that corresponds to the dark theme, and another Visuals which corresponds to the light theme. Then, when a theme change is needed, those custom themes would be used instead of the hard-coded egui::Visuals::dark() or egui::Visuals::light().

Describe alternatives you've considered None

Additional context I am working on https://github.com/damus-io/notedeck and we would like to support custom theming at some point. It's not a high priority at the moment, and I would be open to submitting a PR for this feature in the future.

emilk commented 4 months ago

100% agree with this.

The global style is stored in Options::style, and I suggest we split that into three fields:

struct Options {
    pub dark_mode_style: Arc<Style>,
    pub light_mode_style: Arc<Style>,
    pub theme: Theme, // Dark or Light
    pub follow_system_theme: bool, // Change [`Self::theme`] based on `RawInput::system_theme`?
    …
}

with enum Theme { Dark, Light } (move the one in eframe into egui).

Then move IntegrationInfo::system_theme into RawInput.

PRs welcome!

rustbasic commented 2 months ago

Please also refer to issue #4312 while working.

bash commented 1 month ago

@rustbasic Do you have an minimal example that I can run to verify that visuals_mut works as expected for you?

rustbasic commented 1 month ago

@rustbasic Do you have an minimal example that I can run to verify that visuals_mut works as expected for you?

If you call dark() or light() to change the theme, both will be initialized. I think you need to save the settings for both of them separately so that they don't get initialized.

If you do it like the example, once the settings are set, it will be initialized when you change the theme.

Since the recent update, even the settings are initialized once.

Since text_cursor is easy to check with your eyes, I used text_cursor in the example.

use eframe::egui::*;

fn main() -> eframe::Result {
    let options = eframe::NativeOptions {
        viewport: egui::ViewportBuilder::default().with_inner_size([800.0, 600.0]),
        ..Default::default()
    };
    eframe::run_native(
        "My egui App",
        options,
        Box::new(|_cc| Ok(Box::<MyApp>::default())),
    )
}

#[derive(Default)]
struct MyApp {
    text: String,
    startup_complete: bool,
}

impl eframe::App for MyApp {
    fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
        egui::CentralPanel::default().show(ctx, |ui| {

            self.startup(ui);

            ui.horizontal(|ui| {
                ui.text_edit_multiline(&mut self.text);
            });

            ui.separator();

            ctx.settings_ui(ui);
        });
    }
}

impl MyApp {
    fn startup(&mut self, ui: &mut Ui) {
        if self.startup_complete {
            return;
        }

        self.startup_complete = true;
        ui.visuals_mut().text_cursor.on_duration = 2.0;
        ui.visuals_mut().text_cursor.off_duration = 0.1;
    }
}
bash commented 1 month ago

If I'm not mistaken, ui.visuals_mut() changes the visuals for the current UI and its children only. Since UIs are created per-frame you immediately lose any changes on the next frame.

If you want a change to the visuals to be permanent you have two options:

Currently, if you have eframe configured to follow the system theme (on by default) it will override the visuals when the system theme changes and you lose your changes to the visuals. This will be fixed as soon as #4744 lands.

rustbasic commented 1 month ago

Yes, I think there are some ambiguous parts because I made the example in a hurry. If the following is added at the example, it seems to be the example I was trying to make.

let mut style = ui.style_mut().clone();
ui.ctx().set_style(style);

What I want is that I want the Dark and Light modes to not be reset to the default value when I change them. I think #4744 will solve it because it changes the settings for both. I haven't tested #4744 yet.

Thank you.

bash commented 1 month ago

@rustbasic Yes, this should indeed be fixed by #4744. With this PR you can either make changes to the dark or light style using set_dark_style / set_light_style. If you want to make changes to both, then there's a handy style_mut() function that lets you update both in one go.