1
0
mirror of https://github.com/emilk/egui.git synced 2026-08-30 21:30:03 -04:00
Files
egui/crates/egui_glow/examples/pure_glow.rs
Arthur Brussee 6f2f006885 Upgrade winit to 0.30.2 (#4849)
* Closes https://github.com/emilk/egui/issues/1918
* Closes https://github.com/emilk/egui/issues/4437
* Closes https://github.com/emilk/egui/issues/4709
* [x] I have followed the instructions in the PR template

Hiya,

I need new winit for a specific fix for a android_native_actvity. There
are already two PRs, but both don't seem to have a lot of movement, or
are entirely complete:

https://github.com/emilk/egui/pull/4466
Seems to have gone stale & is missing some bits.

https://github.com/emilk/egui/pull/4702
Also seems stale (if less so), and is missing a refactor to
run_on_demand. I also *think* the accesskit integration has a mistake
and can't be enabled. I've marked them as a co-author on this as I
started from this branch. (I think! Haven't done that on git before...).

Sorry for the wall of text but just dumping some details / thoughts
here:

- There's an issue with creating child windows in winit 0.30.1 and up on
macOS. The multiple_viewports, "create immediate viewport" example
crashes on anything later 0.30.1, with a stack overflow in unsafe code.
I've create [a winit
issue](https://github.com/rust-windowing/winit/issues/3800), it *might*
already be fixed in 0.31.0 but I can't test as 0.31 will likely require
another refactoring. For now I have just pinned things to 0.30.0 exatly.

- Winit has deprecated run_on_demand, instead requiring the
ApplicationHandler interface. In 0.31.0 run_on_demand is removed. I've
refactored both the integration and the WinitApp trait to follow this
pattern. I've left user_events a bit more opaque, as it seems 0.31.0 is
doing a rework of UserEvents too.

- I've used the new lazy init approach for access kit from this branch
https://github.com/mwcampbell/egui/tree/accesskit-new-lazy-init and
marked Matt as co-author, thanks Matt!

- There was very similair but not quite the same code for run_and_return
and run_and_exit. I've merged them, but looking at the github issues
graveyard it seems vey finnicky. I *hope* this is more robust than
before but it's a bit scary.

- when receiving new_events this also used to check the redraw timing
dictionary. That doesn't seem necesarry so left this out, but that is a
slight behaviour change?

- I have reeneabled serial_windows on macOS. I wondered whether it was
fixed after this PR and does seem to be! However, even before this PR it
seems to work, so maybe winit has sorted things out before that...
Windows also works fine now without the extra hack.

- I've done a very basic test of AccessKit on Windows and screen reader
seems ok but I'm really not knowleadgable enough to say whether it's all
good or not.

- I've tested cargo tests & all examples on Windows & macOS, and ran a
basic Android app. Still, testing native platforms is wel... hard so if
anyone can test linux / iOs / older mac versions / windows 10 would
probably be a good idea!

- For consistencys sake I've made all event like functions in WinitApp
return a `Result<EventResult>`. There's quite a bit of Ok-wrapping now,
maybe too annoying? Not sure.

Thank you for having a look!

# Tested on
* [x] macOS
* [x] Windows
* [x] Wayland (thanks [SiebenCorgie](https://github.com/SiebenCorgie))
* [x] X11 (thanks
[crumblingstatue](https://github.com/crumblingstatue)!,
[SiebenCorgie](https://github.com/SiebenCorgie))


# TODO
* [x] Fix "follow system theme" not working on initial startup (winit
issue, pinning to 0.30.2 for now).
* [x] Fix `request_repaint_after`

---------

Co-authored-by: mwcampbell <mattcampbell@pobox.com>
Co-authored-by: j-axa <josef.axa@gmail.com>
Co-authored-by: DataTriny <datatriny@gmail.com>
Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com>
2024-07-31 09:43:16 +02:00

347 lines
13 KiB
Rust

//! Example how to use pure `egui_glow`.
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release
#![allow(rustdoc::missing_crate_level_docs)] // it's an example
#![allow(clippy::undocumented_unsafe_blocks)]
#![allow(clippy::arc_with_non_send_sync)]
// `clippy::arc_with_non_send_sync`: `glow::Context` was accidentally non-Sync in glow 0.13, but that will be fixed in future releases of glow: https://github.com/grovesNL/glow/commit/c4a5f7151b9b4bbb380faa06ec27415235d1bf7e
#![allow(unsafe_code)]
use std::num::NonZeroU32;
use std::sync::Arc;
use egui_winit::winit;
use winit::raw_window_handle::HasWindowHandle;
/// The majority of `GlutinWindowContext` is taken from `eframe`
struct GlutinWindowContext {
window: winit::window::Window,
gl_context: glutin::context::PossiblyCurrentContext,
gl_display: glutin::display::Display,
gl_surface: glutin::surface::Surface<glutin::surface::WindowSurface>,
}
impl GlutinWindowContext {
// refactor this function to use `glutin-winit` crate eventually.
// preferably add android support at the same time.
#[allow(unsafe_code)]
unsafe fn new(event_loop: &winit::event_loop::ActiveEventLoop) -> Self {
use glutin::context::NotCurrentGlContext;
use glutin::display::GetGlDisplay;
use glutin::display::GlDisplay;
use glutin::prelude::GlSurface;
let winit_window_builder = winit::window::WindowAttributes::default()
.with_resizable(true)
.with_inner_size(winit::dpi::LogicalSize {
width: 800.0,
height: 600.0,
})
.with_title("egui_glow example") // Keep hidden until we've painted something. See https://github.com/emilk/egui/pull/2279
.with_visible(false);
let config_template_builder = glutin::config::ConfigTemplateBuilder::new()
.prefer_hardware_accelerated(None)
.with_depth_size(0)
.with_stencil_size(0)
.with_transparency(false);
log::debug!("trying to get gl_config");
let (mut window, gl_config) =
glutin_winit::DisplayBuilder::new() // let glutin-winit helper crate handle the complex parts of opengl context creation
.with_preference(glutin_winit::ApiPreference::FallbackEgl) // https://github.com/emilk/egui/issues/2520#issuecomment-1367841150
.with_window_attributes(Some(winit_window_builder.clone()))
.build(
event_loop,
config_template_builder,
|mut config_iterator| {
config_iterator.next().expect(
"failed to find a matching configuration for creating glutin config",
)
},
)
.expect("failed to create gl_config");
let gl_display = gl_config.display();
log::debug!("found gl_config: {:?}", &gl_config);
let raw_window_handle = window.as_ref().map(|w| {
w.window_handle()
.expect("failed to get window handle")
.as_raw()
});
log::debug!("raw window handle: {:?}", raw_window_handle);
let context_attributes =
glutin::context::ContextAttributesBuilder::new().build(raw_window_handle);
// by default, glutin will try to create a core opengl context. but, if it is not available, try to create a gl-es context using this fallback attributes
let fallback_context_attributes = glutin::context::ContextAttributesBuilder::new()
.with_context_api(glutin::context::ContextApi::Gles(None))
.build(raw_window_handle);
let not_current_gl_context = unsafe {
gl_display
.create_context(&gl_config, &context_attributes)
.unwrap_or_else(|_| {
log::debug!("failed to create gl_context with attributes: {:?}. retrying with fallback context attributes: {:?}",
&context_attributes,
&fallback_context_attributes);
gl_config
.display()
.create_context(&gl_config, &fallback_context_attributes)
.expect("failed to create context even with fallback attributes")
})
};
// this is where the window is created, if it has not been created while searching for suitable gl_config
let window = window.take().unwrap_or_else(|| {
log::debug!("window doesn't exist yet. creating one now with finalize_window");
glutin_winit::finalize_window(event_loop, winit_window_builder.clone(), &gl_config)
.expect("failed to finalize glutin window")
});
let (width, height): (u32, u32) = window.inner_size().into();
let width = NonZeroU32::new(width).unwrap_or(NonZeroU32::MIN);
let height = NonZeroU32::new(height).unwrap_or(NonZeroU32::MIN);
let surface_attributes =
glutin::surface::SurfaceAttributesBuilder::<glutin::surface::WindowSurface>::new()
.build(
window
.window_handle()
.expect("failed to get window handle")
.as_raw(),
width,
height,
);
log::debug!(
"creating surface with attributes: {:?}",
&surface_attributes
);
let gl_surface = unsafe {
gl_display
.create_window_surface(&gl_config, &surface_attributes)
.unwrap()
};
log::debug!("surface created successfully: {gl_surface:?}.making context current");
let gl_context = not_current_gl_context.make_current(&gl_surface).unwrap();
gl_surface
.set_swap_interval(
&gl_context,
glutin::surface::SwapInterval::Wait(NonZeroU32::MIN),
)
.unwrap();
Self {
window,
gl_context,
gl_display,
gl_surface,
}
}
fn window(&self) -> &winit::window::Window {
&self.window
}
fn resize(&self, physical_size: winit::dpi::PhysicalSize<u32>) {
use glutin::surface::GlSurface;
self.gl_surface.resize(
&self.gl_context,
physical_size.width.try_into().unwrap(),
physical_size.height.try_into().unwrap(),
);
}
fn swap_buffers(&self) -> glutin::error::Result<()> {
use glutin::surface::GlSurface;
self.gl_surface.swap_buffers(&self.gl_context)
}
fn get_proc_address(&self, addr: &std::ffi::CStr) -> *const std::ffi::c_void {
use glutin::display::GlDisplay;
self.gl_display.get_proc_address(addr)
}
}
#[derive(Debug)]
pub enum UserEvent {
Redraw(std::time::Duration),
}
struct GlowApp {
proxy: winit::event_loop::EventLoopProxy<UserEvent>,
gl_window: Option<GlutinWindowContext>,
gl: Option<Arc<glow::Context>>,
egui_glow: Option<egui_glow::EguiGlow>,
repaint_delay: std::time::Duration,
clear_color: [f32; 3],
}
impl GlowApp {
fn new(proxy: winit::event_loop::EventLoopProxy<UserEvent>) -> Self {
Self {
proxy,
gl_window: None,
gl: None,
egui_glow: None,
repaint_delay: std::time::Duration::MAX,
clear_color: [0.1, 0.1, 0.1],
}
}
}
impl winit::application::ApplicationHandler<UserEvent> for GlowApp {
fn resumed(&mut self, event_loop: &winit::event_loop::ActiveEventLoop) {
let (gl_window, gl) = create_display(event_loop);
let gl = std::sync::Arc::new(gl);
gl_window.window().set_visible(true);
let egui_glow = egui_glow::EguiGlow::new(event_loop, gl.clone(), None, None, true);
let event_loop_proxy = egui::mutex::Mutex::new(self.proxy.clone());
egui_glow
.egui_ctx
.set_request_repaint_callback(move |info| {
event_loop_proxy
.lock()
.send_event(UserEvent::Redraw(info.delay))
.expect("Cannot send event");
});
self.gl_window = Some(gl_window);
self.gl = Some(gl);
self.egui_glow = Some(egui_glow);
}
fn window_event(
&mut self,
event_loop: &winit::event_loop::ActiveEventLoop,
_window_id: winit::window::WindowId,
event: winit::event::WindowEvent,
) {
let mut redraw = || {
let mut quit = false;
self.egui_glow.as_mut().unwrap().run(
self.gl_window.as_mut().unwrap().window(),
|egui_ctx| {
egui::SidePanel::left("my_side_panel").show(egui_ctx, |ui| {
ui.heading("Hello World!");
if ui.button("Quit").clicked() {
quit = true;
}
ui.color_edit_button_rgb(self.clear_color.as_mut().try_into().unwrap());
});
},
);
if quit {
event_loop.exit();
} else {
event_loop.set_control_flow(if self.repaint_delay.is_zero() {
self.gl_window.as_mut().unwrap().window().request_redraw();
winit::event_loop::ControlFlow::Poll
} else if let Some(repaint_after_instant) =
std::time::Instant::now().checked_add(self.repaint_delay)
{
winit::event_loop::ControlFlow::WaitUntil(repaint_after_instant)
} else {
winit::event_loop::ControlFlow::Wait
});
}
{
unsafe {
use glow::HasContext as _;
self.gl.as_mut().unwrap().clear_color(
self.clear_color[0],
self.clear_color[1],
self.clear_color[2],
1.0,
);
self.gl.as_mut().unwrap().clear(glow::COLOR_BUFFER_BIT);
}
// draw things behind egui here
self.egui_glow
.as_mut()
.unwrap()
.paint(self.gl_window.as_mut().unwrap().window());
// draw things on top of egui here
self.gl_window.as_mut().unwrap().swap_buffers().unwrap();
self.gl_window.as_mut().unwrap().window().set_visible(true);
}
};
use winit::event::WindowEvent;
if matches!(event, WindowEvent::CloseRequested | WindowEvent::Destroyed) {
event_loop.exit();
return;
}
if matches!(event, WindowEvent::RedrawRequested) {
redraw();
return;
}
if let winit::event::WindowEvent::Resized(physical_size) = &event {
self.gl_window.as_mut().unwrap().resize(*physical_size);
}
let event_response = self
.egui_glow
.as_mut()
.unwrap()
.on_window_event(self.gl_window.as_mut().unwrap().window(), &event);
if event_response.repaint {
self.gl_window.as_mut().unwrap().window().request_redraw();
}
}
fn user_event(&mut self, _event_loop: &winit::event_loop::ActiveEventLoop, event: UserEvent) {
match event {
UserEvent::Redraw(delay) => self.repaint_delay = delay,
}
}
fn new_events(
&mut self,
_event_loop: &winit::event_loop::ActiveEventLoop,
cause: winit::event::StartCause,
) {
if let winit::event::StartCause::ResumeTimeReached { .. } = &cause {
self.gl_window.as_mut().unwrap().window().request_redraw();
}
}
fn exiting(&mut self, _event_loop: &winit::event_loop::ActiveEventLoop) {
self.egui_glow.as_mut().unwrap().destroy();
}
}
fn main() {
let event_loop = winit::event_loop::EventLoop::<UserEvent>::with_user_event()
.build()
.unwrap();
let proxy = event_loop.create_proxy();
let mut app = GlowApp::new(proxy);
event_loop.run_app(&mut app).expect("failed to run app");
}
fn create_display(
event_loop: &winit::event_loop::ActiveEventLoop,
) -> (GlutinWindowContext, glow::Context) {
let glutin_window_context = unsafe { GlutinWindowContext::new(event_loop) };
let gl = unsafe {
glow::Context::from_loader_function(|s| {
let s = std::ffi::CString::new(s)
.expect("failed to construct C string from string for gl proc address");
glutin_window_context.get_proc_address(&s)
})
};
(glutin_window_context, gl)
}