-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
4 changed files
with
294 additions
and
300 deletions.
There are no files selected for viewing
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,292 @@ | ||
pub mod epi; | ||
pub mod wgpu_app; | ||
use crate::common::APP_NAME; | ||
use crate::UserEvent; | ||
use egui_winit::egui; | ||
use egui_winit::egui::Visuals; | ||
use egui_winit::winit; | ||
use egui_winit::winit::event::{Event, WindowEvent}; | ||
use std::sync::Arc; | ||
use std::time::Instant; | ||
use winit::event_loop::{EventLoop, EventLoopProxy, EventLoopWindowTarget}; | ||
|
||
|
||
/// Implement this trait to write apps that can be compiled for both web/wasm and desktop/native using [`eframe`](https://github.com/emilk/egui/tree/master/crates/eframe). | ||
pub trait App { | ||
/// Called each time the UI needs repainting, which may be many times per second. | ||
/// | ||
/// Put your widgets into a [`egui::SidePanel`], [`egui::TopBottomPanel`], [`egui::CentralPanel`], [`egui::Window`] or [`egui::Area`]. | ||
/// | ||
/// The [`egui::Context`] can be cloned and saved if you like. | ||
/// | ||
/// To force a repaint, call [`egui::Context::request_repaint`] at any time (e.g. from another thread). | ||
fn update(&mut self, ctx: &egui::Context); | ||
} | ||
|
||
#[derive(Debug)] | ||
pub enum NextPaint { | ||
/// Wait for an event | ||
Wait, | ||
|
||
/// Causes a synchronous repaint inside the event handler. This should only | ||
/// be used in special situations if the window must be repainted while | ||
/// handling a specific event. This occurs on Windows when handling resizes. | ||
/// | ||
/// `RepaintNow` creates a new frame synchronously, and should therefore | ||
/// only be used for extremely urgent repaints. | ||
RepaintNow, | ||
|
||
/// Queues a repaint for once the event loop handles its next redraw. Exists | ||
/// so that multiple input events can be handled in one frame. Does not | ||
/// cause any delay like `RepaintNow`. | ||
RepaintNext, | ||
|
||
/// Repaint at a particular time | ||
RepaintAt(Instant), | ||
|
||
/// Exit the event loop | ||
Exit, | ||
} | ||
|
||
struct WgpuWinitRunning { | ||
painter: egui_wgpu::winit::Painter, | ||
app: Box<dyn App>, | ||
window: winit::window::Window, | ||
follow_system_theme: bool, | ||
egui_ctx: egui::Context, | ||
pending_full_output: egui::FullOutput, | ||
egui_winit: egui_winit::State, | ||
} | ||
|
||
impl Drop for WgpuWinitRunning { | ||
fn drop(&mut self) { | ||
self.painter.destroy(); | ||
} | ||
} | ||
|
||
pub type AppCreator = Box<dyn Fn() -> Box<dyn App>>; | ||
|
||
pub struct WgpuWinitApp { | ||
repaint_proxy: Arc<std::sync::Mutex<EventLoopProxy<UserEvent>>>, | ||
app: AppCreator, | ||
running: Option<WgpuWinitRunning>, | ||
} | ||
|
||
impl WgpuWinitApp { | ||
pub fn new(event_loop: &EventLoop<UserEvent>, app: AppCreator) -> Self { | ||
Self { | ||
repaint_proxy: Arc::new(std::sync::Mutex::new(event_loop.create_proxy())), | ||
running: None, | ||
app, | ||
} | ||
} | ||
|
||
pub fn launch_window( | ||
&mut self, | ||
event_loop: &EventLoopWindowTarget<UserEvent>, | ||
) -> anyhow::Result<NextPaint> { | ||
let window = winit::window::WindowBuilder::new() | ||
.with_decorations(true) | ||
.with_resizable(true) | ||
.with_transparent(false) | ||
.with_title(APP_NAME) | ||
.with_inner_size(winit::dpi::PhysicalSize { | ||
width: 800, | ||
height: 600, | ||
}) | ||
.build(&event_loop)?; | ||
window.set_ime_allowed(true); | ||
|
||
let mut painter = | ||
egui_wgpu::winit::Painter::new(egui_wgpu::WgpuConfiguration::default(), 1, None, false); | ||
|
||
pollster::block_on(painter.set_window(Some(&window)))?; | ||
|
||
let mut egui_winit = egui_winit::State::new(event_loop); | ||
|
||
let max_texture_side = painter.max_texture_side().unwrap_or(2048); | ||
egui_winit.set_max_texture_side(max_texture_side); | ||
|
||
let native_pixels_per_point = window.scale_factor() as f32; | ||
egui_winit.set_pixels_per_point(native_pixels_per_point); | ||
|
||
let egui_ctx = egui::Context::default(); | ||
|
||
let system_theme = window.theme(); | ||
egui_ctx.set_visuals( | ||
system_theme | ||
.map(visuals_from_winit_theme) | ||
.unwrap_or(Visuals::light()), | ||
); | ||
|
||
let event_loop_proxy = self.repaint_proxy.clone(); | ||
egui_ctx.set_request_repaint_callback(move |info| { | ||
let when = Instant::now() + info.after; | ||
let frame_nr = info.current_frame_nr; | ||
event_loop_proxy | ||
.lock() | ||
.unwrap() | ||
.send_event(UserEvent::RequestRepaint { when, frame_nr }) | ||
.ok(); | ||
}); | ||
|
||
self.running = Some(WgpuWinitRunning { | ||
painter, | ||
app: (self.app)(), | ||
window, | ||
follow_system_theme: system_theme.is_some(), | ||
egui_ctx, | ||
pending_full_output: Default::default(), | ||
egui_winit, | ||
}); | ||
|
||
Ok(NextPaint::RepaintNow) | ||
} | ||
|
||
pub fn frame_nr(&self) -> u64 { | ||
self.running.as_ref().map_or(0, |r| r.egui_ctx.frame_nr()) | ||
} | ||
|
||
pub fn window(&self) -> Option<&winit::window::Window> { | ||
self.running.as_ref().map(|r| &r.window) | ||
} | ||
|
||
pub fn paint(&mut self) -> NextPaint { | ||
if let Some(running) = &mut self.running { | ||
let raw_input = running.egui_winit.take_egui_input(&running.window); | ||
|
||
// Run user code: | ||
let full_output = running.egui_ctx.run(raw_input, |egui_ctx| { | ||
running.app.update(egui_ctx); | ||
}); | ||
|
||
running.pending_full_output.append(full_output); | ||
let full_output = std::mem::take(&mut running.pending_full_output); | ||
|
||
let egui::FullOutput { | ||
platform_output, | ||
repaint_after, | ||
textures_delta, | ||
shapes, | ||
} = full_output; | ||
|
||
running.egui_winit.handle_platform_output( | ||
&running.window, | ||
&running.egui_ctx, | ||
platform_output, | ||
); | ||
|
||
let clipped_primitives = { running.egui_ctx.tessellate(shapes) }; | ||
|
||
let clear_color = | ||
egui::Color32::from_rgba_unmultiplied(12, 12, 12, 180).to_normalized_gamma_f32(); | ||
|
||
running.painter.paint_and_update_textures( | ||
running.egui_ctx.pixels_per_point(), | ||
clear_color, | ||
&clipped_primitives, | ||
&textures_delta, | ||
false, | ||
); | ||
|
||
if repaint_after.is_zero() { | ||
NextPaint::RepaintNext | ||
} else if let Some(repaint_after_instant) = Instant::now().checked_add(repaint_after) { | ||
NextPaint::RepaintAt(repaint_after_instant) | ||
} else { | ||
NextPaint::Wait | ||
} | ||
} else { | ||
NextPaint::Wait | ||
} | ||
} | ||
|
||
pub fn on_event( | ||
&mut self, | ||
event_loop: &EventLoopWindowTarget<UserEvent>, | ||
event: &Event<'_, UserEvent>, | ||
) -> anyhow::Result<NextPaint> { | ||
Ok(match event { | ||
// When the window is closed, we destroy the Window | ||
Event::WindowEvent { | ||
event: WindowEvent::CloseRequested, | ||
.. | ||
} => { | ||
self.running = None; | ||
NextPaint::Wait | ||
} | ||
|
||
Event::Resumed if self.running.is_none() => self.launch_window(event_loop)?, | ||
|
||
Event::WindowEvent { event, .. } => { | ||
if let Some(running) = &mut self.running { | ||
// On Windows, if a window is resized by the user, it should repaint synchronously, inside the | ||
// event handler. | ||
// | ||
// If this is not done, the compositor will assume that the window does not want to redraw, | ||
// and continue ahead. | ||
// | ||
// In eframe's case, that causes the window to rapidly flicker, as it struggles to deliver | ||
// new frames to the compositor in time. | ||
// | ||
// The flickering is technically glutin or glow's fault, but we should be responding properly | ||
// to resizes anyway, as doing so avoids dropping frames. | ||
// | ||
// See: https://github.com/emilk/egui/issues/903 | ||
let mut repaint_asap = false; | ||
|
||
match &event { | ||
WindowEvent::Resized(physical_size) => { | ||
repaint_asap = true; | ||
|
||
// Resize with 0 width and height is used by winit to signal a minimize event on Windows. | ||
// See: https://github.com/rust-windowing/winit/issues/208 | ||
// This solves an issue where the app would panic when minimizing on Windows. | ||
if physical_size.width > 0 && physical_size.height > 0 { | ||
running | ||
.painter | ||
.on_window_resized(physical_size.width, physical_size.height); | ||
} | ||
} | ||
WindowEvent::ScaleFactorChanged { new_inner_size, .. } => { | ||
repaint_asap = true; | ||
running | ||
.painter | ||
.on_window_resized(new_inner_size.width, new_inner_size.height); | ||
} | ||
WindowEvent::CloseRequested => { | ||
log::debug!("Received WindowEvent::CloseRequested"); | ||
return Ok(NextPaint::Exit); | ||
} | ||
WindowEvent::ThemeChanged(winit_theme) if running.follow_system_theme => { | ||
let visuals = visuals_from_winit_theme(*winit_theme); | ||
running.egui_ctx.set_visuals(visuals); | ||
} | ||
_ => {} | ||
}; | ||
|
||
let event_response = running.egui_winit.on_event(&running.egui_ctx, event); | ||
|
||
if event_response.repaint { | ||
if repaint_asap { | ||
NextPaint::RepaintNow | ||
} else { | ||
NextPaint::RepaintNext | ||
} | ||
} else { | ||
NextPaint::Wait | ||
} | ||
} else { | ||
NextPaint::Wait | ||
} | ||
} | ||
|
||
_ => NextPaint::Wait, | ||
}) | ||
} | ||
} | ||
|
||
fn visuals_from_winit_theme(theme: winit::window::Theme) -> Visuals { | ||
match theme { | ||
winit::window::Theme::Dark => Visuals::dark(), | ||
winit::window::Theme::Light => Visuals::light(), | ||
} | ||
} |
Oops, something went wrong.