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.67k stars 1.56k forks source link

Animation with request_repaint() does not work when deferred viewport is open #4945

Open Barafu opened 1 month ago

Barafu commented 1 month ago

Describe the bug When multiple viewports are open, animations using request_repaint() work only on the active viewport, and requests to update other viewports are ignored.

To Reproduce I have created a minimal demonstration, which is included below. Compile and run. See how animation works only on window that is clicked. Replace show_viewport_deferred() on line 41 to show_viewport_immediate() to see expected behaviour.

Expected behavior Animation to work identically in both windows.

Demonstration

use egui::{Color32, Painter, Shape, Ui, ViewportId};

fn main() -> eframe::Result {
    let native_options = eframe::NativeOptions {
        viewport: egui::ViewportBuilder::default()
            .with_position([0.0, 0.0])
            .with_inner_size([500.0, 500.0]),
        ..Default::default()
    };
    return eframe::run_native(
        "My egui App",
        native_options,
        Box::new(|_cc| Ok(Box::new(MyApp::default()))),
    );
}

#[derive(Default)]
pub struct MyApp {
}

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

pub fn second_viewport(ui: &mut egui::Ui) -> ViewportId {
    let title = "second viewport";
    let id: ViewportId = ViewportId::from_hash_of(title);

    ui.ctx().show_viewport_deferred(
        id.clone(),
        egui::ViewportBuilder::default()
            .with_title(title)
            .with_position([510.0, 0.0])
            .with_inner_size([500.0, 500.0]),
        move |ctx, _class| {
            egui::CentralPanel::default().show(ctx, |ui| {
                paint_animation(ui);
                request_updates(ui);
            });
        },
    );

    id
}

pub fn paint_animation(ui: &mut Ui) {
    let painter = Painter::new(
        ui.ctx().clone(),
        ui.layer_id(),
        ui.available_rect_before_wrap(),
    );

    let step = std::time::SystemTime::now()
        .duration_since(std::time::UNIX_EPOCH)
        .unwrap()
        .subsec_millis()
        / 5;

    painter.add(Shape::circle_filled(
        [100.0 + step as f32, 100.0 + step as f32].into(),
        100.0,
        Color32::RED,
    ));
    // Make sure we allocate what we used (everything)
    ui.expand_to_include_rect(painter.clip_rect());
}

fn request_updates(ui: &mut Ui) {
    let mut ids: Vec<ViewportId> = Vec::new();
    ui.ctx().input(|i| {
        ids = i.raw.viewports.keys().cloned().collect();
    });
    for id in ids {
        ui.ctx().request_repaint_of(id);
    };
}
Barafu commented 1 month ago

P.S. I'd be grateful if someone could suggest a workaround to have animations smooth, yet painting code in multiple threads, so I could continue my development for now.