egui icon indicating copy to clipboard operation
egui copied to clipboard

Transparency not working with Egui/Eframe

Open GeneralBombe opened this issue 9 months ago • 10 comments

Discussed in https://github.com/emilk/egui/discussions/4446

Originally posted by GeneralBombe May 3, 2024 Does anyone know how i can make a window transparent and use as a overlay? I am using egui, eframe and egui_glow. if i switch everytrhing to transparent, i only have a black window, where it should be transparent. I think the Window Building is not the problem:

fn main() -> eframe::Result<()> {
    env_logger::init(); // Log to stderr (if you run with `RUST_LOG=debug`).

    let native_options = eframe::NativeOptions {
        viewport: egui::ViewportBuilder::default()
            .with_fullscreen(true)
            .with_transparent(true) // Make the window transparent
            .with_decorations(false),
        ..Default::default()
    };

    eframe::run_native(
        "esp",
        native_options,
        Box::new(|cc| Box::new(RustEsp::TemplateApp::new(cc).unwrap())),
    )
}

This is the app.rs i am using:

use std::sync::Arc;

use eframe::{egui_glow, glow};
use egui::{mutex::Mutex, text_selection::visuals, Color32, Vec2};

/// We derive Deserialize/Serialize so we can persist app state on shutdown.
//if we add new fields, give them default values when deserializing old state
pub struct TemplateApp {
    // Example stuff:
    rotating_triangle: Arc<Mutex<RotatingTriangle>>,
    angle: f32,
}



impl TemplateApp {
    /// Called once before the first frame.
    pub fn new<'a>(cc: &'a eframe::CreationContext<'a>) -> Option<Self> {
        let gl = cc.gl.as_ref()?;
        Some(Self {
            rotating_triangle: Arc::new(Mutex::new(RotatingTriangle::new(gl)?)),
            angle: 0.0,
        })
    }


    fn custom_painting(&mut self, ui: &mut egui::Ui) {
        let (rect, response) =
            ui.allocate_exact_size(egui::Vec2::new(ui.available_width()/2.0, ui.available_height()/2.0), egui::Sense::drag());

        self.angle += response.drag_motion().x * 0.01;
        // Clone locals so we can move them into the paint callback:
        let angle = self.angle;
        let rotating_triangle = self.rotating_triangle.clone();

        let cb = egui_glow::CallbackFn::new(move |_info, painter| {
            rotating_triangle.lock().paint(painter.gl(), angle);
        });

        let callback = egui::PaintCallback {
            rect,
            callback: Arc::new(cb),
        };
        ui.painter().add(callback);
    }
}

impl eframe::App for TemplateApp {
    /// Called by the frame work to save state before shutdown.
    fn clear_color(&self, visuals: &egui::Visuals) -> [f32; 4] {
        let u8_array = visuals.panel_fill.to_array();
    // Convert each u8 value to f32
    let f32_array: [f32; 4] = [
        u8_array[0] as f32,
        u8_array[1] as f32,
        u8_array[2] as f32,
        u8_array[3] as f32,
    ];
    f32_array
    }
    /// Called each time the UI needs repainting, which may be many times per s
    /// econd.
    fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
        
        let mut style = (*ctx.style()).clone();
        ctx.set_visuals(egui::Visuals {
            //window_fill: egui::Color32::TRANSPARENT,
            panel_fill: egui::Color32::TRANSPARENT,
            ..Default::default()
        });
        
        
        let my_frame = egui::containers::Frame {
           
            fill: egui::Color32::TRANSPARENT,
            ..Default::default()
        };

        
        egui::CentralPanel::default().show(ctx, |ui| {
            egui::ScrollArea::both()
                .auto_shrink(false)
                .show(ui, |ui| {
                    

                    egui::Frame::canvas(ui.style()).show(ui, |ui| {
                        self.custom_painting(ui);
                    });
                    ui.label("Drag to rotate!");
                    
                });
        });
    }

    fn on_exit(&mut self, gl: Option<&glow::Context>) {
        if let Some(gl) = gl {
            self.rotating_triangle.lock().destroy(gl);
        }
    }
}

struct RotatingTriangle {
    program: glow::Program,
    vertex_array: glow::VertexArray,
}

