#![warn(missing_docs)]
#[cfg(target_arch = "wasm32")]
use std::any::Any;
#[cfg(not(target_arch = "wasm32"))]
#[cfg(any(feature = "glow", feature = "wgpu_no_default_features"))]
pub use crate::native::winit_integration::UserEvent;
#[cfg(not(target_arch = "wasm32"))]
use raw_window_handle::{
DisplayHandle, HandleError, HasDisplayHandle, HasWindowHandle, RawDisplayHandle,
RawWindowHandle, WindowHandle,
};
#[cfg(not(target_arch = "wasm32"))]
use static_assertions::assert_not_impl_any;
#[cfg(not(target_arch = "wasm32"))]
#[cfg(any(feature = "glow", feature = "wgpu_no_default_features"))]
pub use winit::{event_loop::EventLoopBuilder, window::WindowAttributes};
#[cfg(not(target_arch = "wasm32"))]
#[cfg(any(feature = "glow", feature = "wgpu_no_default_features"))]
pub type EventLoopBuilderHook = Box<dyn FnOnce(&mut EventLoopBuilder<UserEvent>)>;
#[cfg(not(target_arch = "wasm32"))]
#[cfg(any(feature = "glow", feature = "wgpu_no_default_features"))]
pub type WindowBuilderHook = Box<dyn FnOnce(egui::ViewportBuilder) -> egui::ViewportBuilder>;
type DynError = Box<dyn std::error::Error + Send + Sync>;
pub type AppCreator<'app> =
Box<dyn 'app + FnOnce(&CreationContext<'_>) -> Result<Box<dyn 'app + App>, DynError>>;
pub struct CreationContext<'s> {
pub egui_ctx: egui::Context,
pub integration_info: IntegrationInfo,
pub storage: Option<&'s dyn Storage>,
#[cfg(feature = "glow")]
pub gl: Option<std::sync::Arc<glow::Context>>,
#[cfg(feature = "glow")]
pub get_proc_address:
Option<std::sync::Arc<dyn Fn(&std::ffi::CStr) -> *const std::ffi::c_void + Send + Sync>>,
#[cfg(feature = "wgpu_no_default_features")]
pub wgpu_render_state: Option<egui_wgpu::RenderState>,
#[cfg(not(target_arch = "wasm32"))]
pub(crate) raw_window_handle: Result<RawWindowHandle, HandleError>,
#[cfg(not(target_arch = "wasm32"))]
pub(crate) raw_display_handle: Result<RawDisplayHandle, HandleError>,
}
#[expect(unsafe_code)]
#[cfg(not(target_arch = "wasm32"))]
impl HasWindowHandle for CreationContext<'_> {
fn window_handle(&self) -> Result<WindowHandle<'_>, HandleError> {
unsafe { Ok(WindowHandle::borrow_raw(self.raw_window_handle.clone()?)) }
}
}
#[expect(unsafe_code)]
#[cfg(not(target_arch = "wasm32"))]
impl HasDisplayHandle for CreationContext<'_> {
fn display_handle(&self) -> Result<DisplayHandle<'_>, HandleError> {
unsafe { Ok(DisplayHandle::borrow_raw(self.raw_display_handle.clone()?)) }
}
}
impl CreationContext<'_> {
#[doc(hidden)]
pub fn _new_kittest(egui_ctx: egui::Context) -> Self {
Self {
egui_ctx,
integration_info: IntegrationInfo::mock(),
storage: None,
#[cfg(feature = "glow")]
gl: None,
#[cfg(feature = "glow")]
get_proc_address: None,
#[cfg(feature = "wgpu_no_default_features")]
wgpu_render_state: None,
#[cfg(not(target_arch = "wasm32"))]
raw_window_handle: Err(HandleError::NotSupported),
#[cfg(not(target_arch = "wasm32"))]
raw_display_handle: Err(HandleError::NotSupported),
}
}
}
pub trait App {
fn logic(&mut self, ctx: &egui::Context, frame: &mut Frame) {
_ = (ctx, frame);
}
fn ui(&mut self, ui: &mut egui::Ui, frame: &mut Frame);
#[deprecated = "Use Self::ui instead"]
fn update(&mut self, ctx: &egui::Context, frame: &mut Frame) {
_ = (ctx, frame);
}
#[cfg(target_arch = "wasm32")]
fn as_any_mut(&mut self) -> Option<&mut dyn Any> {
None
}
fn save(&mut self, _storage: &mut dyn Storage) {}
#[cfg(feature = "glow")]
fn on_exit(&mut self, _gl: Option<&glow::Context>) {}
#[cfg(not(feature = "glow"))]
fn on_exit(&mut self) {}
fn auto_save_interval(&self) -> std::time::Duration {
std::time::Duration::from_secs(30)
}
fn clear_color(&self, _visuals: &egui::Visuals) -> [f32; 4] {
egui::Color32::from_rgba_unmultiplied(12, 12, 12, 180).to_normalized_gamma_f32()
}
fn persist_egui_memory(&self) -> bool {
true
}
fn raw_input_hook(&mut self, _ctx: &egui::Context, _raw_input: &mut egui::RawInput) {}
}
#[cfg(not(target_arch = "wasm32"))]
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum HardwareAcceleration {
Required,
Preferred,
Off,
}
#[cfg(not(target_arch = "wasm32"))]
pub struct NativeOptions {
pub viewport: egui::ViewportBuilder,
pub vsync: bool,
pub multisampling: u16,
pub depth_buffer: u8,
pub stencil_buffer: u8,
pub hardware_acceleration: HardwareAcceleration,
#[cfg(any(feature = "glow", feature = "wgpu_no_default_features"))]
pub renderer: Renderer,
pub run_and_return: bool,
#[cfg(any(feature = "glow", feature = "wgpu_no_default_features"))]
pub event_loop_builder: Option<EventLoopBuilderHook>,
#[cfg(any(feature = "glow", feature = "wgpu_no_default_features"))]
pub window_builder: Option<WindowBuilderHook>,
#[cfg(feature = "glow")]
pub shader_version: Option<egui_glow::ShaderVersion>,
pub centered: bool,
#[cfg(feature = "wgpu_no_default_features")]
pub wgpu_options: egui_wgpu::WgpuConfiguration,
pub persist_window: bool,
pub persistence_path: Option<std::path::PathBuf>,
pub dithering: bool,
#[cfg(target_os = "android")]
pub android_app: Option<winit::platform::android::activity::AndroidApp>,
}
#[cfg(not(target_arch = "wasm32"))]
impl Clone for NativeOptions {
fn clone(&self) -> Self {
Self {
viewport: self.viewport.clone(),
#[cfg(any(feature = "glow", feature = "wgpu_no_default_features"))]
event_loop_builder: None,
#[cfg(any(feature = "glow", feature = "wgpu_no_default_features"))]
window_builder: None,
#[cfg(feature = "wgpu_no_default_features")]
wgpu_options: self.wgpu_options.clone(),
persistence_path: self.persistence_path.clone(),
#[cfg(target_os = "android")]
android_app: self.android_app.clone(),
..*self
}
}
}
#[cfg(not(target_arch = "wasm32"))]
impl Default for NativeOptions {
fn default() -> Self {
Self {
viewport: Default::default(),
vsync: true,
multisampling: 0,
depth_buffer: 0,
stencil_buffer: 0,
hardware_acceleration: HardwareAcceleration::Preferred,
#[cfg(any(feature = "glow", feature = "wgpu_no_default_features"))]
renderer: Renderer::default(),
run_and_return: true,
#[cfg(any(feature = "glow", feature = "wgpu_no_default_features"))]
event_loop_builder: None,
#[cfg(any(feature = "glow", feature = "wgpu_no_default_features"))]
window_builder: None,
#[cfg(feature = "glow")]
shader_version: None,
centered: false,
#[cfg(feature = "wgpu_no_default_features")]
wgpu_options: egui_wgpu::WgpuConfiguration::default(),
persist_window: true,
persistence_path: None,
dithering: true,
#[cfg(target_os = "android")]
android_app: None,
}
}
}
#[cfg(target_arch = "wasm32")]
pub struct WebOptions {
#[cfg(any(feature = "glow", feature = "wgpu_no_default_features"))]
pub renderer: Renderer,
pub depth_buffer: u8,
#[cfg(feature = "glow")]
pub webgl_context_option: WebGlContextOption,
#[cfg(feature = "wgpu_no_default_features")]
pub wgpu_options: egui_wgpu::WgpuConfiguration,
pub dithering: bool,
pub should_stop_propagation: Box<dyn Fn(&egui::Event) -> bool>,
pub should_prevent_default: Box<dyn Fn(&egui::Event) -> bool>,
pub max_fps: Option<u32>,
}
#[cfg(target_arch = "wasm32")]
impl Default for WebOptions {
fn default() -> Self {
Self {
#[cfg(any(feature = "glow", feature = "wgpu_no_default_features"))]
renderer: Renderer::default(),
depth_buffer: 0,
#[cfg(feature = "glow")]
webgl_context_option: WebGlContextOption::BestFirst,
#[cfg(feature = "wgpu_no_default_features")]
wgpu_options: egui_wgpu::WgpuConfiguration::default(),
dithering: true,
should_stop_propagation: Box::new(|_| true),
should_prevent_default: Box::new(|_| true),
max_fps: None,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
pub enum WebGlContextOption {
WebGl1,
WebGl2,
BestFirst,
CompatibilityFirst,
}
#[cfg(any(feature = "glow", feature = "wgpu_no_default_features"))]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
pub enum Renderer {
#[cfg(feature = "glow")]
Glow,
#[cfg(feature = "wgpu_no_default_features")]
Wgpu,
}
#[cfg(any(feature = "glow", feature = "wgpu_no_default_features"))]
impl Default for Renderer {
fn default() -> Self {
#[cfg(not(feature = "glow"))]
#[cfg(not(feature = "wgpu_no_default_features"))]
compile_error!(
"eframe: you must enable at least one of the rendering backend features: 'glow' or 'wgpu'"
);
#[cfg(feature = "glow")]
#[cfg(not(feature = "wgpu_no_default_features"))]
return Self::Glow;
#[cfg(not(feature = "glow"))]
#[cfg(feature = "wgpu_no_default_features")]
return Self::Wgpu;
#[cfg(feature = "glow")]
#[cfg(feature = "wgpu_no_default_features")]
return Self::Wgpu;
}
}
#[cfg(any(feature = "glow", feature = "wgpu_no_default_features"))]
impl std::fmt::Display for Renderer {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
#[cfg(feature = "glow")]
Self::Glow => "glow".fmt(f),
#[cfg(feature = "wgpu_no_default_features")]
Self::Wgpu => "wgpu".fmt(f),
}
}
}
#[cfg(any(feature = "glow", feature = "wgpu_no_default_features"))]
impl std::str::FromStr for Renderer {
type Err = String;
fn from_str(name: &str) -> Result<Self, String> {
match name.to_lowercase().as_str() {
#[cfg(feature = "glow")]
"glow" => Ok(Self::Glow),
#[cfg(feature = "wgpu_no_default_features")]
"wgpu" => Ok(Self::Wgpu),
_ => Err(format!(
"eframe renderer {name:?} is not available. Make sure that the corresponding eframe feature is enabled."
)),
}
}
}
pub struct Frame {
pub(crate) info: IntegrationInfo,
pub(crate) storage: Option<Box<dyn Storage>>,
#[cfg(feature = "glow")]
pub(crate) gl: Option<std::sync::Arc<glow::Context>>,
#[cfg(all(feature = "glow", not(target_arch = "wasm32")))]
pub(crate) glow_register_native_texture:
Option<Box<dyn FnMut(glow::Texture) -> egui::TextureId>>,
#[cfg(feature = "wgpu_no_default_features")]
#[doc(hidden)]
pub wgpu_render_state: Option<egui_wgpu::RenderState>,
#[cfg(not(target_arch = "wasm32"))]
pub(crate) raw_window_handle: Result<RawWindowHandle, HandleError>,
#[cfg(not(target_arch = "wasm32"))]
pub(crate) raw_display_handle: Result<RawDisplayHandle, HandleError>,
}
#[cfg(not(target_arch = "wasm32"))]
assert_not_impl_any!(Frame: Clone);
#[expect(unsafe_code)]
#[cfg(not(target_arch = "wasm32"))]
impl HasWindowHandle for Frame {
fn window_handle(&self) -> Result<WindowHandle<'_>, HandleError> {
unsafe { Ok(WindowHandle::borrow_raw(self.raw_window_handle.clone()?)) }
}
}
#[expect(unsafe_code)]
#[cfg(not(target_arch = "wasm32"))]
impl HasDisplayHandle for Frame {
fn display_handle(&self) -> Result<DisplayHandle<'_>, HandleError> {
unsafe { Ok(DisplayHandle::borrow_raw(self.raw_display_handle.clone()?)) }
}
}
impl Frame {
#[doc(hidden)]
pub fn _new_kittest() -> Self {
Self {
#[cfg(feature = "glow")]
gl: None,
#[cfg(all(feature = "glow", not(target_arch = "wasm32")))]
glow_register_native_texture: None,
info: IntegrationInfo::mock(),
#[cfg(not(target_arch = "wasm32"))]
raw_display_handle: Err(HandleError::NotSupported),
#[cfg(not(target_arch = "wasm32"))]
raw_window_handle: Err(HandleError::NotSupported),
storage: None,
#[cfg(feature = "wgpu_no_default_features")]
wgpu_render_state: None,
}
}
#[expect(clippy::unused_self)]
pub fn is_web(&self) -> bool {
cfg!(target_arch = "wasm32")
}
pub fn info(&self) -> &IntegrationInfo {
&self.info
}
pub fn storage(&self) -> Option<&dyn Storage> {
self.storage.as_deref()
}
pub fn storage_mut(&mut self) -> Option<&mut (dyn Storage + 'static)> {
self.storage.as_deref_mut()
}
#[cfg(feature = "glow")]
pub fn gl(&self) -> Option<&std::sync::Arc<glow::Context>> {
self.gl.as_ref()
}
#[cfg(all(feature = "glow", not(target_arch = "wasm32")))]
pub fn register_native_glow_texture(&mut self, native: glow::Texture) -> egui::TextureId {
#[expect(clippy::unwrap_used)]
self.glow_register_native_texture.as_mut().unwrap()(native)
}
#[cfg(feature = "wgpu_no_default_features")]
pub fn wgpu_render_state(&self) -> Option<&egui_wgpu::RenderState> {
self.wgpu_render_state.as_ref()
}
}
#[derive(Clone, Debug)]
#[cfg(target_arch = "wasm32")]
pub struct WebInfo {
pub user_agent: String,
pub location: Location,
}
#[cfg(target_arch = "wasm32")]
#[derive(Clone, Debug)]
pub struct Location {
pub url: String,
pub protocol: String,
pub host: String,
pub hostname: String,
pub port: String,
pub hash: String,
pub query: String,
pub query_map: std::collections::BTreeMap<String, Vec<String>>,
pub origin: String,
}
#[derive(Clone, Debug)]
pub struct IntegrationInfo {
#[cfg(target_arch = "wasm32")]
pub web_info: WebInfo,
pub cpu_usage: Option<f32>,
}
impl IntegrationInfo {
fn mock() -> Self {
Self {
#[cfg(target_arch = "wasm32")]
web_info: WebInfo {
user_agent: "kittest".to_owned(),
location: Location {
url: "http://localhost".to_owned(),
protocol: "http:".to_owned(),
host: "localhost".to_owned(),
hostname: "localhost".to_owned(),
port: "80".to_owned(),
hash: String::new(),
query: String::new(),
query_map: Default::default(),
origin: "http://localhost".to_owned(),
},
},
cpu_usage: None,
}
}
}
pub trait Storage {
fn get_string(&self, key: &str) -> Option<String>;
fn set_string(&mut self, key: &str, value: String);
fn flush(&mut self);
}
#[cfg(feature = "ron")]
pub fn get_value<T: serde::de::DeserializeOwned>(storage: &dyn Storage, key: &str) -> Option<T> {
profiling::function_scope!(key);
let value = storage.get_string(key)?;
match ron::from_str(&value) {
Ok(value) => Some(value),
Err(err) => {
log::debug!("Failed to decode RON: {err}");
None
}
}
}
#[cfg(feature = "ron")]
pub fn set_value<T: serde::Serialize>(storage: &mut dyn Storage, key: &str, value: &T) {
profiling::function_scope!(key);
match ron::ser::to_string(value) {
Ok(string) => storage.set_string(key, string),
Err(err) => log::error!("eframe failed to encode data using ron: {err}"),
}
}
pub const APP_KEY: &str = "app";