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
22.28k stars 1.6k forks source link

Wrong window place and size on Win 11 with multiple displays with different DPI. #4918

Open Barafu opened 3 months ago

Barafu commented 3 months ago

Describe the bug I have Win11 and two monitors with different DPI and I wanted to make an app that displays something in fullscreen on the secondary monitor. I detect the position and size of the second monitor using screen-info crate, but when I create the window it is not positioned properly. See the screenshot. Because of the workaround below I don't think the problem can be in screen-info.

To Reproduce Here is the relevant part of the code that creates a window.

        // Get information on all displays
        let mut displays = DisplayInfo::all().unwrap();
        if displays.len() == 0 {
            panic!("Can't find any displays");
        }

        // Find primary display
        let primary_position = displays.iter().position(|d| !d.is_primary).unwrap();
        let primary_display = displays.swap_remove(primary_position);

        let native_options = eframe::NativeOptions {
            viewport: egui::ViewportBuilder::default()
                .with_position([primary_display.x as f32, primary_display.y as f32])
                .with_fullscreen(true)
                .with_taskbar(false)
                .with_drag_and_drop(false)
                .with_icon(
                    // NOTE: Adding an icon is optional
                    eframe::icon_data::from_png_bytes(
                        &include_bytes!("../assets/icon-256.png")[..],
                    )
                    .expect("Failed to load icon"),
                ),
            ..Default::default()
        };
        return eframe::run_native(
            "DreamSpinner",
            native_options,
            Box::new(|cc| Ok(Box::new(dream_spinner::TemplateApp::new(cc)))),
        );

(the primary display being set to non-primary is intentional for example)

Workarounds Any one of these measures solves the problem.

Screenshots Screenshot 2024-08-05 111444 Note how on the right display you can see parts of "Chatbox" app beside the supposedly fullscreen demo app.

Desktop (please complete the following information):

rustbasic commented 3 months ago

It seems like this would require using a method that skips the first frame.

My opinion , this is not a bug, the monitor information provision and monitor selection features are not there yet.

Barafu commented 3 months ago

There are functions for moving the window and setting it fullscreen. In the described conditions, they move window to the location that is different from the specified one, and fullscreen does not occupy full screen. That is what I call a bug.

rustbasic commented 2 months ago

Yes, you try to find a temporary solution.

I did this and it worked fine on Windows 10 with two different DPI monitors.


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

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

impl eframe::App for MyApp {
    fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
        if !self.fullscreen_onetime {
            self.fullscreen_onetime = true;
            ctx.send_viewport_cmd(egui::ViewportCommand::Fullscreen(true));
        }

        egui::CentralPanel::default().show(ctx, |ui| {
            ui.add(
                egui::TextEdit::multiline(&mut self.text)
                    .desired_width(300.0)
                    .desired_rows(6)
                    .lock_focus(true)
                    .code_editor(),
            );
        });
    }
}
Barafu commented 2 months ago

I can't find a way to fix this for a secondary viewport (opened with show_viewport_deferred()). Even resizing and fullscreening as late as possible does not help in this case. Also, I doublechecked that X,Y coordinates I pass are correct. The left monitor is 2560 pixels wide, and I pass x=2561, y=-998 (since right monitor is higher than the primary monitor).

rustbasic commented 2 months ago

I tested it with the test example below and there was no problem with the fullscreen function.

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

#[derive(Default)]
pub struct MyApp {
    fullscreen_onetime: bool,
    text: String,
    visible_test_viewport: std::sync::Arc<std::sync::atomic::AtomicBool>,
}

impl eframe::App for MyApp {
    fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
        if !self.fullscreen_onetime {
            self.fullscreen_onetime = true;
            // ctx.send_viewport_cmd(egui::ViewportCommand::Fullscreen(true));
        }

        egui::CentralPanel::default().show(ctx, |ui| {
            ui.add(
                egui::TextEdit::multiline(&mut self.text)
                    .desired_width(300.0)
                    .desired_rows(6)
                    .lock_focus(true)
                    .code_editor(),
            );

            if !self.visible_test_viewport.load(std::sync::atomic::Ordering::Relaxed) {
                test_viewport(self, ui);
            }
        });
    }
}