#[allow(unsafe_code)] // we need unsafe code to use glow
impl RotatingTriangle {
    fn new(gl: &glow::Context) -> Option<Self> {
        use glow::HasContext as _;

        let shader_version = egui_glow::ShaderVersion::get(gl);

        unsafe {
            let program = gl.create_program().expect("Cannot create program");

            if !shader_version.is_new_shader_interface() {
                log::warn!(
                    "Custom 3D painting hasn't been ported to {:?}",
                    shader_version
                );
                return None;
            }

            let (vertex_shader_source, fragment_shader_source) = (
                r#"
                    const vec2 verts[3] = vec2[3](
                        vec2(0.0, 1.0),
                        vec2(-1.0, -1.0),
                        vec2(1.0, -1.0)
                    );
                    const vec4 colors[3] = vec4[3](
                        vec4(1.0, 0.0, 0.0, 1.0),
                        vec4(0.0, 1.0, 0.0, 1.0),
                        vec4(0.0, 0.0, 1.0, 1.0)
                    );
                    out vec4 v_color;
                    uniform float u_angle;
                    void main() {
                        v_color = colors[gl_VertexID];
                        gl_Position = vec4(verts[gl_VertexID], 0.0, 1.0);
                        gl_Position.x *= cos(u_angle);
                    }
                "#,
                r#"
                precision mediump float;

                uniform float backgroundAlpha; // Alpha value for background transparency
                
                in vec4 v_color;
                out vec4 out_color;
                
                void main() {
                    // Check if the color is black
                    if (all(equal(v_color.rgb, vec3(0.0)))) {
                        // Set alpha to backgroundAlpha for black pixels
                        out_color = v_color;

                    } else {
                        // Keep original color for non-black pixels#
                        out_color = vec4(0.0, 200.0, 0.0, backgroundAlpha);

                    }
                }
                
                "#,
            );

            let shader_sources = [
                (glow::VERTEX_SHADER, vertex_shader_source),
                (glow::FRAGMENT_SHADER, fragment_shader_source),
            ];

            let shaders: Vec<_> = shader_sources
                .iter()
                .map(|(shader_type, shader_source)| {
                    let shader = gl
                        .create_shader(*shader_type)
                        .expect("Cannot create shader");
                    gl.shader_source(
                        shader,
                        &format!(
                            "{}\n{}",
                            shader_version.version_declaration(),
                            shader_source
                        ),
                    );
                    gl.compile_shader(shader);
                    assert!(
                        gl.get_shader_compile_status(shader),
                        "Failed to compile custom_3d_glow {shader_type}: {}",
                        gl.get_shader_info_log(shader)
                    );

                    gl.attach_shader(program, shader);
                    shader
                })
                .collect();

            gl.link_program(program);
            assert!(
                gl.get_program_link_status(program),
                "{}",
                gl.get_program_info_log(program)
            );

            for shader in shaders {
                gl.detach_shader(program, shader);
                gl.delete_shader(shader);
            }

            let vertex_array = gl
                .create_vertex_array()
                .expect("Cannot create vertex array");

            unsafe {
                gl.enable(glow::BLEND);
                gl.blend_func(glow::SRC_ALPHA, glow::ONE_MINUS_SRC_ALPHA);
            }

            Some(Self {
                program,
                vertex_array,
            })
        }
    }

    fn destroy(&self, gl: &glow::Context) {
        use glow::HasContext as _;
        unsafe {
            gl.delete_program(self.program);
            gl.delete_vertex_array(self.vertex_array);
        }
    }

    fn paint(&self, gl: &glow::Context, angle: f32) {
        use glow::HasContext as _;
        
        unsafe {
            //gl.clear_color(0.0, 0.0, 200.0, 0.0);
            //gl.clear(glow::COLOR_BUFFER_BIT);
            gl.use_program(Some(self.program));
            gl.uniform_1_f32(
                gl.get_uniform_location(self.program, "u_angle").as_ref(),
                angle,
            );
            
            
            gl.bind_vertex_array(Some(self.vertex_array));
            gl.draw_arrays(glow::TRIANGLES, 0, 3);
            
            
        }
    }
}

All colors work except transparent, thats why im thinking its eframe, but i really do not understand what the issue is. Also in alt tab, it shows as transparent(i think). The eframe window is fullscreen:

4feda3e3-d014-4e11-93c7-6e4e77fa6bfd

I am not sure if the issue is ony my side or not, but i'd appreciate any help!

GeneralBombe avatar May 03 '24 13:05 GeneralBombe

Try fill like this:

        let panel_frame = egui::Frame {
            fill: egui::Color32::TRANSPARENT,
            ..Default::default()
        };

OR

        let panel_frame = egui::Frame {
            fill: egui::Color32::from_rgba_premultiplied(0, 0, 0, 50),
            ..Default::default()
        };

rustbasic avatar May 03 '24 17:05 rustbasic

Try fill like this:

        let panel_frame = egui::Frame {
            fill: egui::Color32::TRANSPARENT,
            ..Default::default()
        };

OR

        let panel_frame = egui::Frame {
            fill: egui::Color32::from_rgba_premultiplied(0, 0, 0, 50),
            ..Default::default()
        };

Hmmm, still does not work. Also i somehow don't get a windowed window, only fullscreen. Do you have a idea what the issue is? I tried it with this:

fn main() -> eframe::Result<()> {
    env_logger::init(); // Log to stderr (if you run with `RUST_LOG=debug`).

    let native_options = eframe::NativeOptions {
        viewport: egui::ViewportBuilder::default()
            .with_transparent(true) // Make the window transparent
            .with_decorations(false)
            .with_max_inner_size([200.0, 200.0])
            .with_min_inner_size([100.0,100.0]),
        ..Default::default()
    };

    eframe::run_native(
        "esp",
        native_options,
        Box::new(|cc| Box::new(RustEsp::TemplateApp::new(cc).unwrap())),
    )
}

