use std::collections::HashMap;
use std::num::NonZeroU32;
use std::{mem, sync::Arc};
use crate::multi_window::{DefaultCustomEvent, EventTrait, NewWindowRequest};
use egui::NumExt;
use egui_glow::glow;
use egui_glow::EguiGlow;
use glutin::context::{NotCurrentContext, PossiblyCurrentContext};
use glutin::prelude::{GlConfig, GlDisplay};
use glutin::prelude::{
NotCurrentGlContextSurfaceAccessor, PossiblyCurrentContextGlSurfaceAccessor,
};
use glutin::surface::GlSurface;
use glutin::surface::SurfaceAttributesBuilder;
use glutin::surface::WindowSurface;
use raw_window_handle::{HasRawDisplayHandle, HasRawWindowHandle};
use thiserror::Error;
use winit::window::WindowId;
use winit::{
event::Event,
event_loop::{ControlFlow, EventLoopWindowTarget},
};
pub struct ContextHolder<T> {
context: T,
window: winit::window::Window,
ws: glutin::surface::Surface<WindowSurface>,
display: glutin::display::Display,
options: TrackedWindowOptions,
}
impl<T> ContextHolder<T> {
fn new(
context: T,
window: winit::window::Window,
ws: glutin::surface::Surface<WindowSurface>,
display: glutin::display::Display,
options: TrackedWindowOptions,
) -> Self {
Self {
context,
window,
ws,
display,
options,
}
}
}
impl ContextHolder<PossiblyCurrentContext> {
fn swap_buffers(&self) -> glutin::error::Result<()> {
if self.options.vsync {
let _e = self.ws.set_swap_interval(
&self.context,
glutin::surface::SwapInterval::Wait(NonZeroU32::new(1).unwrap()),
);
} else {
let _e = self
.ws
.set_swap_interval(&self.context, glutin::surface::SwapInterval::DontWait);
}
self.ws.swap_buffers(&self.context)
}
fn resize(&self, size: winit::dpi::PhysicalSize<u32>) {
let w = size.width;
let h = size.height;
self.ws.resize(
&self.context,
NonZeroU32::new(w.at_least(1)).unwrap(),
NonZeroU32::new(h.at_least(1)).unwrap(),
)
}
fn make_current(&self) -> glutin::error::Result<()> {
self.context.make_current(&self.ws)
}
fn get_proc_address(&self, s: &str) -> *const std::ffi::c_void {
let cs: *const std::ffi::c_char = s.as_ptr().cast();
let cst = unsafe { std::ffi::CStr::from_ptr(cs) };
self.display.get_proc_address(cst)
}
}
impl ContextHolder<NotCurrentContext> {
fn make_current(self) -> Result<ContextHolder<PossiblyCurrentContext>, glutin::error::Error> {
let c = self.context.make_current(&self.ws).unwrap();
let s = ContextHolder::<PossiblyCurrentContext> {
context: c,
window: self.window,
ws: self.ws,
display: self.display,
options: self.options,
};
Ok(s)
}
}
pub struct RedrawResponse<T, U = DefaultCustomEvent> {
pub quit: bool,
pub new_windows: Vec<NewWindowRequest<T, U>>,
}
pub trait TrackedWindow<T, U = DefaultCustomEvent> {
fn is_root(&self) -> bool {
false
}
fn can_quit(&mut self, _c: &mut T) -> bool {
true
}
fn set_root(&mut self, _root: bool) {}
fn custom_event(
&mut self,
_event: &U,
_c: &mut T,
_egui: &mut EguiGlow,
_window: &winit::window::Window,
_clipboard: &mut arboard::Clipboard,
) -> RedrawResponse<T, U> {
RedrawResponse {
quit: false,
new_windows: vec![],
}
}
fn redraw(
&mut self,
c: &mut T,
egui: &mut EguiGlow,
window: &winit::window::Window,
clipboard: &mut arboard::Clipboard,
) -> RedrawResponse<T, U>;
unsafe fn opengl_before(&mut self, _c: &mut T, _gl: &Arc<egui_glow::painter::Context>) {}
unsafe fn opengl_after(&mut self, _c: &mut T, _gl: &Arc<egui_glow::painter::Context>) {}
}
fn handle_event<COMMON, U: EventTrait>(
s: &mut dyn TrackedWindow<COMMON, U>,
event: &winit::event::Event<U>,
c: &mut COMMON,
egui: &mut EguiGlow,
root_window_exists: bool,
gl_window: &mut ContextHolder<PossiblyCurrentContext>,
clipboard: &mut arboard::Clipboard,
) -> TrackedWindowControl<COMMON, U> {
let mut control_flow = ControlFlow::Wait; let mut redraw = || {
let input = egui.egui_winit.take_egui_input(&gl_window.window);
let ppp = input.pixels_per_point;
egui.egui_ctx.begin_frame(input);
let rr = s.redraw(c, egui, &gl_window.window, clipboard);
let full_output = egui.egui_ctx.end_frame();
if rr.quit {
control_flow = winit::event_loop::ControlFlow::Exit;
} else if full_output.repaint_after.is_zero() {
gl_window.window.request_redraw();
control_flow = winit::event_loop::ControlFlow::Poll;
} else if full_output.repaint_after.as_millis() > 0 && full_output.repaint_after.as_millis() < 10000 {
control_flow = winit::event_loop::ControlFlow::WaitUntil(
std::time::Instant::now() + full_output.repaint_after,
);
} else {
control_flow = winit::event_loop::ControlFlow::Wait;
};
{
let color = egui::Rgba::from_white_alpha(0.0);
unsafe {
use glow::HasContext as _;
egui.painter
.gl()
.clear_color(color[0], color[1], color[2], color[3]);
egui.painter.gl().clear(glow::COLOR_BUFFER_BIT);
}
unsafe { s.opengl_before(c, egui.painter.gl()) };
let prim = egui.egui_ctx.tessellate(full_output.shapes);
egui.painter.paint_and_update_textures(
gl_window.window.inner_size().into(),
ppp.unwrap_or(1.0),
&prim[..],
&full_output.textures_delta,
);
unsafe { s.opengl_after(c, egui.painter.gl()) };
gl_window.swap_buffers().unwrap();
}
rr
};
let response = match event {
winit::event::Event::RedrawEventsCleared if cfg!(windows) => Some(redraw()),
winit::event::Event::RedrawRequested(_) if !cfg!(windows) => Some(redraw()),
winit::event::Event::UserEvent(ue) => Some(s.custom_event(ue, c, egui, &gl_window.window, clipboard)),
winit::event::Event::WindowEvent { event, .. } => {
if let winit::event::WindowEvent::Resized(physical_size) = event {
gl_window.resize(*physical_size);
}
if let winit::event::WindowEvent::CloseRequested = event {
control_flow = winit::event_loop::ControlFlow::Exit;
}
let resp = egui.on_event(event);
if resp.repaint {
gl_window.window.request_redraw();
}
None
}
winit::event::Event::LoopDestroyed => {
egui.destroy();
None
}
_ => None,
};
if !root_window_exists && !s.is_root() {
control_flow = ControlFlow::Exit;
}
TrackedWindowControl {
requested_control_flow: control_flow,
windows_to_create: if let Some(a) = response {
a.new_windows
} else {
Vec::new()
},
}
}
#[derive(Copy, Clone)]
pub struct TrackedWindowOptions {
pub vsync: bool,
pub shader: Option<egui_glow::ShaderVersion>,
}
pub struct TrackedWindowContainer<T, U: EventTrait> {
pub gl_window: IndeterminateWindowedContext,
pub egui: Option<EguiGlow>,
pub window: Box<dyn TrackedWindow<T, U>>,
pub shader: Option<egui_glow::ShaderVersion>,
_phantom: std::marker::PhantomData<U>,
}
impl<T, U: EventTrait> TrackedWindowContainer<T, U> {
pub fn get_window_id(&self) -> Option<WindowId> {
match &self.gl_window {
IndeterminateWindowedContext::PossiblyCurrent(w) => Some(w.window.id()),
IndeterminateWindowedContext::NotCurrent(w) => Some(w.window.id()),
IndeterminateWindowedContext::None => {
println!("Not able to get window id");
None
}
}
}
pub fn create<TE>(
window: Box<dyn TrackedWindow<T, U>>,
window_builder: winit::window::WindowBuilder,
event_loop: &winit::event_loop::EventLoopWindowTarget<TE>,
options: &TrackedWindowOptions,
) -> Result<TrackedWindowContainer<T, U>, DisplayCreationError> {
let rdh = event_loop.raw_display_handle();
let winitwindow = window_builder.build(event_loop).unwrap();
let rwh = winitwindow.raw_window_handle();
#[cfg(target_os = "windows")]
let pref = glutin::display::DisplayApiPreference::Wgl(Some(rwh));
#[cfg(target_os = "linux")]
let pref = glutin::display::DisplayApiPreference::Egl;
#[cfg(target_os = "macos")]
let pref = glutin::display::DisplayApiPreference::Cgl;
let display = unsafe { glutin::display::Display::new(rdh, pref) };
if let Ok(display) = display {
let configt = glutin::config::ConfigTemplateBuilder::default().build();
let config = unsafe { display.find_configs(configt) }
.unwrap()
.reduce(|config, acc| {
if config.num_samples() > acc.num_samples() {
config
} else {
acc
}
});
if let Some(config) = config {
let sab: SurfaceAttributesBuilder<WindowSurface> =
glutin::surface::SurfaceAttributesBuilder::default();
let sa = sab.build(
rwh,
std::num::NonZeroU32::new(winitwindow.inner_size().width).unwrap(),
std::num::NonZeroU32::new(winitwindow.inner_size().height).unwrap(),
);
let ws = unsafe { display.create_window_surface(&config, &sa).unwrap() };
let attr = glutin::context::ContextAttributesBuilder::new().build(Some(rwh));
let gl_window = unsafe { display.create_context(&config, &attr).unwrap() };
return Ok(TrackedWindowContainer {
window,
gl_window: IndeterminateWindowedContext::NotCurrent(ContextHolder::new(
gl_window,
winitwindow,
ws,
display,
*options,
)),
egui: None,
shader: options.shader,
_phantom: std::marker::PhantomData,
});
}
}
panic!("No window created");
}
pub fn is_event_for_window(&self, event: &winit::event::Event<U>) -> bool {
match (event, &self.gl_window) {
(Event::UserEvent(ev), IndeterminateWindowedContext::PossiblyCurrent(gl_window)) => {
if let Some(wid) = ev.window_id() {
wid == gl_window.window.id()
} else {
false
}
}
(Event::UserEvent(ev), IndeterminateWindowedContext::NotCurrent(gl_window)) => {
if let Some(wid) = ev.window_id() {
wid == gl_window.window.id()
} else {
false
}
}
(
Event::WindowEvent {
window_id: id,
event: _,
..
},
IndeterminateWindowedContext::PossiblyCurrent(gl_window),
) => gl_window.window.id() == *id,
(
Event::WindowEvent {
window_id: id,
event: _,
..
},
IndeterminateWindowedContext::NotCurrent(gl_window),
) => gl_window.window.id() == *id,
_ => true, }
}
pub fn handle_event_outer(
&mut self,
c: &mut T,
event: &winit::event::Event<U>,
el: &EventLoopWindowTarget<U>,
root_window_exists: bool,
fontmap: &HashMap<String, egui::FontData>,
clipboard: &mut arboard::Clipboard,
) -> TrackedWindowControl<T, U> {
let gl_window = mem::replace(&mut self.gl_window, IndeterminateWindowedContext::None);
let mut gl_window = match gl_window {
IndeterminateWindowedContext::PossiblyCurrent(w) => {
let _e = w.make_current();
w
}
IndeterminateWindowedContext::NotCurrent(w) => w.make_current().unwrap(),
IndeterminateWindowedContext::None => panic!("there's no window context???"),
};
match self.egui.as_ref() {
None => {
let gl = Arc::new(unsafe {
glow::Context::from_loader_function(|s| gl_window.get_proc_address(s))
});
unsafe {
use glow::HasContext as _;
gl.enable(glow::FRAMEBUFFER_SRGB);
}
let egui = egui_glow::EguiGlow::new(el, gl, self.shader);
{
let mut fonts = egui::FontDefinitions::default();
for (name, font) in fontmap {
fonts.font_data.insert(name.clone(), font.clone());
fonts.families.insert(
egui::FontFamily::Name(name.to_owned().into()),
vec![name.to_owned()],
);
}
egui.egui_ctx.set_fonts(fonts)
}
self.egui = Some(egui);
}
Some(_) => (),
};
let result = match self.egui.as_mut() {
Some(egui) => {
let result = handle_event(
&mut *self.window,
event,
c,
egui,
root_window_exists,
&mut gl_window,
clipboard,
);
if let ControlFlow::Exit = result.requested_control_flow {
if self.window.can_quit(c) {
egui.destroy();
}
};
result
}
_ => {
panic!("Window wasn't fully initialized");
}
};
match mem::replace(
&mut self.gl_window,
IndeterminateWindowedContext::PossiblyCurrent(gl_window),
) {
IndeterminateWindowedContext::None => (),
_ => {
panic!("Window had a GL context while we were borrowing it?");
}
}
result
}
}
pub enum IndeterminateWindowedContext {
PossiblyCurrent(ContextHolder<PossiblyCurrentContext>),
NotCurrent(ContextHolder<NotCurrentContext>),
None,
}
pub struct TrackedWindowControl<T, U = DefaultCustomEvent> {
pub requested_control_flow: ControlFlow,
pub windows_to_create: Vec<NewWindowRequest<T, U>>,
}
#[derive(Error, Debug)]
pub enum DisplayCreationError {}