pub fn test_viewport(app: &mut MyApp, ui: &mut egui::Ui) {
    let show_deferred_viewport = app.visible_test_viewport.clone();
    let title = "test viewport";

    ui.ctx().show_viewport_deferred(
        egui::ViewportId::from_hash_of(title.clone()),
        egui::ViewportBuilder::default()
            .with_title(title.clone())
            .with_inner_size([1280.0, 720.0]),
        move |ctx, class| {
            assert!(
                class == egui::ViewportClass::Deferred,
                "This egui backend doesn't support multiple viewports"
            );

            egui::CentralPanel::default().show(ctx, |ui| {
                ui.label("Hello from deferred viewport");
                if ui.button("Fullscreen").clicked() {
                    let is_on = ui.input(|i| i.time) % 2.0 < 1.0;
                    ui.ctx()
                        .send_viewport_cmd(egui::ViewportCommand::Fullscreen(is_on));
                }            
            });
            if ctx.input(|i| i.viewport().close_requested()) {
                // Tell parent to close us.
                show_deferred_viewport.store(false, std::sync::atomic::Ordering::Relaxed);
            }
        },
    );
}
Barafu commented 2 months ago

I have modified your example to not work: to show error exactly like on my screenshot. Please note line 60: dbg! there reports correct coordinates, but the window is not displayed on them.

use display_info::DisplayInfo;

fn main() -> eframe::Result {
    let displays = DisplayInfo::all().unwrap();
    let primary = displays.iter().find(|d| d.is_primary).unwrap();
    let native_options = eframe::NativeOptions {
        viewport: egui::ViewportBuilder::default()
            .with_position([primary.x as f32, primary.y as f32])
            .with_drag_and_drop(false),
        ..Default::default()
    };
    return eframe::run_native(
        "My egui App",
        native_options,
        Box::new(|_cc| Ok(Box::new(MyApp::default()))),
    );
}

#[derive(Default)]
pub struct MyApp {
    fullscreen_onetime: bool,
    text: String,
    visible_test_viewport: std::sync::Arc<std::sync::atomic::AtomicBool>,
}

impl eframe::App for MyApp {
    fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
        if !self.fullscreen_onetime {
            self.fullscreen_onetime = true;
            // ctx.send_viewport_cmd(egui::ViewportCommand::Fullscreen(true));
        }

        egui::CentralPanel::default().show(ctx, |ui| {
            ui.add(
                egui::TextEdit::multiline(&mut self.text)
                    .desired_width(300.0)
                    .desired_rows(6)
                    .lock_focus(true)
                    .code_editor(),
            );

            if !self.visible_test_viewport.load(std::sync::atomic::Ordering::Relaxed) {
                test_viewport(self, ui);
            }
        });
    }
}

pub fn test_viewport(app: &mut MyApp, ui: &mut egui::Ui) {
    let show_deferred_viewport = app.visible_test_viewport.clone();
    let title = "test viewport";

    let displays = DisplayInfo::all().unwrap();
    let non_primary = displays.iter().find(|d| !d.is_primary).unwrap();

    ui.ctx().show_viewport_deferred(
        egui::ViewportId::from_hash_of(title.clone()),
        egui::ViewportBuilder::default()
            .with_title(title.clone())
            .with_position(dbg!([non_primary.x as f32, non_primary.y as f32]))
            .with_inner_size([500.0, 500.0]),
        move |ctx, class| {
            assert!(
                class == egui::ViewportClass::Deferred,
                "This egui backend doesn't support multiple viewports"
            );

            egui::CentralPanel::default().show(ctx, |ui| {
                ui.label("Hello from deferred viewport");
                if ui.button("Fullscreen").clicked() {
                    let is_on = ui.input(|i| i.time) % 2.0 < 1.0;
                    ui.ctx()
                        .send_viewport_cmd(egui::ViewportCommand::Fullscreen(is_on));
                }            
            });
            if ctx.input(|i| i.viewport().close_requested()) {
                // Tell parent to close us.
                show_deferred_viewport.store(false, std::sync::atomic::Ordering::Relaxed);
            }
        },
    );
}
rustbasic commented 2 months ago