I am really confused right now.

GeneralBombe avatar May 04 '24 00:05 GeneralBombe

You ask a question, but I don't even know what an your OS is. may be Linux. There seems to be no problem with Windows 10. Please find a solution in the form below.

            if ui.button("transparent").clicked() {                                   
                ui.ctx()                                                              
                    .send_viewport_cmd(egui::ViewportCommand::Transparent(false));    
            }                                                                         
                                                                                      
            if ui.button("innersize").clicked() {                                     
                ui.ctx()                                                              
                    .send_viewport_cmd(egui::ViewportCommand::InnerSize(egui::Vec2::new(500.0, 500.0)));
            }                                                                         

rustbasic avatar May 04 '24 01:05 rustbasic

@GeneralBombe

I tested your source code. Temporarily use below. For the remaining functions, try using ViewportCommand.

    let native_options = eframe::NativeOptions {
        viewport: egui::ViewportBuilder::default()
            // .with_fullscreen(true)
            .with_inner_size(egui::vec2(800.0, 600.0))
            // .with_decorations(false)
            .with_transparent(true), // Make the window transparent
        ..Default::default()
    };

rustbasic avatar May 05 '24 17:05 rustbasic

@rustbasic thank you for now, ill try it later :)

GeneralBombe avatar May 05 '24 17:05 GeneralBombe

@GeneralBombe

I tested your source code. Temporarily use below. For the remaining functions, try using ViewportCommand.

    let native_options = eframe::NativeOptions {
        viewport: egui::ViewportBuilder::default()
            // .with_fullscreen(true)
            .with_inner_size(egui::vec2(800.0, 600.0))
            // .with_decorations(false)
            .with_transparent(true), // Make the window transparent
        ..Default::default()
    };

Hello, yes this works :). But i am still really really really confused. The window is only Transparent if it opens my 2nd monitor (it does not matter if it is set as the main or secondary monitor). Same thing if i try to open the window with glfw ... Wtf?

GeneralBombe avatar May 05 '24 18:05 GeneralBombe

So i think this issue is not about egui/eframe/glfw but about windows? Really annoying ... I can drag the window to the other monitor and the transparency works ...

GeneralBombe avatar May 05 '24 18:05 GeneralBombe

@rustbasic i got a github email with your quote "Could you please apply the Pull Request below and see if it works as desired, and let me know the results?". I tried to add the branch as dependencie, got this error when i tried to cargo run: error: failed to select a version for objc-sys. ... required by package objc2 v0.4.1... which satisfies dependencyobjc2 = "^0.4.1"(locked to 0.4.1) of packageglutin v0.31.2... which satisfies dependencyglutin = "^0.31"(locked to 0.31.2) of packageeframe v0.27.2 (https://github.com/rustbasic/egui.git?branch=patch52#267fb5ac)... which satisfies git dependencyeframeof packageeframe_template v0.1.0 (F:\Programming\egui-eframe-playground)versions that meet the requirements^0.3.1` (locked to 0.3.2) are: 0.3.2

the package objc-sys links to the native library objc_0_3, but it conflicts with a previous package which links to objc_0_3 as well: package objc-sys v0.3.3 ... which satisfies dependency objc-sys = "^0.3.3" of package objc2 v0.5.1 ... which satisfies dependency objc2 = "^0.5.1" of package block2 v0.5.0 ... which satisfies dependency block2 = "^0.5.0" of package objc2-app-kit v0.2.0 ... which satisfies dependency objc2-app-kit = "^0.2.0" of package eframe v0.27.2 (https://github.com/rustbasic/egui.git?branch=patch52#267fb5ac) ... which satisfies git dependency eframe of package eframe_template v0.1.0 (F:\Programming\egui-eframe-playground) Only one package in the dependency graph may specify the same links value. This helps ensure that only one copy of a native library is linked in the final binary. Try to adjust your dependencies so that only one package uses the links ='objc-sys' value. For more information, see https://doc.rust-lang.org/cargo/reference/resolver.html#links. Did i add it correctly as dependencie? eframe = { version = "0.27.0", default-features = false, features = [ "accesskit", # Make egui comptaible with screen readers. NOTE: adds a lot of dependencies. "default_fonts", # Embed the default egui fonts. "glow", # Use the glow rendering backend. Alternative: "wgpu". "persistence", # Enable restoring app state when restarting the app. ], git = "https://github.com/rustbasic/egui.git", branch = "patch52"}`

GeneralBombe avatar May 06 '24 20:05 GeneralBombe

I closed it because I didn't think it could be resolved this way. Use ViewportCommand to implement the functionality you want as much as possible.

rustbasic avatar May 07 '24 06:05 rustbasic