eframe app creation refactor (#1363)

* Change how eframe apps are created
* eframe: re-export epi::* so users don't need to care about what epi is
This commit is contained in:
Emil Ernerfeldt 2022-03-16 15:39:48 +01:00 committed by GitHub
parent c768d1d48e
commit c8f6cae362
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
26 changed files with 387 additions and 444 deletions

View file

@ -1,6 +1,11 @@
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release
use eframe::{egui, epi};
use eframe::egui;
fn main() {
let options = eframe::NativeOptions::default();
eframe::run_native("Confirm exit", options, |_cc| Box::new(MyApp::default()));
}
#[derive(Default)]
struct MyApp {
@ -8,17 +13,13 @@ struct MyApp {
is_exiting: bool,
}
impl epi::App for MyApp {
fn name(&self) -> &str {
"Confirm exit"
}
impl eframe::App for MyApp {
fn on_exit_event(&mut self) -> bool {
self.is_exiting = true;
self.can_exit
}
fn update(&mut self, ctx: &egui::Context, frame: &epi::Frame) {
fn update(&mut self, ctx: &egui::Context, frame: &eframe::Frame) {
egui::CentralPanel::default().show(ctx, |ui| {
ui.heading("Try to close the window");
});
@ -42,8 +43,3 @@ impl epi::App for MyApp {
}
}
}
fn main() {
let options = eframe::NativeOptions::default();
eframe::run_native(Box::new(MyApp::default()), options);
}

View file

@ -8,25 +8,42 @@
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release
use eframe::{egui, epi};
use eframe::egui;
use parking_lot::Mutex;
use std::sync::Arc;
#[derive(Default)]
fn main() {
let options = eframe::NativeOptions::default();
eframe::run_native("Custom 3D painting in eframe", options, |cc| {
Box::new(MyApp::new(cc))
});
}
struct MyApp {
rotating_triangle: Arc<Mutex<Option<RotatingTriangle>>>,
/// Behind an `Arc<Mutex<…>>` so we can pass it to [`egui::PaintCallback`] and paint later.
rotating_triangle: Arc<Mutex<RotatingTriangle>>,
angle: f32,
}
impl epi::App for MyApp {
fn name(&self) -> &str {
"Custom 3D painting inside an egui window"
impl MyApp {
fn new(cc: &eframe::CreationContext<'_>) -> Self {
Self {
rotating_triangle: Arc::new(Mutex::new(RotatingTriangle::new(&cc.gl))),
angle: 0.0,
}
}
}
fn update(&mut self, ctx: &egui::Context, _frame: &epi::Frame) {
impl eframe::App for MyApp {
fn update(&mut self, ctx: &egui::Context, _frame: &eframe::Frame) {
egui::CentralPanel::default().show(ctx, |ui| {
ui.heading("Here is some 3D stuff:");
ui.horizontal(|ui| {
ui.spacing_mut().item_spacing.x = 0.0;
ui.label("The triangle is being painted using ");
ui.hyperlink_to("glow", "https://github.com/grovesNL/glow");
ui.label(" (OpenGL).");
});
egui::ScrollArea::both().show(ui, |ui| {
egui::Frame::dark_canvas(ui.style()).show(ui, |ui| {
@ -35,14 +52,10 @@ impl epi::App for MyApp {
ui.label("Drag to rotate!");
});
});
}
let mut frame = egui::Frame::window(&*ctx.style());
frame.fill = frame.fill.linear_multiply(0.5); // transparent
egui::Window::new("3D stuff in a window")
.frame(frame)
.show(ctx, |ui| {
self.custom_painting(ui);
});
fn on_exit(&mut self, gl: &glow::Context) {
self.rotating_triangle.lock().destroy(gl)
}
}
@ -53,17 +66,15 @@ impl MyApp {
self.angle += response.drag_delta().x * 0.01;
// Clone locals so we can move them into the paint callback:
let angle = self.angle;
let rotating_triangle = self.rotating_triangle.clone();
let callback = egui::epaint::PaintCallback {
let callback = egui::PaintCallback {
rect,
callback: std::sync::Arc::new(move |render_ctx| {
if let Some(painter) = render_ctx.downcast_ref::<egui_glow::Painter>() {
let mut rotating_triangle = rotating_triangle.lock();
let rotating_triangle = rotating_triangle
.get_or_insert_with(|| RotatingTriangle::new(painter.gl()));
rotating_triangle.paint(painter.gl(), angle);
rotating_triangle.lock().paint(painter.gl(), angle);
} else {
eprintln!("Can't do custom painting because we are not using a glow context");
}
@ -163,9 +174,7 @@ impl RotatingTriangle {
}
}
// TODO: figure out how to call this in a nice way
#[allow(unused)]
fn destroy(self, gl: &glow::Context) {
fn destroy(&self, gl: &glow::Context) {
use glow::HasContext as _;
unsafe {
gl.delete_program(self.program);
@ -186,8 +195,3 @@ impl RotatingTriangle {
}
}
}
fn main() {
let options = eframe::NativeOptions::default();
eframe::run_native(Box::new(MyApp::default()), options);
}

View file

@ -1,66 +1,61 @@
use eframe::{egui, epi};
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release
use eframe::egui;
fn main() {
let options = eframe::NativeOptions::default();
eframe::run_native("egui example: custom font", options, |cc| {
Box::new(MyApp::new(cc))
});
}
fn setup_custom_fonts(ctx: &egui::Context) {
// Start with the default fonts (we will be adding to them rather than replacing them).
let mut fonts = egui::FontDefinitions::default();
// Install my own font (maybe supporting non-latin characters).
// .ttf and .otf files supported.
fonts.font_data.insert(
"my_font".to_owned(),
egui::FontData::from_static(include_bytes!("../../epaint/fonts/Hack-Regular.ttf")),
);
// Put my font first (highest priority) for proportional text:
fonts
.families
.entry(egui::FontFamily::Proportional)
.or_default()
.insert(0, "my_font".to_owned());
// Put my font as last fallback for monospace:
fonts
.families
.entry(egui::FontFamily::Monospace)
.or_default()
.push("my_font".to_owned());
// Tell egui to use these fonts:
ctx.set_fonts(fonts);
}
struct MyApp {
text: String,
}
impl Default for MyApp {
fn default() -> Self {
impl MyApp {
fn new(cc: &eframe::CreationContext<'_>) -> Self {
setup_custom_fonts(&cc.egui_ctx);
Self {
text: "Edit this text field if you want".to_owned(),
}
}
}
impl epi::App for MyApp {
fn name(&self) -> &str {
"egui example: custom font"
}
fn setup(
&mut self,
ctx: &egui::Context,
_frame: &epi::Frame,
_storage: Option<&dyn epi::Storage>,
_gl: &std::rc::Rc<epi::glow::Context>,
) {
// Start with the default fonts (we will be adding to them rather than replacing them).
let mut fonts = egui::FontDefinitions::default();
// Install my own font (maybe supporting non-latin characters).
// .ttf and .otf files supported.
fonts.font_data.insert(
"my_font".to_owned(),
egui::FontData::from_static(include_bytes!("../../epaint/fonts/Hack-Regular.ttf")),
);
// Put my font first (highest priority) for proportional text:
fonts
.families
.entry(egui::FontFamily::Proportional)
.or_default()
.insert(0, "my_font".to_owned());
// Put my font as last fallback for monospace:
fonts
.families
.entry(egui::FontFamily::Monospace)
.or_default()
.push("my_font".to_owned());
// Tell egui to use these fonts:
ctx.set_fonts(fonts);
}
fn update(&mut self, ctx: &egui::Context, _frame: &epi::Frame) {
impl eframe::App for MyApp {
fn update(&mut self, ctx: &egui::Context, _frame: &eframe::Frame) {
egui::CentralPanel::default().show(ctx, |ui| {
ui.heading("egui using custom fonts");
ui.text_edit_multiline(&mut self.text);
});
}
}
fn main() {
let options = eframe::NativeOptions::default();
eframe::run_native(Box::new(MyApp::default()), options);
}

View file

@ -1,12 +1,16 @@
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release
use eframe::{egui, epi};
use eframe::egui;
use egui_extras::RetainedImage;
use poll_promise::Promise;
fn main() {
let options = eframe::NativeOptions::default();
eframe::run_native(Box::new(MyApp::default()), options);
eframe::run_native(
"Download and show an image with eframe/egui",
options,
|_cc| Box::new(MyApp::default()),
);
}
#[derive(Default)]
@ -15,12 +19,8 @@ struct MyApp {
promise: Option<Promise<ehttp::Result<RetainedImage>>>,
}
impl epi::App for MyApp {
fn name(&self) -> &str {
"Download and show an image with eframe/egui"
}
fn update(&mut self, ctx: &egui::Context, _frame: &epi::Frame) {
impl eframe::App for MyApp {
fn update(&mut self, ctx: &egui::Context, _frame: &eframe::Frame) {
let promise = self.promise.get_or_insert_with(|| {
// Begin download.
// We download the image using `ehttp`, a library that works both in WASM and on native.

View file

@ -1,6 +1,18 @@
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release
use eframe::{egui, epi};
use eframe::egui;
fn main() {
let options = eframe::NativeOptions {
drag_and_drop_support: true,
..Default::default()
};
eframe::run_native(
"Native file dialogs and drag-and-drop files",
options,
|_cc| Box::new(MyApp::default()),
);
}
#[derive(Default)]
struct MyApp {
@ -8,12 +20,8 @@ struct MyApp {
picked_path: Option<String>,
}
impl epi::App for MyApp {
fn name(&self) -> &str {
"Native file dialogs and drag-and-drop files"
}
fn update(&mut self, ctx: &egui::Context, _frame: &epi::Frame) {
impl eframe::App for MyApp {
fn update(&mut self, ctx: &egui::Context, _frame: &eframe::Frame) {
egui::CentralPanel::default().show(ctx, |ui| {
ui.label("Drag-and-drop files onto the window!");
@ -93,11 +101,3 @@ impl MyApp {
}
}
}
fn main() {
let options = eframe::NativeOptions {
drag_and_drop_support: true,
..Default::default()
};
eframe::run_native(Box::new(MyApp::default()), options);
}

View file

@ -1,6 +1,11 @@
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release
use eframe::{egui, epi};
use eframe::egui;
fn main() {
let options = eframe::NativeOptions::default();
eframe::run_native("My egui App", options, |_cc| Box::new(MyApp::default()));
}
struct MyApp {
name: String,
@ -16,12 +21,8 @@ impl Default for MyApp {
}
}
impl epi::App for MyApp {
fn name(&self) -> &str {
"My egui App"
}
fn update(&mut self, ctx: &egui::Context, frame: &epi::Frame) {
impl eframe::App for MyApp {
fn update(&mut self, ctx: &egui::Context, frame: &eframe::Frame) {
egui::CentralPanel::default().show(ctx, |ui| {
ui.heading("My egui Application");
ui.horizontal(|ui| {
@ -39,8 +40,3 @@ impl epi::App for MyApp {
frame.set_window_size(ctx.used_size());
}
}
fn main() {
let options = eframe::NativeOptions::default();
eframe::run_native(Box::new(MyApp::default()), options);
}

View file

@ -1,8 +1,15 @@
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release
use eframe::{egui, epi};
use eframe::egui;
use egui_extras::RetainedImage;
fn main() {
let options = eframe::NativeOptions::default();
eframe::run_native("Show an image with eframe/egui", options, |_cc| {
Box::new(MyApp::default())
});
}
struct MyApp {
image: RetainedImage,
}
@ -19,12 +26,8 @@ impl Default for MyApp {
}
}
impl epi::App for MyApp {
fn name(&self) -> &str {
"Show an image with eframe/egui"
}
fn update(&mut self, ctx: &egui::Context, _frame: &epi::Frame) {
impl eframe::App for MyApp {
fn update(&mut self, ctx: &egui::Context, _frame: &eframe::Frame) {
egui::CentralPanel::default().show(ctx, |ui| {
ui.heading("This is an image:");
self.image.show(ui);
@ -37,8 +40,3 @@ impl epi::App for MyApp {
});
}
}
fn main() {
let options = eframe::NativeOptions::default();
eframe::run_native(Box::new(MyApp::default()), options);
}

View file

@ -4,7 +4,15 @@
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release
use eframe::{egui, epi};
use eframe::egui;
fn main() {
let options = eframe::NativeOptions {
initial_window_size: Some(egui::vec2(1000.0, 700.0)),
..Default::default()
};
eframe::run_native("svg example", options, |_cc| Box::new(MyApp::default()));
}
struct MyApp {
svg_image: egui_extras::RetainedImage,
@ -22,12 +30,8 @@ impl Default for MyApp {
}
}
impl epi::App for MyApp {
fn name(&self) -> &str {
"svg example"
}
fn update(&mut self, ctx: &egui::Context, _frame: &epi::Frame) {
impl eframe::App for MyApp {
fn update(&mut self, ctx: &egui::Context, _frame: &eframe::Frame) {
egui::CentralPanel::default().show(ctx, |ui| {
ui.heading("SVG example");
ui.label("The SVG is rasterized and displayed as a texture.");
@ -39,11 +43,3 @@ impl epi::App for MyApp {
});
}
}
fn main() {
let options = eframe::NativeOptions {
initial_window_size: Some(egui::vec2(1000.0, 700.0)),
..Default::default()
};
eframe::run_native(Box::new(MyApp::default()), options);
}

View file

@ -16,27 +16,32 @@
//!
//! ## Usage, native:
//! ``` no_run
//! use eframe::{epi, egui};
//! use eframe::egui;
//!
//! fn main() {
//! let native_options = eframe::NativeOptions::default();
//! eframe::run_native("My egui App", native_options, |cc| Box::new(MyEguiApp::new(cc)));
//! }
//!
//! #[derive(Default)]
//! struct MyEguiApp {}
//!
//! impl epi::App for MyEguiApp {
//! fn name(&self) -> &str {
//! "My egui App"
//! }
//! impl MyEguiApp {
//! fn new(cc: &eframe::CreationContext<'_>) -> Self {
//! // Customize egui here with cc.egui_ctx.set_fonts and cc.egui_ctx.set_visuals.
//! // Restore app state using cc.storage (requires the "persistence" feature).
//! // Use the cc.gl (a glow::Context) to create graphics shaders and buffers that you can use
//! // for e.g. egui::PaintCallback.
//! Self::default()
//! }
//! }
//!
//! fn update(&mut self, ctx: &egui::Context, frame: &epi::Frame) {
//! impl eframe::App for MyEguiApp {
//! fn update(&mut self, ctx: &egui::Context, frame: &eframe::Frame) {
//! egui::CentralPanel::default().show(ctx, |ui| {
//! ui.heading("Hello World!");
//! });
//! }
//!}
//!
//! fn main() {
//! let app = MyEguiApp::default();
//! let native_options = eframe::NativeOptions::default();
//! eframe::run_native(Box::new(app), native_options);
//! }
//! ```
//!
@ -49,8 +54,7 @@
//! #[cfg(target_arch = "wasm32")]
//! #[wasm_bindgen]
//! pub fn start(canvas_id: &str) -> Result<(), eframe::wasm_bindgen::JsValue> {
//! let app = MyEguiApp::default();
//! eframe::start_web(canvas_id, Box::new(app))
//! eframe::start_web(canvas_id, |cc| Box::new(MyApp::new(cc)))
//! }
//! ```
@ -65,10 +69,11 @@
)]
#![allow(clippy::needless_doctest_main)]
// Re-export all useful libraries:
pub use {egui, egui::emath, egui::epaint, epi};
#[cfg(not(target_arch = "wasm32"))]
pub use epi::NativeOptions;
// Re-export everything in `epi` so `eframe` users don't have to care about what `epi` is:
pub use epi::*;
// ----------------------------------------------------------------------------
// When compiling for web
@ -83,16 +88,6 @@ pub use egui_web::wasm_bindgen;
/// fill the whole width of the browser.
/// This can be changed by overriding [`epi::Frame::max_size_points`].
///
/// ### Usage, native:
/// ``` no_run
/// fn main() {
/// let app = MyEguiApp::default();
/// let native_options = eframe::NativeOptions::default();
/// eframe::run_native(Box::new(app), native_options);
/// }
/// ```
///
/// ### Web
/// ``` no_run
/// #[cfg(target_arch = "wasm32")]
/// use wasm_bindgen::prelude::*;
@ -104,45 +99,54 @@ pub use egui_web::wasm_bindgen;
/// #[cfg(target_arch = "wasm32")]
/// #[wasm_bindgen]
/// pub fn start(canvas_id: &str) -> Result<(), eframe::wasm_bindgen::JsValue> {
/// let app = MyEguiApp::default();
/// eframe::start_web(canvas_id, Box::new(app))
/// eframe::start_web(canvas_id, |cc| Box::new(MyEguiApp::new(cc)))
/// }
/// ```
#[cfg(target_arch = "wasm32")]
pub fn start_web(canvas_id: &str, app: Box<dyn epi::App>) -> Result<(), wasm_bindgen::JsValue> {
egui_web::start(canvas_id, app)?;
pub fn start_web(canvas_id: &str, app_creator: AppCreator) -> Result<(), wasm_bindgen::JsValue> {
egui_web::start(canvas_id, app_creator)?;
Ok(())
}
// ----------------------------------------------------------------------------
// When compiling natively
/// This is how you start a native (desktop) app.
///
/// The first argument is name of your app, used for the title bar of the native window
/// and the save location of persistence (see [`epi::App::save`]).
///
/// Call from `fn main` like this:
/// ``` no_run
/// use eframe::{epi, egui};
/// use eframe::egui;
///
/// fn main() {
/// let native_options = eframe::NativeOptions::default();
/// eframe::run_native("MyApp", native_options, |cc| Box::new(MyEguiApp::new(cc)));
/// }
///
/// #[derive(Default)]
/// struct MyEguiApp {}
///
/// impl epi::App for MyEguiApp {
/// fn name(&self) -> &str {
/// "My egui App"
/// }
/// impl MyEguiApp {
/// fn new(cc: &eframe::CreationContext<'_>) -> Self {
/// // Customize egui here with cc.egui_ctx.set_fonts and cc.egui_ctx.set_visuals.
/// // Restore app state using cc.storage (requires the "persistence" feature).
/// // Use the cc.gl (a glow::Context) to create graphics shaders and buffers that you can use
/// // for e.g. egui::PaintCallback.
/// Self::default()
/// }
/// }
///
/// fn update(&mut self, ctx: &egui::Context, frame: &epi::Frame) {
/// impl eframe::App for MyEguiApp {
/// fn update(&mut self, ctx: &egui::Context, frame: &eframe::Frame) {
/// egui::CentralPanel::default().show(ctx, |ui| {
/// ui.heading("Hello World!");
/// });
/// }
///}
///
/// fn main() {
/// let app = MyEguiApp::default();
/// let native_options = eframe::NativeOptions::default();
/// eframe::run_native(Box::new(app), native_options);
/// }
/// ```
#[cfg(not(target_arch = "wasm32"))]
pub fn run_native(app: Box<dyn epi::App>, native_options: epi::NativeOptions) -> ! {
egui_glow::run(app, &native_options)
pub fn run_native(app_name: &str, native_options: NativeOptions, app_creator: AppCreator) -> ! {
egui_glow::run(app_name, &native_options, app_creator)
}

View file

@ -216,12 +216,11 @@ impl Persistence {
/// Everything needed to make a winit-based integration for [`epi`].
pub struct EpiIntegration {
frame: epi::Frame,
persistence: crate::epi::Persistence,
pub frame: epi::Frame,
pub persistence: crate::epi::Persistence,
pub egui_ctx: egui::Context,
pending_full_output: egui::FullOutput,
egui_winit: crate::State,
pub app: Box<dyn epi::App>,
/// When set, it is time to quit
quit: bool,
can_drag_window: bool,
@ -232,9 +231,7 @@ impl EpiIntegration {
integration_name: &'static str,
max_texture_side: usize,
window: &winit::window::Window,
gl: &std::rc::Rc<glow::Context>,
persistence: crate::epi::Persistence,
app: Box<dyn epi::App>,
) -> Self {
let egui_ctx = egui::Context::default();
@ -259,41 +256,21 @@ impl EpiIntegration {
egui_ctx.set_visuals(egui::Visuals::light());
}
let mut slf = Self {
Self {
frame,
persistence,
egui_ctx,
egui_winit: crate::State::new(max_texture_side, window),
pending_full_output: Default::default(),
app,
quit: false,
can_drag_window: false,
};
slf.setup(window, gl);
if slf.app.warm_up_enabled() {
slf.warm_up(window);
}
slf
}
fn setup(&mut self, window: &winit::window::Window, gl: &std::rc::Rc<glow::Context>) {
self.app
.setup(&self.egui_ctx, &self.frame, self.persistence.storage(), gl);
let app_output = self.frame.take_app_output();
if app_output.quit {
self.quit = self.app.on_exit_event();
}
crate::epi::handle_app_output(window, self.egui_ctx.pixels_per_point(), app_output);
}
fn warm_up(&mut self, window: &winit::window::Window) {
pub fn warm_up(&mut self, app: &mut dyn epi::App, window: &winit::window::Window) {
let saved_memory: egui::Memory = self.egui_ctx.memory().clone();
self.egui_ctx.memory().set_everything_is_visible(true);
let full_output = self.update(window);
let full_output = self.update(app, window);
self.pending_full_output.append(full_output); // Handle it next frame
*self.egui_ctx.memory() = saved_memory; // We don't want to remember that windows were huge.
self.egui_ctx.clear_animations();
@ -304,11 +281,11 @@ impl EpiIntegration {
self.quit
}
pub fn on_event(&mut self, event: &winit::event::WindowEvent<'_>) {
pub fn on_event(&mut self, app: &mut dyn epi::App, event: &winit::event::WindowEvent<'_>) {
use winit::event::{ElementState, MouseButton, WindowEvent};
match event {
WindowEvent::CloseRequested => self.quit = self.app.on_exit_event(),
WindowEvent::CloseRequested => self.quit = app.on_exit_event(),
WindowEvent::Destroyed => self.quit = true,
WindowEvent::MouseInput {
button: MouseButton::Left,
@ -321,12 +298,16 @@ impl EpiIntegration {
self.egui_winit.on_event(&self.egui_ctx, event);
}
pub fn update(&mut self, window: &winit::window::Window) -> egui::FullOutput {
pub fn update(
&mut self,
app: &mut dyn epi::App,
window: &winit::window::Window,
) -> egui::FullOutput {
let frame_start = instant::Instant::now();
let raw_input = self.egui_winit.take_egui_input(window);
let full_output = self.egui_ctx.run(raw_input, |egui_ctx| {
self.app.update(egui_ctx, &self.frame);
app.update(egui_ctx, &self.frame);
});
self.pending_full_output.append(full_output);
let full_output = std::mem::take(&mut self.pending_full_output);
@ -336,7 +317,7 @@ impl EpiIntegration {
app_output.drag_window &= self.can_drag_window; // Necessary on Windows; see https://github.com/emilk/egui/pull/1108
self.can_drag_window = false;
if app_output.quit {
self.quit = self.app.on_exit_event();
self.quit = app.on_exit_event();
}
crate::epi::handle_app_output(window, self.egui_ctx.pixels_per_point(), app_output);
}
@ -356,15 +337,9 @@ impl EpiIntegration {
.handle_platform_output(window, &self.egui_ctx, platform_output);
}
pub fn maybe_autosave(&mut self, window: &winit::window::Window) {
pub fn maybe_autosave(&mut self, app: &mut dyn epi::App, window: &winit::window::Window) {
self.persistence
.maybe_autosave(&mut *self.app, &self.egui_ctx, window);
}
pub fn on_exit(&mut self, window: &winit::window::Window) {
self.app.on_exit();
self.persistence
.save(&mut *self.app, &self.egui_ctx, window);
.maybe_autosave(&mut *app, &self.egui_ctx, window);
}
}

View file

@ -386,8 +386,8 @@ pub use epaint::{
color, mutex,
text::{FontData, FontDefinitions, FontFamily, FontId, FontTweak},
textures::TexturesDelta,
AlphaImage, ClippedPrimitive, Color32, ColorImage, ImageData, Mesh, Rgba, Rounding, Shape,
Stroke, TextureHandle, TextureId,
AlphaImage, ClippedPrimitive, Color32, ColorImage, ImageData, Mesh, PaintCallback, Rgba,
Rounding, Shape, Stroke, TextureHandle, TextureId,
};
pub mod text {

View file

@ -19,6 +19,5 @@ pub fn start(canvas_id: &str) -> Result<(), wasm_bindgen::JsValue> {
// Redirect tracing to console.log and friends:
tracing_wasm::set_as_global_default();
let app = egui_demo_lib::WrapApp::default();
eframe::start_web(canvas_id, Box::new(app))
eframe::start_web(canvas_id, |cc| Box::new(egui_demo_lib::WrapApp::new(cc)))
}

View file

@ -10,12 +10,13 @@ fn main() {
// Log to stdout (if you run with `RUST_LOG=debug`).
tracing_subscriber::fmt::init();
let app = egui_demo_lib::WrapApp::default();
let options = eframe::NativeOptions {
// Let's show off that we support transparent windows
transparent: true,
drag_and_drop_support: true,
..Default::default()
};
eframe::run_native(Box::new(app), options);
eframe::run_native("egui demo app", options, |cc| {
Box::new(egui_demo_lib::WrapApp::new(cc))
});
}

View file

@ -30,10 +30,6 @@ impl Default for ColorTest {
}
impl epi::App for ColorTest {
fn name(&self) -> &str {
"🎨 Color test"
}
fn update(&mut self, ctx: &egui::Context, frame: &epi::Frame) {
egui::CentralPanel::default().show(ctx, |ui| {
if frame.is_web() {

View file

@ -1,7 +1,3 @@
/// Demonstrates how to make an app using egui.
///
/// Implements `epi::App` so it can be used with
/// [`egui_glium`](https://github.com/emilk/egui/tree/master/egui_glium), [`egui_glow`](https://github.com/emilk/egui/tree/master/egui_glow) and [`egui_web`](https://github.com/emilk/egui/tree/master/egui_web).
#[derive(Default)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "serde", serde(default))]
@ -10,28 +6,6 @@ pub struct DemoApp {
}
impl epi::App for DemoApp {
fn name(&self) -> &str {
"✨ Demos"
}
fn setup(
&mut self,
_ctx: &egui::Context,
_frame: &epi::Frame,
_storage: Option<&dyn epi::Storage>,
_gl: &std::rc::Rc<epi::glow::Context>,
) {
#[cfg(feature = "persistence")]
if let Some(storage) = _storage {
*self = epi::get_value(storage, epi::APP_KEY).unwrap_or_default();
}
}
#[cfg(feature = "persistence")]
fn save(&mut self, storage: &mut dyn epi::Storage) {
epi::set_value(storage, epi::APP_KEY, self);
}
fn update(&mut self, ctx: &egui::Context, _frame: &epi::Frame) {
self.demo_windows.ui(ctx);
}

View file

@ -33,10 +33,6 @@ impl Default for FractalClock {
}
impl epi::App for FractalClock {
fn name(&self) -> &str {
"🕑 Fractal Clock"
}
fn update(&mut self, ctx: &egui::Context, _frame: &epi::Frame) {
egui::CentralPanel::default()
.frame(Frame::dark_canvas(&ctx.style()))

View file

@ -54,10 +54,6 @@ impl Default for HttpApp {
}
impl epi::App for HttpApp {
fn name(&self) -> &str {
"⬇ HTTP"
}
fn update(&mut self, ctx: &egui::Context, frame: &epi::Frame) {
egui::TopBottomPanel::bottom("http_bottom").show(ctx, |ui| {
let layout = egui::Layout::top_down(egui::Align::Center).with_main_justify(true);

View file

@ -30,10 +30,6 @@ impl Default for EasyMarkEditor {
}
impl epi::App for EasyMarkEditor {
fn name(&self) -> &str {
"🖹 EasyMark editor"
}
fn update(&mut self, ctx: &egui::Context, _frame: &epi::Frame) {
egui::TopBottomPanel::bottom("easy_mark_bottom").show(ctx, |ui| {
let layout = egui::Layout::top_down(egui::Align::Center).with_main_justify(true);

View file

@ -12,14 +12,26 @@ pub struct Apps {
}
impl Apps {
fn iter_mut(&mut self) -> impl Iterator<Item = (&str, &mut dyn epi::App)> {
fn iter_mut(&mut self) -> impl Iterator<Item = (&str, &str, &mut dyn epi::App)> {
vec![
("demo", &mut self.demo as &mut dyn epi::App),
("easymark", &mut self.easy_mark_editor as &mut dyn epi::App),
("✨ Demos", "demo", &mut self.demo as &mut dyn epi::App),
(
"🖹 EasyMark editor",
"easymark",
&mut self.easy_mark_editor as &mut dyn epi::App,
),
#[cfg(feature = "http")]
("http", &mut self.http as &mut dyn epi::App),
("clock", &mut self.clock as &mut dyn epi::App),
("colors", &mut self.color_test as &mut dyn epi::App),
("⬇ HTTP", "http", &mut self.http as &mut dyn epi::App),
(
"🕑 Fractal Clock",
"clock",
&mut self.clock as &mut dyn epi::App,
),
(
"🎨 Color test",
"colors",
&mut self.color_test as &mut dyn epi::App,
),
]
.into_iter()
}
@ -37,24 +49,17 @@ pub struct WrapApp {
dropped_files: Vec<egui::DroppedFile>,
}
impl epi::App for WrapApp {
fn name(&self) -> &str {
"egui demo apps"
}
fn setup(
&mut self,
_ctx: &egui::Context,
_frame: &epi::Frame,
_storage: Option<&dyn epi::Storage>,
_gl: &std::rc::Rc<epi::glow::Context>,
) {
impl WrapApp {
pub fn new(_cc: &epi::CreationContext<'_>) -> Self {
#[cfg(feature = "persistence")]
if let Some(storage) = _storage {
*self = epi::get_value(storage, epi::APP_KEY).unwrap_or_default();
if let Some(storage) = _cc.storage {
return epi::get_value(storage, epi::APP_KEY).unwrap_or_default();
}
Self::default()
}
}
impl epi::App for WrapApp {
#[cfg(feature = "persistence")]
fn save(&mut self, storage: &mut dyn epi::Storage) {
epi::set_value(storage, epi::APP_KEY, self);
@ -111,7 +116,7 @@ impl epi::App for WrapApp {
let mut found_anchor = false;
for (anchor, app) in self.apps.iter_mut() {
for (_name, anchor, app) in self.apps.iter_mut() {
if anchor == self.selected_anchor || ctx.memory().everything_is_visible() {
app.update(ctx, frame);
found_anchor = true;
@ -138,9 +143,9 @@ impl WrapApp {
ui.checkbox(&mut self.backend_panel.open, "💻 Backend");
ui.separator();
for (anchor, app) in self.apps.iter_mut() {
for (name, anchor, _app) in self.apps.iter_mut() {
if ui
.selectable_label(self.selected_anchor == anchor, app.name())
.selectable_label(self.selected_anchor == anchor, name)
.clicked()
{
self.selected_anchor = anchor.to_owned();

View file

@ -4,41 +4,6 @@
use glium::glutin;
fn create_display(event_loop: &glutin::event_loop::EventLoop<()>) -> glium::Display {
let window_builder = glutin::window::WindowBuilder::new()
.with_resizable(true)
.with_inner_size(glutin::dpi::LogicalSize {
width: 800.0,
height: 600.0,
})
.with_title("egui_glium example");
let context_builder = glutin::ContextBuilder::new()
.with_depth_buffer(0)
.with_srgb(true)
.with_stencil_buffer(0)
.with_vsync(true);
glium::Display::new(window_builder, context_builder, event_loop).unwrap()
}
fn load_glium_image(png_data: &[u8]) -> glium::texture::RawImage2d<u8> {
// Load image using the image crate:
let image = image::load_from_memory(png_data).unwrap().to_rgba8();
let image_dimensions = image.dimensions();
// Premultiply alpha:
let pixels: Vec<_> = image
.into_vec()
.chunks_exact(4)
.map(|p| egui::Color32::from_rgba_unmultiplied(p[0], p[1], p[2], p[3]))
.flat_map(|color| color.to_array())
.collect();
// Convert to glium image:
glium::texture::RawImage2d::from_raw_rgba(pixels, image_dimensions)
}
fn main() {
let event_loop = glutin::event_loop::EventLoop::with_user_event();
let display = create_display(&event_loop);
@ -127,3 +92,38 @@ fn main() {
}
});
}
fn create_display(event_loop: &glutin::event_loop::EventLoop<()>) -> glium::Display {
let window_builder = glutin::window::WindowBuilder::new()
.with_resizable(true)
.with_inner_size(glutin::dpi::LogicalSize {
width: 800.0,
height: 600.0,
})
.with_title("egui_glium example");
let context_builder = glutin::ContextBuilder::new()
.with_depth_buffer(0)
.with_srgb(true)
.with_stencil_buffer(0)
.with_vsync(true);
glium::Display::new(window_builder, context_builder, event_loop).unwrap()
}
fn load_glium_image(png_data: &[u8]) -> glium::texture::RawImage2d<u8> {
// Load image using the image crate:
let image = image::load_from_memory(png_data).unwrap().to_rgba8();
let image_dimensions = image.dimensions();
// Premultiply alpha:
let pixels: Vec<_> = image
.into_vec()
.chunks_exact(4)
.map(|p| egui::Color32::from_rgba_unmultiplied(p[0], p[1], p[2], p[3]))
.flat_map(|color| color.to_array())
.collect();
// Convert to glium image:
glium::texture::RawImage2d::from_raw_rgba(pixels, image_dimensions)
}

View file

@ -4,24 +4,6 @@
use glium::glutin;
fn create_display(event_loop: &glutin::event_loop::EventLoop<()>) -> glium::Display {
let window_builder = glutin::window::WindowBuilder::new()
.with_resizable(true)
.with_inner_size(glutin::dpi::LogicalSize {
width: 800.0,
height: 600.0,
})
.with_title("egui_glium example");
let context_builder = glutin::ContextBuilder::new()
.with_depth_buffer(0)
.with_srgb(true)
.with_stencil_buffer(0)
.with_vsync(true);
glium::Display::new(window_builder, context_builder, event_loop).unwrap()
}
fn main() {
let event_loop = glutin::event_loop::EventLoop::with_user_event();
let display = create_display(&event_loop);
@ -89,3 +71,21 @@ fn main() {
}
});
}
fn create_display(event_loop: &glutin::event_loop::EventLoop<()>) -> glium::Display {
let window_builder = glutin::window::WindowBuilder::new()
.with_resizable(true)
.with_inner_size(glutin::dpi::LogicalSize {
width: 800.0,
height: 600.0,
})
.with_title("egui_glium example");
let context_builder = glutin::ContextBuilder::new()
.with_depth_buffer(0)
.with_srgb(true)
.with_stencil_buffer(0)
.with_vsync(true);
glium::Display::new(window_builder, context_builder, event_loop).unwrap()
}

View file

@ -2,7 +2,6 @@
//!
//! The main type you want to use is [`EguiGlium`].
//!
//! This library is an [`epi`] backend.
//! If you are writing an app, you may want to look at [`eframe`](https://docs.rs/eframe) instead.
// Forbid warnings in release builds:

View file

@ -2,42 +2,6 @@
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release
fn create_display(
event_loop: &glutin::event_loop::EventLoop<()>,
) -> (
glutin::WindowedContext<glutin::PossiblyCurrent>,
glow::Context,
) {
let window_builder = glutin::window::WindowBuilder::new()
.with_resizable(true)
.with_inner_size(glutin::dpi::LogicalSize {
width: 800.0,
height: 600.0,
})
.with_title("egui_glow example");
let gl_window = unsafe {
glutin::ContextBuilder::new()
.with_depth_buffer(0)
.with_srgb(true)
.with_stencil_buffer(0)
.with_vsync(true)
.build_windowed(window_builder, event_loop)
.unwrap()
.make_current()
.unwrap()
};
let gl = unsafe { glow::Context::from_loader_function(|s| gl_window.get_proc_address(s)) };
unsafe {
use glow::HasContext as _;
gl.enable(glow::FRAMEBUFFER_SRGB);
}
(gl_window, gl)
}
fn main() {
let mut clear_color = [0.1, 0.1, 0.1];
@ -116,3 +80,39 @@ fn main() {
}
});
}
fn create_display(
event_loop: &glutin::event_loop::EventLoop<()>,
) -> (
glutin::WindowedContext<glutin::PossiblyCurrent>,
glow::Context,
) {
let window_builder = glutin::window::WindowBuilder::new()
.with_resizable(true)
.with_inner_size(glutin::dpi::LogicalSize {
width: 800.0,
height: 600.0,
})
.with_title("egui_glow example");
let gl_window = unsafe {
glutin::ContextBuilder::new()
.with_depth_buffer(0)
.with_srgb(true)
.with_stencil_buffer(0)
.with_vsync(true)
.build_windowed(window_builder, event_loop)
.unwrap()
.make_current()
.unwrap()
};
let gl = unsafe { glow::Context::from_loader_function(|s| gl_window.get_proc_address(s)) };
unsafe {
use glow::HasContext as _;
gl.enable(glow::FRAMEBUFFER_SRGB);
}
(gl_window, gl)
}

View file

@ -39,24 +39,23 @@ pub use epi::NativeOptions;
/// Run an egui app
#[allow(unsafe_code)]
pub fn run(app: Box<dyn epi::App>, native_options: &epi::NativeOptions) -> ! {
let persistence = egui_winit::epi::Persistence::from_app_name(app.name());
pub fn run(app_name: &str, native_options: &epi::NativeOptions, app_creator: epi::AppCreator) -> ! {
let persistence = egui_winit::epi::Persistence::from_app_name(app_name);
let window_settings = persistence.load_window_settings();
let window_builder =
egui_winit::epi::window_builder(native_options, &window_settings).with_title(app.name());
egui_winit::epi::window_builder(native_options, &window_settings).with_title(app_name);
let event_loop = winit::event_loop::EventLoop::with_user_event();
let (gl_window, gl) = create_display(window_builder, &event_loop);
let gl = std::rc::Rc::new(gl);
let mut painter = crate::Painter::new(gl.clone(), None, "")
.unwrap_or_else(|error| panic!("some OpenGL error occurred {}\n", error));
let mut integration = egui_winit::epi::EpiIntegration::new(
"egui_glow",
painter.max_texture_side(),
gl_window.window(),
&gl,
persistence,
app,
);
{
@ -66,6 +65,17 @@ pub fn run(app: Box<dyn epi::App>, native_options: &epi::NativeOptions) -> ! {
});
}
let mut app = app_creator(&epi::CreationContext {
egui_ctx: integration.egui_ctx.clone(),
integration_info: integration.frame.info(),
storage: integration.persistence.storage(),
gl: gl.clone(),
});
if app.warm_up_enabled() {
integration.warm_up(app.as_mut(), gl_window.window());
}
let mut is_focused = true;
event_loop.run(move |event, _, control_flow| {
@ -84,7 +94,7 @@ pub fn run(app: Box<dyn epi::App>, native_options: &epi::NativeOptions) -> ! {
needs_repaint,
textures_delta,
shapes,
} = integration.update(gl_window.window());
} = integration.update(app.as_mut(), gl_window.window());
integration.handle_platform_output(gl_window.window(), platform_output);
@ -92,7 +102,7 @@ pub fn run(app: Box<dyn epi::App>, native_options: &epi::NativeOptions) -> ! {
// paint:
{
let color = integration.app.clear_color();
let color = app.clear_color();
unsafe {
use glow::HasContext as _;
gl.disable(glow::SCISSOR_TEST);
@ -120,7 +130,7 @@ pub fn run(app: Box<dyn epi::App>, native_options: &epi::NativeOptions) -> ! {
};
}
integration.maybe_autosave(gl_window.window());
integration.maybe_autosave(app.as_mut(), gl_window.window());
};
match event {
@ -139,7 +149,7 @@ pub fn run(app: Box<dyn epi::App>, native_options: &epi::NativeOptions) -> ! {
gl_window.resize(physical_size);
}
integration.on_event(&event);
integration.on_event(app.as_mut(), &event);
if integration.should_quit() {
*control_flow = winit::event_loop::ControlFlow::Exit;
}
@ -147,7 +157,10 @@ pub fn run(app: Box<dyn epi::App>, native_options: &epi::NativeOptions) -> ! {
gl_window.window().request_redraw(); // TODO: ask egui if the events warrants a repaint instead
}
winit::event::Event::LoopDestroyed => {
integration.on_exit(gl_window.window());
integration
.persistence
.save(&mut *app, &integration.egui_ctx, gl_window.window());
app.on_exit(&gl);
painter.destroy();
}
winit::event::Event::UserEvent(RequestRepaintEvent) => {

View file

@ -139,7 +139,7 @@ pub struct AppRunner {
}
impl AppRunner {
pub fn new(canvas_id: &str, app: Box<dyn epi::App>) -> Result<Self, JsValue> {
pub fn new(canvas_id: &str, app_creator: epi::AppCreator) -> Result<Self, JsValue> {
let painter = WrappedGlowPainter::new(canvas_id).map_err(JsValue::from)?;
let prefer_dark_mode = crate::prefer_dark_mode();
@ -177,6 +177,13 @@ impl AppRunner {
let storage = LocalStorage::default();
let app = app_creator(&epi::CreationContext {
egui_ctx: egui_ctx.clone(),
integration_info: frame.info(),
storage: Some(&storage),
gl: painter.painter.gl().clone(),
});
let mut runner = Self {
frame,
egui_ctx,
@ -194,11 +201,6 @@ impl AppRunner {
runner.input.raw.max_texture_side = Some(runner.painter.max_texture_side());
let gl = runner.painter.painter.gl();
runner
.app
.setup(&runner.egui_ctx, &runner.frame, Some(&runner.storage), gl);
Ok(runner)
}
@ -327,8 +329,8 @@ impl AppRunner {
/// Install event listeners to register different input events
/// and start running the given app.
pub fn start(canvas_id: &str, app: Box<dyn epi::App>) -> Result<AppRunnerRef, JsValue> {
let mut runner = AppRunner::new(canvas_id, app)?;
pub fn start(canvas_id: &str, app_creator: epi::AppCreator) -> Result<AppRunnerRef, JsValue> {
let mut runner = AppRunner::new(canvas_id, app_creator)?;
runner.warm_up()?;
start_runner(runner)
}

View file

@ -98,6 +98,30 @@ pub use glow; // Re-export for user convenience
use std::sync::{Arc, Mutex};
/// The is is how your app is created.
///
/// You can use the [`CreationContext`] to setup egui, restore state, setup OpenGL things, etc.
pub type AppCreator = fn(&CreationContext<'_>) -> Box<dyn App>;
/// Data that is passed to [`AppCreator`] that can be used to setup and initialize your app.
pub struct CreationContext<'s> {
/// The egui Context.
///
/// You can use this to customize the look of egui, e.g to call [`egui::Context::set_fonts`],
/// [`egui::Context::set_visuals`] etc.
pub egui_ctx: egui::Context,
/// Information about the surrounding environment.
pub integration_info: IntegrationInfo,
/// You can use the storage to restore app state(requires the "persistence" feature).
pub storage: Option<&'s dyn Storage>,
/// The [`glow::Context`] allows you to initialize OpenGL resources (e.g. shaders) that
/// you might want to use later from a [`egui::PaintCallback`].
pub gl: std::rc::Rc<glow::Context>,
}
// ----------------------------------------------------------------------------
/// Implement this trait to write apps that can be compiled both natively using the [`egui_glium`](https://github.com/emilk/egui/tree/master/egui_glium) crate,
@ -109,39 +133,20 @@ pub trait App {
///
/// The [`egui::Context`] and [`Frame`] can be cloned and saved if you like.
///
/// To force a repaint, call either [`egui::Context::request_repaint`] during the call to `update`,
/// or call [`Frame::request_repaint`] at any time (e.g. from another thread).
/// To force a repaint, call [`egui::Context::request_repaint`] at any time (e.g. from another thread).
fn update(&mut self, ctx: &egui::Context, frame: &Frame);
/// Called exactly once at startup, before any call to [`Self::update`].
///
/// Allows you to do setup code, e.g to call [`egui::Context::set_fonts`],
/// [`egui::Context::set_visuals`] etc.
///
/// Also allows you to restore state, if there is a storage (requires the "persistence" feature).
///
/// The [`glow::Context`] allows you to initialize OpenGL resources (e.g. shaders) that
/// you might want to use later from a [`egui::PaintCallback`].
fn setup(
&mut self,
_ctx: &egui::Context,
_frame: &Frame,
_storage: Option<&dyn Storage>,
_gl: &std::rc::Rc<glow::Context>,
) {
}
/// Called on shutdown, and perhaps at regular intervals. Allows you to save state.
///
/// Only called when the "persistence" feature is enabled.
///
/// On web the states is stored to "Local Storage".
/// On web the state is stored to "Local Storage".
/// On native the path is picked using [`directories_next::ProjectDirs::data_dir`](https://docs.rs/directories-next/2.0.0/directories_next/struct.ProjectDirs.html#method.data_dir) which is:
/// * Linux: `/home/UserName/.local/share/APPNAME`
/// * macOS: `/Users/UserName/Library/Application Support/APPNAME`
/// * Windows: `C:\Users\UserName\AppData\Roaming\APPNAME`
///
/// where `APPNAME` is what is returned by [`Self::name()`].
/// where `APPNAME` is what is given to `eframe::run_native`.
fn save(&mut self, _storage: &mut dyn Storage) {}
/// Called before an exit that can be aborted.
@ -156,17 +161,14 @@ pub trait App {
true
}
/// Called once on shutdown (before or after [`Self::save`]). If you need to abort an exit use
/// [`Self::on_exit_event`]
fn on_exit(&mut self) {}
/// Called once on shutdown, after [`Self::save`].
///
/// If you need to abort an exit use [`Self::on_exit_event`].
fn on_exit(&mut self, _gl: &glow::Context) {}
// ---------
// Settings:
/// The name of your App, used for the title bar of native windows
/// and the save location of persistence (see [`Self::save`]).
fn name(&self) -> &str;
/// Time between automatic calls to [`Self::save`]
fn auto_save_interval(&self) -> std::time::Duration {
std::time::Duration::from_secs(30)