use std::{
sync::{
Arc,
atomic::{AtomicBool, Ordering},
},
time::Duration,
};
#[cfg(glow)]
use egui_glow::glow;
#[cfg(wgpu)]
use egui_wgpu::wgpu;
use egui_winit::winit::{
self,
application::ApplicationHandler,
event::{StartCause, WindowEvent},
event_loop::{ActiveEventLoop, EventLoopProxy},
};
#[cfg(glow)]
pub(crate) mod _glow;
#[cfg(wgpu)]
pub(crate) mod _wgpu;
#[derive(Debug, Clone, Copy)]
pub enum Renderer {
Glow,
Wgpu,
}
pub struct Frame {
handle: Handle,
#[cfg(glow)]
gl: Option<Arc<glow::Context>>,
#[cfg(wgpu)]
render_state: Option<egui_wgpu::RenderState>,
window: Arc<winit::window::Window>,
}
impl Frame {
#[cfg(glow)]
pub fn gl(&self) -> Option<&Arc<glow::Context>> {
self.gl.as_ref()
}
#[cfg(wgpu)]
pub fn render_state(&self) -> Option<&egui_wgpu::RenderState> {
self.render_state.as_ref()
}
#[cfg(wgpu)]
pub fn device(&self) -> Option<&wgpu::Device> {
self.render_state.as_ref().map(|x| &x.device)
}
#[cfg(wgpu)]
pub fn queue(&self) -> Option<&wgpu::Queue> {
self.render_state.as_ref().map(|x| &x.queue)
}
pub fn winit_window(&self) -> &Arc<winit::window::Window> {
&self.window
}
pub fn handle(&self) -> Handle {
self.handle.clone()
}
}
impl std::ops::Deref for Frame {
type Target = Handle;
fn deref(&self) -> &Self::Target {
&self.handle
}
}
impl std::ops::DerefMut for Frame {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.handle
}
}
#[derive(Debug, Clone, Default)]
pub struct Handle {
proxy: Arc<std::sync::OnceLock<EventLoopProxy<UserEvent>>>,
visible: Arc<AtomicBool>,
}
impl Handle {
pub(crate) fn new() -> Self {
Self::default()
}
pub(crate) fn init(&self, proxy: EventLoopProxy<UserEvent>) {
self.proxy.set(proxy).unwrap();
tracing::debug!("application handle initialized");
}
pub fn request_repaint(&self) {
if let Some(proxy) = self.proxy.get() {
_ = proxy.send_event(UserEvent::MessageReady);
}
}
#[cfg(not(android))]
pub fn hide(&self) {
if let Some(proxy) = self.proxy.get() {
_ = proxy.send_event(UserEvent::Hide);
}
}
#[cfg(not(android))]
pub fn show(&self) {
if let Some(proxy) = self.proxy.get() {
_ = proxy.send_event(UserEvent::Show);
}
}
pub fn is_visible(&self) -> bool {
self.visible.load(Ordering::Relaxed)
}
pub fn exit(&self) {
if let Some(proxy) = self.proxy.get() {
_ = proxy.send_event(UserEvent::Exit);
}
}
}
#[allow(unused)]
#[derive(Debug)]
pub(crate) enum UserEvent {
Show,
Hide,
Exit,
RequestRepaint(Duration),
MessageReady,
#[cfg(feature = "accesskit")]
AccessKit(egui_winit::accesskit_winit::Event),
}
#[cfg(feature = "accesskit")]
impl From<egui_winit::accesskit_winit::Event> for UserEvent {
fn from(value: egui_winit::accesskit_winit::Event) -> Self {
Self::AccessKit(value)
}
}
pub(crate) trait Runner {
fn has_window(&self) -> bool;
fn ensure_window(&mut self, event_loop: &ActiveEventLoop);
fn destroy_window(&mut self);
fn update(&mut self);
fn request_redraw(&self);
fn set_repaint_delay(&mut self, delay: Duration);
fn window_event(&mut self, event_loop: &ActiveEventLoop, window_id: winit::window::WindowId, event: WindowEvent);
fn shutdown_root(&mut self);
#[cfg(feature = "accesskit")]
fn access_kit_event(&mut self, event: egui_winit::accesskit_winit::Event);
}
impl ApplicationHandler<UserEvent> for dyn Runner {
fn resumed(&mut self, event_loop: &ActiveEventLoop) {
tracing::info!("application resumed");
if !self.has_window() {
self.ensure_window(event_loop);
}
}
fn suspended(&mut self, _event_loop: &ActiveEventLoop) {
tracing::info!("application suspended");
self.destroy_window();
}
fn new_events(&mut self, _event_loop: &ActiveEventLoop, cause: StartCause) {
if matches!(cause, StartCause::ResumeTimeReached { .. }) {
self.request_redraw();
}
}
fn user_event(&mut self, event_loop: &ActiveEventLoop, event: UserEvent) {
tracing::trace!(?event, "processing user event");
match event {
UserEvent::Show => self.ensure_window(event_loop),
UserEvent::Hide => self.destroy_window(),
UserEvent::Exit => event_loop.exit(),
UserEvent::RequestRepaint(delay) => self.set_repaint_delay(delay),
UserEvent::MessageReady => self.update(),
#[cfg(feature = "accesskit")]
UserEvent::AccessKit(event) => self.access_kit_event(event),
}
}
fn window_event(&mut self, event_loop: &ActiveEventLoop, window_id: winit::window::WindowId, event: WindowEvent) {
Runner::window_event(self, event_loop, window_id, event);
}
fn exiting(&mut self, _event_loop: &ActiveEventLoop) {
tracing::info!("application event loop exiting");
self.shutdown_root();
self.destroy_window();
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn default_handle_is_hidden() {
assert!(!Handle::default().is_visible());
}
#[test]
fn cloned_handles_share_visibility_state() {
let handle = Handle::default();
let clone = handle.clone();
handle.visible.store(true, Ordering::Relaxed);
assert!(clone.is_visible());
}
#[test]
fn uninitialized_handle_control_methods_are_no_ops() {
let handle = Handle::default();
handle.request_repaint();
handle.exit();
#[cfg(not(target_os = "android"))]
{
handle.show();
handle.hide();
}
assert!(!handle.is_visible());
}
#[test]
fn renderer_is_copyable_and_debuggable() {
let renderer = Renderer::Glow;
let copy = renderer;
assert_eq!(format!("{renderer:?}"), "Glow");
assert_eq!(format!("{copy:?}"), "Glow");
}
}