Using egui, how to control the size of a window on a pixel basis

Hello,

I'm writing a small graph application using egui/eframe/egui_plot and I have window size problems on my linux setup.
I think it comes from the fact that egui (cleverly) does not think in terms of pixels, but in terms of physical size on the screen, so takes the screen dpi into account.
My workstation is composed of a laptop, a 24'' monitor and a 32'' monitor. The 24'' monitor is full-HD, the 32'' is configured in QHD (2560x1440), so their dpi is the same. The fullHD monitor is mirroring the 15'' laptop screen. Everything is configured with xwindows (no wayland) and Xrandr. I'm using openbox as window manager dpi is configured in my xfce setup at 93dpi.
Whatever window straddles both monitors, the physical size is exactly the same on both monitors.
However with my little egui application, the window size changes when I move the window from the 24'' monitor to the 32'' monitor (and vice-versa). It seems like egui/eframe is clever enough to take the dpi into account and changes the rendering size. But instead of taking my FHD monitor dpi, it is taking my laptop dpi (as they are in mirror mode, it can't decide). Which leads to this resize problem.
I managed to force the eframe application to keep its size, but anyway text inside resizes ...
How can I:

  • Be sure that my analysis is the good one
  • Trick the application so that it does not resize on my setup when changing screen.

I can reproduce the problem compiling one of the egui demo applications.

Here's the a trimmed down version of my code, which manages to control the size, but not the text.
Screenshots attached. First on in on the 32'', second one on the 24''.

Thanks.

use eframe::egui;
use egui_plot::{Line, Plot};
use std::time::Duration;

fn main() -> Result<(), eframe::Error> {
    let options = eframe::NativeOptions {
        viewport: egui::ViewportBuilder::default()
            .with_resizable(false),
        ..Default::default()
    };

    eframe::run_native(
        "Fixed-Size Plot",
        options,
        Box::new(|cc| {
            cc.egui_ctx.set_visuals(egui::Visuals::light());
            Ok(Box::new(FixedPlotApp::new()))
        }),

    )
}

struct FixedPlotApp {
    data: Vec<[f64; 2]>,
}

impl FixedPlotApp {
    fn new() -> Self {
        Self {
            data: (0..100).map(|i| {
                let x = i as f64 * 0.1;
                [x, x.sin()]
            }).collect(),
        }
    }
}

impl eframe::App for FixedPlotApp {
    fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
        egui::CentralPanel::default().show(ctx, |ui| {
            egui::Rect::from_min_size(
                ui.min_rect().min,
                egui::Vec2::new(600.0, 400.0),
            );

            let plot = Plot::new("fixed_plot");
            plot.show(ui, |plot_ui| {
                plot_ui.line(
                    Line::new("Sine Wave", self.data.clone())
                        .color(egui::Color32::BLUE)
                        .width(2.0)
                );
            });
            ctx.request_repaint_after(Duration::from_millis(1000));
        });
    }
}


I've found a workaround using ctx.set_pixels_per_point(); in the update() method. Not perfect, as I still have one frame during the update of the window which is not in the correct scale when I move the windows from one monitor to the other. But this is only for a couple of milliseconds and I barely see it. Big improvement for my setup.
If someone has a better idea, don't hesitate.
Thanks.

Something like that. egui is aware of the "scale factor" reported by the windowing environment (typically winit: See Window::scale_factor() - the section about X11 is probably relevant - and the ScaleFactorChanged event).

I honestly don't know how mirroring works in X11. It seems like the winit docs encourage configuring XRandR "correctly" for your hardware, whatever that entails.

Changing the egui pixels-per-point is likely incorrect. The idea is to match it to the physical display's scale factor. (egui-winit already does this correctly: egui/crates/egui-winit/src/lib.rs at 0.31.1 · emilk/egui) Hopefully your desktop environment is not lying about the scale factor.

Hello,

Thank you for your pointers. Especially Window in winit::window - Rust which explains how dpi is obtained. Seeing my setup (just below), I can't see how the software can decide if screen0 has 93dpi or 144dpi. Mirroring both screens is very misleading for winit.
However I have the perfect solution for me, do not touch the code, just use the WINIT_X11_SCALE_FACTOR environment variable when I am in this configuration, this is cleaner and works perfectly.

Thanks for your help.