As below, we have confirmed that there are no problems with the FullScreen function or other functions in Windows 10.

use display_info::DisplayInfo;

fn main() -> eframe::Result {
    let displays = DisplayInfo::all().unwrap();
    let primary = displays.iter().find(|d| d.is_primary).unwrap();
    let native_options = eframe::NativeOptions {
        viewport: egui::ViewportBuilder::default()
            .with_position([primary.x as f32, primary.y as f32])
            .with_drag_and_drop(false),
        ..Default::default()
    };
    return eframe::run_native(
        "My egui App",
        native_options,
        Box::new(|_cc| Ok(Box::new(MyApp::default()))),
    );
}

#[derive(Default)]
pub struct MyApp {
    fullscreen_onetime: bool,
    text: String,
    visible_test_viewport: std::sync::Arc<std::sync::atomic::AtomicBool>,
}

impl eframe::App for MyApp {
    fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
        if !self.fullscreen_onetime {
            self.fullscreen_onetime = true;
            // ctx.send_viewport_cmd(egui::ViewportCommand::Fullscreen(true));
        }

        egui::CentralPanel::default().show(ctx, |ui| {
            ui.add(
                egui::TextEdit::multiline(&mut self.text)
                    .desired_width(300.0)
                    .desired_rows(6)
                    .lock_focus(true)
                    .code_editor(),
            );

            if !self
                .visible_test_viewport
                .load(std::sync::atomic::Ordering::Relaxed)
            {
                test_viewport(self, ui);
            }
        });
    }
}

pub fn test_viewport(app: &mut MyApp, ui: &mut egui::Ui) {
    let show_deferred_viewport = app.visible_test_viewport.clone();
    let title = "test viewport";

    let displays = DisplayInfo::all().unwrap();
    let _primary = displays.iter().find(|d| d.is_primary).unwrap();
    let non_primary = displays.iter().find(|d| !d.is_primary).unwrap();

    ui.ctx().show_viewport_deferred(
        egui::ViewportId::from_hash_of(title),
        egui::ViewportBuilder::default()
            .with_title(title)
            .with_position(dbg!([
                non_primary.width as f32 / non_primary.scale_factor,
                non_primary.y as f32
            ]))
            .with_inner_size([500.0, 500.0]),
        move |ctx, class| {
            assert!(
                class == egui::ViewportClass::Deferred,
                "This egui backend doesn't support multiple viewports"
            );

            egui::CentralPanel::default().show(ctx, |ui| {
                ui.label("Hello from deferred viewport");
                if ui.button("Fullscreen").clicked() {
                    let is_on = ui.input(|i| i.time) % 2.0 < 1.0;
                    ui.ctx()
                        .send_viewport_cmd(egui::ViewportCommand::Fullscreen(is_on));
                }
            });
            if ctx.input(|i| i.viewport().close_requested()) {
                // Tell parent to close us.
                show_deferred_viewport.store(false, std::sync::atomic::Ordering::Relaxed);
            }
        },
    );
}
Barafu commented 2 months ago

I also came to conclusion that coordinates need to be divided. However, I have run the example as you provided exactly, and it looks like this: (the white square seems to be a problem with screenshot tool)

Screenshot 2024-08-07 173949

However, then I modified it like this:

let non_primary = displays.iter().find(|d| !d.is_primary).unwrap();

    ui.ctx().show_viewport_deferred(
        egui::ViewportId::from_hash_of(title),
        egui::ViewportBuilder::default()
            .with_title(title)
            .with_position(dbg!([
                non_primary.x as f32 / non_primary.scale_factor,
                non_primary.y as f32 / non_primary.scale_factor,
            ]))
            .with_inner_size([500.0, 500.0]),

And now it looks perfect.

Screenshot 2024-08-07 174303

Anyway, even if not a problem with code, it needs to be addressed in the documentation.