egui icon indicating copy to clipboard operation
egui copied to clipboard

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

Open Barafu opened this issue 1 year ago • 2 comments

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 avatar Aug 11 '24 09:08 Barafu

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.

Barafu avatar Aug 11 '24 09:08 Barafu

Do you happen to have any updates on this issue? It appears that you cannot use any methods from ctx within show_viewport_deferred. For example, if you try to ctx.send_viewport_cmd or ctx.request_repaint(), the parent viewport will no longer repaint, it becomes unresponsive, and if there is more than one viewport, they will start to render in turns. It makes show_viewport_deferred unusable.

someone2080 avatar Jun 06 '25 03:06 someone2080