diff --git a/Cargo.toml b/Cargo.toml index 6f5c86b..92ba97d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,15 +13,11 @@ readme = "README.md" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html -[features] -default = ["opengl"] -opengl = ["raw-gl-context", "gl"] - [dependencies] -egui = "0.15" -raw-gl-context = { version = "0.1", optional = true } -gl = { version = "0.14", optional = true } -keyboard-types = { version = "0.5", default-features = false } -baseview = { git = "https://github.com/RustAudio/baseview.git", rev = "f6e99e9aa6f5aeb6b721cb05e4d882a51d995909" } -raw-window-handle = "0.3" -copypasta = "0.7.1" \ No newline at end of file +egui = "0.17" +gl = { version = "0.14" } +keyboard-types = { version = "0.6.1", default-features = false } +# TODO: Move this back to RustAudio/baseview once the PR gets merged +baseview = { git = "https://github.com/robbert-vdh/baseview.git", branch = "feature/mouse-event-modifiers", features = ["opengl"] } +raw-window-handle = "0.4" +copypasta = "0.7.1" diff --git a/README.md b/README.md index 1b36f4c..381612c 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ A [`baseview`] backend for [`egui`]. ```rust use baseview::{Size, WindowOpenOptions, WindowScalePolicy}; -use egui::CtxRef; +use egui::Context; use egui_baseview::{EguiWindow, Queue, RenderSettings, Settings}; fn main() { @@ -33,10 +33,10 @@ fn main() { state, // Called once before the first frame. Allows you to do setup code and to // call `ctx.set_fonts()`. Optional. - |_egui_ctx: &CtxRef, _queue: &mut Queue, _state: &mut State| {}, + |_egui_ctx: &Context, _queue: &mut Queue, _state: &mut State| {}, // Called before each frame. Here you should update the state of your // application and build the UI. - |egui_ctx: &CtxRef, _queue: &mut Queue, state: &mut State| { + |egui_ctx: &Context, _queue: &mut Queue, state: &mut State| { egui::Window::new("egui-baseview simple demo").show(&egui_ctx, |ui| { ui.heading("My Egui Application"); ui.horizontal(|ui| { diff --git a/examples/hello_world.rs b/examples/hello_world.rs index 53f0a57..be71fc1 100644 --- a/examples/hello_world.rs +++ b/examples/hello_world.rs @@ -1,15 +1,13 @@ use baseview::{Size, WindowOpenOptions, WindowScalePolicy}; -use egui::CtxRef; -use egui_baseview::{EguiWindow, Queue, RenderSettings, Settings}; +use egui::Context; +use egui_baseview::{EguiWindow, Queue}; fn main() { - let settings = Settings { - window: WindowOpenOptions { - title: String::from("egui-baseview hello world"), - size: Size::new(300.0, 110.0), - scale: WindowScalePolicy::SystemScaleFactor, - }, - render_settings: RenderSettings::default(), + let settings = WindowOpenOptions { + title: String::from("egui-baseview hello world"), + size: Size::new(300.0, 110.0), + scale: WindowScalePolicy::SystemScaleFactor, + gl_config: Some(baseview::gl::GlConfig::default()), }; let state = (); @@ -17,8 +15,8 @@ fn main() { EguiWindow::open_blocking( settings, state, - |_egui_ctx: &CtxRef, _queue: &mut Queue, _state: &mut ()| {}, - |egui_ctx: &CtxRef, _queue: &mut Queue, _state: &mut ()| { + |_egui_ctx: &Context, _queue: &mut Queue, _state: &mut ()| {}, + |egui_ctx: &Context, _queue: &mut Queue, _state: &mut ()| { egui::Window::new("egui-baseview hello world").show(&egui_ctx, |ui| { ui.label("Hello World!"); }); diff --git a/examples/simple_demo.rs b/examples/simple_demo.rs index e2505dc..a98a73d 100644 --- a/examples/simple_demo.rs +++ b/examples/simple_demo.rs @@ -1,15 +1,13 @@ use baseview::{Size, WindowOpenOptions, WindowScalePolicy}; -use egui::CtxRef; -use egui_baseview::{EguiWindow, Queue, RenderSettings, Settings}; +use egui::Context; +use egui_baseview::{EguiWindow, Queue}; fn main() { - let settings = Settings { - window: WindowOpenOptions { - title: String::from("egui-baseview simple demo"), - size: Size::new(400.0, 200.0), - scale: WindowScalePolicy::SystemScaleFactor, - }, - render_settings: RenderSettings::default(), + let settings = WindowOpenOptions { + title: String::from("egui-baseview simple demo"), + size: Size::new(400.0, 200.0), + scale: WindowScalePolicy::SystemScaleFactor, + gl_config: Some(baseview::gl::GlConfig::default()), }; let state = State::new(); @@ -19,10 +17,10 @@ fn main() { state, // Called once before the first frame. Allows you to do setup code and to // call `ctx.set_fonts()`. Optional. - |_egui_ctx: &CtxRef, _queue: &mut Queue, _state: &mut State| {}, + |_egui_ctx: &Context, _queue: &mut Queue, _state: &mut State| {}, // Called before each frame. Here you should update the state of your // application and build the UI. - |egui_ctx: &CtxRef, queue: &mut Queue, state: &mut State| { + |egui_ctx: &Context, queue: &mut Queue, state: &mut State| { egui::Window::new("egui-baseview simple demo").show(&egui_ctx, |ui| { ui.heading("My Egui Application"); ui.horizontal(|ui| { diff --git a/src/lib.rs b/src/lib.rs index 4ea5e98..41d9530 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,7 +1,5 @@ mod renderer; -mod settings; mod window; pub use renderer::RenderSettings; -pub use settings::Settings; pub use window::{EguiWindow, Queue}; diff --git a/src/renderer/mod.rs b/src/renderer/mod.rs index 2d87c9a..edf86ac 100644 --- a/src/renderer/mod.rs +++ b/src/renderer/mod.rs @@ -1,6 +1,3 @@ -#[cfg(feature = "opengl")] mod opengl_renderer; -#[cfg(feature = "opengl")] pub use opengl_renderer::RenderSettings; -#[cfg(feature = "opengl")] pub(crate) use opengl_renderer::Renderer; diff --git a/src/renderer/opengl_renderer.rs b/src/renderer/opengl_renderer.rs index 22bdd8e..cc70566 100644 --- a/src/renderer/opengl_renderer.rs +++ b/src/renderer/opengl_renderer.rs @@ -1,46 +1,48 @@ +use baseview::gl::GlContext; use baseview::Window; use egui::{Color32, Rgba}; -use raw_gl_context::GlContext; -pub use raw_gl_context::GlConfig as RenderSettings; +pub use baseview::gl::GlConfig as RenderSettings; mod painter; use painter::Painter; pub struct Renderer { - context: GlContext, painter: Painter, } +// TODO: This API is mostly unchanged from when the renderer owned the context. Now it's owned by +// the window. impl Renderer { - pub fn new(window: &Window, render_settings: RenderSettings, canvas_size: (u32, u32)) -> Self { - let context = GlContext::create(window, render_settings).unwrap(); + pub fn new(window: &Window, canvas_size: (u32, u32)) -> Option { + let context = window.gl_context()?; - context.make_current(); + unsafe { context.make_current() }; gl::load_with(|s| context.get_proc_address(s) as _); let painter = Painter::new(canvas_size.0, canvas_size.1); - context.make_not_current(); + unsafe { context.make_not_current() }; - Self { context, painter } + Some(Self { painter }) } pub fn render( &mut self, + context: &GlContext, bg_color: Rgba, clipped_meshes: Vec, - egui_texture: &egui::Texture, + texture_delta: &egui::TexturesDelta, pixels_per_point: f32, ) { - self.context.make_current(); + unsafe { context.make_current() }; self.painter - .paint_meshes(bg_color, clipped_meshes, egui_texture, pixels_per_point); + .paint_meshes(bg_color, clipped_meshes, texture_delta, pixels_per_point); - self.context.swap_buffers(); - self.context.make_not_current(); + context.swap_buffers(); + unsafe { context.make_not_current() }; } pub fn new_user_texture( diff --git a/src/renderer/opengl_renderer/painter.rs b/src/renderer/opengl_renderer/painter.rs index 9bc7d59..736238a 100644 --- a/src/renderer/opengl_renderer/painter.rs +++ b/src/renderer/opengl_renderer/painter.rs @@ -33,15 +33,16 @@ */ use gl::types::*; +use std::collections::HashMap; use std::ffi::CString; -use std::mem; +use std::mem::{self, MaybeUninit}; use std::os::raw::c_void; use std::ptr; use std::str; use egui::{ - paint::{ClippedMesh, Color32, Mesh, Texture}, - vec2, + epaint::{ClippedMesh, Color32, Mesh}, + vec2, TexturesDelta, }; #[derive(Default)] @@ -143,10 +144,12 @@ pub struct Painter { color_buffer: GLuint, canvas_width: u32, canvas_height: u32, - egui_texture: GLuint, - egui_texture_version: Option, vert_shader: GLuint, frag_shader: GLuint, + // As of egui 0.17 egui can manage multiple textures, which sort of avoids the need for having + // these user textures. We need to keep track of which OpenGL texture belongs to which + // [egui::TextureId::Managed] ID. + egui_textures: HashMap, user_textures: Vec, } @@ -167,7 +170,7 @@ fn compile_shader(src: &str, ty: GLenum) -> GLuint { if status != (gl::TRUE as GLint) { let mut len = 0; gl::GetShaderiv(shader, gl::INFO_LOG_LENGTH, &mut len); - let mut buf = Vec::with_capacity(len as usize); + let mut buf: Vec> = Vec::with_capacity(len as usize); buf.set_len((len as usize) - 1); // subtract 1 to skip the trailing null character gl::GetShaderInfoLog( shader, @@ -177,7 +180,8 @@ fn compile_shader(src: &str, ty: GLenum) -> GLuint { ); panic!( "{}", - str::from_utf8(&buf).expect("ShaderInfoLog not valid utf8") + str::from_utf8(&*(buf.as_slice() as *const [MaybeUninit] as *const [u8])) + .expect("ShaderInfoLog not valid utf8") ); } } @@ -198,7 +202,7 @@ fn link_program(vs: GLuint, fs: GLuint) -> GLuint { if status != (gl::TRUE as GLint) { let mut len: GLint = 0; gl::GetProgramiv(program, gl::INFO_LOG_LENGTH, &mut len); - let mut buf = Vec::with_capacity(len as usize); + let mut buf: Vec> = Vec::with_capacity(len as usize); buf.set_len((len as usize) - 1); // subtract 1 to skip the trailing null character gl::GetProgramInfoLog( program, @@ -208,7 +212,8 @@ fn link_program(vs: GLuint, fs: GLuint) -> GLuint { ); panic!( "{}", - str::from_utf8(&buf).expect("ProgramInfoLog not valid utf8") + str::from_utf8(&*(buf.as_slice() as *const [MaybeUninit] as *const [u8])) + .expect("ProgramInfoLog not valid utf8") ); } program @@ -218,14 +223,6 @@ fn link_program(vs: GLuint, fs: GLuint) -> GLuint { impl Painter { pub(crate) fn new(canvas_width: u32, canvas_height: u32) -> Painter { unsafe { - let mut egui_texture = 0; - gl::GenTextures(1, &mut egui_texture); - gl::BindTexture(gl::TEXTURE_2D, egui_texture); - gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_WRAP_S, gl::CLAMP_TO_EDGE as i32); - gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_WRAP_T, gl::CLAMP_TO_EDGE as i32); - gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MIN_FILTER, gl::LINEAR as i32); - gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MAG_FILTER, gl::LINEAR as i32); - let vert_shader = compile_shader(VS_SRC, gl::VERTEX_SHADER); let frag_shader = compile_shader(FS_SRC, gl::FRAGMENT_SHADER); @@ -249,11 +246,10 @@ impl Painter { pos_buffer, tc_buffer, color_buffer, - egui_texture, vert_shader, frag_shader, - egui_texture_version: None, - user_textures: Default::default(), + egui_textures: HashMap::new(), + user_textures: Vec::new(), } } } @@ -290,48 +286,131 @@ impl Painter { id } - fn upload_egui_texture(&mut self, texture: &Texture) { - if self.egui_texture_version == Some(texture.version) { - return; // No change - } + fn upload_texture_delta(&mut self, texture_delta: &TexturesDelta) { + // As of egui 0.17, egui can manage multiple textures, and the texture updates themsleves + // are deltas + for (egui_texture_id, delta) in &texture_delta.set { + let egui_texture_id = match egui_texture_id { + egui::TextureId::Managed(id) => *id, + // This shouldn't be reachable + egui::TextureId::User(_) => { + eprintln!("Texture manager tried to set a user texture"); + continue; + } + }; + + // When egui sets a managed texture it may create a new one or it may update (part of) + // an existing texture + let gl_texture_id = *self + .egui_textures + .entry(egui_texture_id) + .or_insert_with(|| { + let mut gl_texture_id = 0; + unsafe { + gl::GenTextures(1, &mut gl_texture_id); + gl::BindTexture(gl::TEXTURE_2D, gl_texture_id); + gl::TexParameteri( + gl::TEXTURE_2D, + gl::TEXTURE_WRAP_S, + gl::CLAMP_TO_EDGE as i32, + ); + gl::TexParameteri( + gl::TEXTURE_2D, + gl::TEXTURE_WRAP_T, + gl::CLAMP_TO_EDGE as i32, + ); + gl::TexParameteri( + gl::TEXTURE_2D, + gl::TEXTURE_MIN_FILTER, + gl::LINEAR as i32, + ); + gl::TexParameteri( + gl::TEXTURE_2D, + gl::TEXTURE_MAG_FILTER, + gl::LINEAR as i32, + ); + } - let mut pixels: Vec = Vec::with_capacity(texture.pixels.len() * 4); - for &alpha in &texture.pixels { - let srgba = Color32::from_white_alpha(alpha); - pixels.push(srgba.r()); - pixels.push(srgba.g()); - pixels.push(srgba.b()); - pixels.push(srgba.a()); - } + gl_texture_id + }); + + let mut pixels: Vec = + Vec::with_capacity(delta.image.width() * delta.image.height() * 4); + match &delta.image { + egui::ImageData::Color(image) => { + for pixel in &image.pixels { + pixels.push(pixel.r()); + pixels.push(pixel.g()); + pixels.push(pixel.b()); + pixels.push(pixel.a()); + } + } + egui::ImageData::Alpha(image) => { + for pixel in &image.pixels { + let srgba = Color32::from_white_alpha(*pixel); + pixels.push(srgba.r()); + pixels.push(srgba.g()); + pixels.push(srgba.b()); + pixels.push(srgba.a()); + } + } + }; - unsafe { - gl::BindTexture(gl::TEXTURE_2D, self.egui_texture); - - let level = 0; - let internal_format = gl::RGBA; - let border = 0; - let src_format = gl::RGBA; - let src_type = gl::UNSIGNED_BYTE; - gl::TexImage2D( - gl::TEXTURE_2D, - level, - internal_format as i32, - texture.width as i32, - texture.height as i32, - border, - src_format, - src_type, - pixels.as_ptr() as *const c_void, - ); + unsafe { + gl::BindTexture(gl::TEXTURE_2D, gl_texture_id); - self.egui_texture_version = Some(texture.version); + let level = 0; + let internal_format = gl::RGBA; + let border = 0; + let src_format = gl::RGBA; + let src_type = gl::UNSIGNED_BYTE; + match delta.pos { + Some([x, y]) => { + gl::TexSubImage2D( + gl::TEXTURE_2D, + level, + x as i32, + y as i32, + delta.image.width() as i32, + delta.image.height() as i32, + src_format, + src_type, + pixels.as_ptr() as *const c_void, + ); + } + None => { + gl::TexImage2D( + gl::TEXTURE_2D, + level, + internal_format as i32, + delta.image.width() as i32, + delta.image.height() as i32, + border, + src_format, + src_type, + pixels.as_ptr() as *const c_void, + ); + } + } + } + } + + for egui_texture_id in &texture_delta.free { + match &egui_texture_id { + egui::TextureId::Managed(id) => { + unsafe { gl::DeleteTextures(1, &self.egui_textures[id]) } + self.egui_textures.remove(id); + } + // This shouldn't be reachable + egui::TextureId::User(_) => eprintln!("Texture manager tried to free user texture"), + }; } } fn upload_user_textures(&mut self) { unsafe { for user_texture in &mut self.user_textures { - if !user_texture.texture.is_none() && !user_texture.dirty { + if user_texture.texture.is_some() && !user_texture.dirty { continue; } let pixels = std::mem::take(&mut user_texture.pixels); @@ -396,7 +475,7 @@ impl Painter { fn get_texture(&self, texture_id: egui::TextureId) -> Option { match texture_id { - egui::TextureId::Egui => Some(self.egui_texture), + egui::TextureId::Managed(id) => self.egui_textures.get(&id).copied(), egui::TextureId::User(id) => { let id = id as usize; if id >= self.user_textures.len() { @@ -409,7 +488,9 @@ impl Painter { pub fn update_user_texture_data(&mut self, texture_id: egui::TextureId, pixels: &[Color32]) { match texture_id { - egui::TextureId::Egui => {} + egui::TextureId::Managed(_) => { + eprintln!("Managed textures should be updated through the texture manager") + } egui::TextureId::User(id) => { let id = id as usize; assert!(id < self.user_textures.len()); @@ -430,10 +511,10 @@ impl Painter { &mut self, clear_color: egui::Rgba, clipped_meshes: Vec, - egui_texture: &Texture, + texture_delta: &TexturesDelta, pixels_per_point: f32, ) { - self.upload_egui_texture(egui_texture); + self.upload_texture_delta(texture_delta); self.upload_user_textures(); unsafe { @@ -669,8 +750,8 @@ impl Drop for Painter { gl::DeleteVertexArrays(1, &self.index_buffer); } - if self.egui_texture != 0 { - gl::DeleteTextures(1, &self.egui_texture); + for (_, gl_texture_id) in self.egui_textures.drain() { + gl::DeleteTextures(1, &gl_texture_id); } for user_texture in &self.user_textures { if let Some(texture_id) = user_texture.texture { diff --git a/src/settings.rs b/src/settings.rs deleted file mode 100644 index 0235863..0000000 --- a/src/settings.rs +++ /dev/null @@ -1,13 +0,0 @@ -//! Configure your application; - -use crate::RenderSettings; -use baseview::WindowOpenOptions; - -/// The settings of an application. -pub struct Settings { - /// The `baseview` window settings. - pub window: WindowOpenOptions, - - /// The settings for the rendering backend. - pub render_settings: RenderSettings, -} diff --git a/src/window.rs b/src/window.rs index c8329be..08ae485 100644 --- a/src/window.rs +++ b/src/window.rs @@ -1,7 +1,9 @@ -use crate::renderer::{RenderSettings, Renderer}; -use crate::Settings; -use baseview::{Event, EventStatus, Window, WindowHandle, WindowHandler, WindowScalePolicy}; +use crate::renderer::Renderer; +use baseview::{ + Event, EventStatus, Window, WindowHandle, WindowHandler, WindowOpenOptions, WindowScalePolicy, +}; use copypasta::ClipboardProvider; +use keyboard_types::Modifiers; use raw_window_handle::HasRawWindowHandle; use std::time::Instant; @@ -35,7 +37,8 @@ impl<'a> Queue<'a> { *self.bg_color = bg_color; } - /// Create a new custom texture. + /// Create a new custom texture. Instead of using this and [Self::update_user_texture_data()], + /// you can also use the texture methods on the egui context. pub fn new_user_texture( &mut self, size: (usize, usize), @@ -69,17 +72,17 @@ struct OpenSettings { } impl OpenSettings { - fn new(settings: &Settings) -> Self { + fn new(settings: &WindowOpenOptions) -> Self { // WindowScalePolicy does not implement copy/clone. - let scale_policy = match &settings.window.scale { + let scale_policy = match &settings.scale { WindowScalePolicy::SystemScaleFactor => WindowScalePolicy::SystemScaleFactor, WindowScalePolicy::ScaleFactor(scale) => WindowScalePolicy::ScaleFactor(*scale), }; Self { scale_policy, - logical_width: settings.window.size.width as f64, - logical_height: settings.window.size.height as f64, + logical_width: settings.size.width as f64, + logical_height: settings.size.height as f64, } } } @@ -88,13 +91,13 @@ impl OpenSettings { pub struct EguiWindow where State: 'static + Send, - U: FnMut(&egui::CtxRef, &mut Queue, &mut State), + U: FnMut(&egui::Context, &mut Queue, &mut State), U: 'static + Send, { user_state: Option, user_update: U, - egui_ctx: egui::CtxRef, + egui_ctx: egui::Context, raw_input: egui::RawInput, clipboard_ctx: Option, @@ -112,28 +115,30 @@ where impl EguiWindow where State: 'static + Send, - U: FnMut(&egui::CtxRef, &mut Queue, &mut State), + U: FnMut(&egui::Context, &mut Queue, &mut State), U: 'static + Send, { fn new( window: &mut baseview::Window<'_>, open_settings: OpenSettings, - mut render_settings: Option, mut build: B, update: U, mut state: State, - ) -> EguiWindow + ) -> Option> where - B: FnMut(&egui::CtxRef, &mut Queue, &mut State), + B: FnMut(&egui::Context, &mut Queue, &mut State), B: 'static + Send, { + // This only works for windows with OpenGL contexts attached to them + window.gl_context()?; + // Assume scale for now until there is an event with a new one. let scale = match open_settings.scale_policy { WindowScalePolicy::ScaleFactor(scale) => scale, WindowScalePolicy::SystemScaleFactor => 1.0, } as f32; - let egui_ctx = egui::CtxRef::default(); + let egui_ctx = egui::Context::default(); let raw_input = egui::RawInput { screen_rect: Some(Rect::from_min_size( @@ -156,12 +161,12 @@ where let mut renderer = Renderer::new( window, - render_settings.take().unwrap(), ( (open_settings.logical_width * scale as f64).round() as u32, (open_settings.logical_height * scale as f64).round() as u32, ), - ); + ) + .expect("Somehow the window was created without an OpenGL context"); let mut bg_color = Rgba::BLACK; @@ -183,7 +188,7 @@ where } }; - Self { + Some(Self { user_state: Some(state), user_update: update, @@ -199,7 +204,7 @@ where redraw: true, mouse_pos: None, close_requested, - } + }) } /// Open a new child window. @@ -211,28 +216,34 @@ where /// call `ctx.set_fonts()`. Optional. /// * `update` - Called before each frame. Here you should update the state of your /// application and build the UI. + /// + /// Returns a `None` if the window settings did not contain an OpenGL config. pub fn open_parented( parent: &P, - settings: Settings, + settings: WindowOpenOptions, state: State, build: B, update: U, - ) -> WindowHandle + ) -> Option where P: HasRawWindowHandle, - B: FnMut(&egui::CtxRef, &mut Queue, &mut State), + B: FnMut(&egui::Context, &mut Queue, &mut State), B: 'static + Send, { - let open_settings = OpenSettings::new(&settings); - let render_settings = Some(settings.render_settings); - - Window::open_parented( - parent, - settings.window, - move |window: &mut baseview::Window<'_>| -> EguiWindow { - EguiWindow::new(window, open_settings, render_settings, build, update, state) - }, - ) + if settings.gl_config.is_some() { + let open_settings = OpenSettings::new(&settings); + + Some(Window::open_parented( + parent, + settings, + move |window: &mut baseview::Window<'_>| -> EguiWindow { + EguiWindow::new(window, open_settings, build, update, state) + .expect("Somehow the window got created without an OpenGL context") + }, + )) + } else { + None + } } /// Open a new window as if it had a parent window. @@ -243,25 +254,31 @@ where /// call `ctx.set_fonts()`. Optional. /// * `update` - Called before each frame. Here you should update the state of your /// application and build the UI. + /// + /// Returns a `None` if the window settings did not contain an OpenGL config. pub fn open_as_if_parented( - settings: Settings, + settings: WindowOpenOptions, state: State, build: B, update: U, - ) -> WindowHandle + ) -> Option where - B: FnMut(&egui::CtxRef, &mut Queue, &mut State), + B: FnMut(&egui::Context, &mut Queue, &mut State), B: 'static + Send, { - let open_settings = OpenSettings::new(&settings); - let render_settings = Some(settings.render_settings); - - Window::open_as_if_parented( - settings.window, - move |window: &mut baseview::Window<'_>| -> EguiWindow { - EguiWindow::new(window, open_settings, render_settings, build, update, state) - }, - ) + if settings.gl_config.is_some() { + let open_settings = OpenSettings::new(&settings); + + Some(Window::open_as_if_parented( + settings, + move |window: &mut baseview::Window<'_>| -> EguiWindow { + EguiWindow::new(window, open_settings, build, update, state) + .expect("Somehow the window got created without an OpenGL context") + }, + )) + } else { + None + } } /// Open a new window that blocks the current thread until the window is destroyed. @@ -272,27 +289,38 @@ where /// call `ctx.set_fonts()`. Optional. /// * `update` - Called before each frame. Here you should update the state of your /// application and build the UI. - pub fn open_blocking(settings: Settings, state: State, build: B, update: U) + /// + /// Returns immediately if the window settings did not contain an OpenGL config. + pub fn open_blocking(settings: WindowOpenOptions, state: State, build: B, update: U) where - B: FnMut(&egui::CtxRef, &mut Queue, &mut State), + B: FnMut(&egui::Context, &mut Queue, &mut State), B: 'static + Send, { - let open_settings = OpenSettings::new(&settings); - let render_settings = Some(settings.render_settings); + if settings.gl_config.is_some() { + let open_settings = OpenSettings::new(&settings); + + Window::open_blocking( + settings, + move |window: &mut baseview::Window<'_>| -> EguiWindow { + EguiWindow::new(window, open_settings, build, update, state) + .expect("Somehow the window got created without an OpenGL context") + }, + ) + } + } - Window::open_blocking( - settings.window, - move |window: &mut baseview::Window<'_>| -> EguiWindow { - EguiWindow::new(window, open_settings, render_settings, build, update, state) - }, - ) + /// Update the pressed key modifiers when a mouse event has sent a new set of modifiers. + fn update_modifiers(&mut self, modifiers: &Modifiers) { + self.raw_input.modifiers.alt = !(*modifiers & Modifiers::ALT).is_empty(); + self.raw_input.modifiers.shift = !(*modifiers & Modifiers::SHIFT).is_empty(); + self.raw_input.modifiers.command = !(*modifiers & Modifiers::CONTROL).is_empty(); } } impl WindowHandler for EguiWindow where State: 'static + Send, - U: FnMut(&egui::CtxRef, &mut Queue, &mut State), + U: FnMut(&egui::Context, &mut Queue, &mut State), U: 'static + Send, { fn on_frame(&mut self, window: &mut Window) { @@ -310,24 +338,26 @@ where (self.user_update)(&self.egui_ctx, &mut queue, state); - let (output, paint_cmds) = self.egui_ctx.end_frame(); + let output = self.egui_ctx.end_frame(); + let platform_output = output.platform_output; if output.needs_repaint || self.redraw || repaint_requested { - let paint_jobs = self.egui_ctx.tessellate(paint_cmds); + let paint_jobs = self.egui_ctx.tessellate(output.shapes); self.renderer.render( + window.gl_context().unwrap(), self.bg_color, paint_jobs, - &self.egui_ctx.texture(), + &output.textures_delta, self.scale_factor, ); self.redraw = false; } - if !output.copied_text.is_empty() { + if !platform_output.copied_text.is_empty() { if let Some(clipboard_ctx) = &mut self.clipboard_ctx { - if let Err(err) = clipboard_ctx.set_contents(output.copied_text) { + if let Err(err) = clipboard_ctx.set_contents(platform_output.copied_text) { eprintln!("Copy/Cut error: {}", err); } } @@ -338,19 +368,26 @@ where window.close(); } - // TODO: Handle the rest of the outputs. + // TODO: Handle the rest of the platform outputs } } fn on_event(&mut self, _window: &mut Window, event: Event) -> EventStatus { match &event { baseview::Event::Mouse(event) => match event { - baseview::MouseEvent::CursorMoved { position } => { + baseview::MouseEvent::CursorMoved { + position, + modifiers, + } => { + self.update_modifiers(modifiers); + let pos = pos2(position.x as f32, position.y as f32); self.mouse_pos = Some(pos); self.raw_input.events.push(egui::Event::PointerMoved(pos)); } - baseview::MouseEvent::ButtonPressed(button) => { + baseview::MouseEvent::ButtonPressed { button, modifiers } => { + self.update_modifiers(modifiers); + if let Some(pos) = self.mouse_pos { if let Some(button) = translate_mouse_button(*button) { self.raw_input.events.push(egui::Event::PointerButton { @@ -362,7 +399,9 @@ where } } } - baseview::MouseEvent::ButtonReleased(button) => { + baseview::MouseEvent::ButtonReleased { button, modifiers } => { + self.update_modifiers(modifiers); + if let Some(pos) = self.mouse_pos { if let Some(button) = translate_mouse_button(*button) { self.raw_input.events.push(egui::Event::PointerButton { @@ -374,7 +413,12 @@ where } } } - baseview::MouseEvent::WheelScrolled(scroll_delta) => { + baseview::MouseEvent::WheelScrolled { + delta: scroll_delta, + modifiers, + } => { + self.update_modifiers(modifiers); + let mut delta = match scroll_delta { baseview::ScrollDelta::Lines { x, y } => { let points_per_scroll_line = 50.0; // Scroll speed decided by consensus: https://github.com/emilk/egui/issues/461 @@ -396,9 +440,11 @@ where if self.raw_input.modifiers.ctrl || self.raw_input.modifiers.command { // Treat as zoom instead: - self.raw_input.zoom_delta *= (delta.y / 200.0).exp(); + self.raw_input + .events + .push(egui::Event::Zoom((delta.y / 200.0).exp())); } else { - self.raw_input.scroll_delta += delta; + self.raw_input.events.push(egui::Event::Scroll(delta)); } } baseview::MouseEvent::CursorLeft => { @@ -429,7 +475,6 @@ where self.raw_input.modifiers.mac_cmd = pressed; self.raw_input.modifiers.command = pressed; } - () // prevent `rustfmt` from breaking this } _ => (), }